diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6f8839d63..845b8ab35 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,7 +26,7 @@ If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. Windows] - Lazygit Version [e.g. v0.1.45] - - The last commit id if you built project from sources (run : ```git-rev parse HEAD```) + - The last commit id if you built project from sources (run : ```git rev-parse HEAD```) **Additional context** Add any other context about the problem here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 066322b58..2176fef97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,3 +63,43 @@ jobs: - name: Build darwin binary run: | GOOS=darwin go build + check-cheatsheet: + runs-on: ubuntu-latest + env: + GOFLAGS: -mod=vendor + GOARCH: amd64 + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v1 + with: + go-version: 1.16.x + - name: Cache build + uses: actions/cache@v1 + with: + path: ~/.cache/go-build + key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build + restore-keys: | + ${{runner.os}}-go- + - name: Check Cheatsheet + run: | + go run scripts/cheatsheet/main.go check + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Lint + uses: golangci/golangci-lint-action@v2 + with: + version: latest + - name: Format code + run: | + if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then + find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \; + exit 1 + fi + - name: errors + run: golangci-lint run + if: ${{ failure() }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 08e339d8d..000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Lint - -on: pull_request - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Lint - uses: golangci/golangci-lint-action@v2 - with: - version: latest - - name: Format code - run: | - if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then - find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \; - exit 1 - fi diff --git a/.gitignore b/.gitignore index 0e905b107..84258eeee 100644 --- a/.gitignore +++ b/.gitignore @@ -18,19 +18,21 @@ coverage.txt # Binaries lazygit +lazygit.exe # Exceptions !.gitignore !.goreleaser.yml !.circleci/ !.github/ +# these are for our integration tests +!.git_keep +!.gitmodules_keep test/git_server/data test/integration/*/actual/ test/integration/*/actual_remote/ test/integration/*/used_config/ # these sample hooks waste too much space -test/integration/*/expected/.git_keep/hooks/ -test/integration/*/expected_remote/hooks/ -!.git_keep/ -lazygit.exe +test/integration/*/expected/**/hooks/ +test/integration/*/expected_remote/**/hooks/ diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md index 308f11a15..931c6eb69 100644 --- a/CODE-OF-CONDUCT.md +++ b/CODE-OF-CONDUCT.md @@ -1,74 +1,3 @@ -# Contributor Covenant Code of Conduct +# Lazygit 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 leader](https://github.com/jesseduffield). -All complaints will be reviewed and investigated and will result in a response that -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 +Be nice, or face the wrath of the maintainer. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 574088cb7..71c43a3bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ 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 +## 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: @@ -14,10 +14,10 @@ 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. 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. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). -7. Issue that pull request! +4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +5. Issue that pull request! + +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. ## Code of conduct @@ -36,9 +36,60 @@ covers the project. Feel free to contact the maintainers if that's a concern. 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. + +## Internationalisation + +Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. 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. Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!). + +## 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")` + +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 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! + +If you want to trigger a debug session from VSCode, you can use the following snippet. Note that the 'console' key is not, at the time of writing, in a stable release. + +```jsonc +// .vscode/launch.json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "debug lazygit", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "main.go", + "console": "externalTerminal" // <-- you need this to actually see the lazygit UI in a window while debugging + } + ] +} +``` + +## 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. Lazygit has its own integration test system where you can build a sandbox repo with a shell script, record yourself doing something, and commit the resulting repo snapshot. It's pretty damn cool! To learn more see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.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 rending windows and handling user input. Here's the typical process to follow: +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 the 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`) @@ -46,7 +97,11 @@ Sometimes you will need to make a change in the gocui fork (https://github.com/j 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 -./bump_gocui.sh +./scripts/bump_gocui.sh ``` 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/README.md b/README.md index 4c523209a..218218130 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,13 @@ If you're a mere mortal like me and you're tired of hearing how powerful git is - [Binary releases](#binary-releases) - [Homebrew](#homebrew) - [MacPorts](#macports) + - [Ubuntu](#ubuntu) - [Void Linux](#void-linux) - [Scoop (Windows)](#scoop-windows) - [Arch Linux](#arch-linux) - [Fedora and CentOS 7](#fedora-and-centos-7) - [Solus Linux](#solus-linux) + - [Funtoo Linux](#funtoo-linux) - [FreeBSD](#freebsd) - [Conda](#conda) - [Go](#go) @@ -141,6 +143,14 @@ sudo dnf install lazygit sudo eopkg install lazygit ``` +### 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 +``` + ### FreeBSD ```sh @@ -159,7 +169,7 @@ conda install -c conda-forge lazygit ### Go ```sh -go get github.com/jesseduffield/lazygit +go install github.com/jesseduffield/lazygit@latest ``` Please note: @@ -256,7 +266,7 @@ See the [docs](docs/Custom_Command_Keybindings.md) - Easily check out recent branches - Scroll through logs/diffs of branches/commits/stash - Quick pushing/pulling -- Squash down and rename commits +- Squash down and reword commits ### Resolving merge conflicts diff --git a/docs/Config.md b/docs/Config.md index e253610cb..89c0b7d63 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -44,18 +44,18 @@ gui: show: true mouseEvents: true skipUnstageLineWarning: false - skipStashWarning: true - showFileTree: false # for rendering changes files in a tree format + skipStashWarning: false + showFileTree: true # for rendering changes files in a tree format showListFooter: true # for seeing the '5 of 20' message in list panels showRandomTip: true showCommandLog: true commandLogSize: 8 - authorColors: # in case you're not happy with the randomly assigned colour - 'John Smith': '#ff0000' git: paging: colorArg: always useConfig: false + commit: + signOff: false merging: # only applicable to unix users manualCommit: false @@ -77,6 +77,7 @@ git: disableForcePushing: false parseEmoji: false enableGhCommand: false + diffContextSize: 3 # how many lines of context are shown around a change in diffs os: editCommand: '' # see 'Configuring File Editing' section editCommandTemplate: '{{editor}} {{filename}}' @@ -154,6 +155,8 @@ keybinding: appendNewline: '' extrasMenu: '@' toggleWhitespaceInDiffView: '' + increaseContextInDiffView: '}' + decreaseContextInDiffView: '{' status: checkForUpdate: 'u' recentRepos: '' @@ -203,6 +206,7 @@ keybinding: resetCherryPick: '' copyCommitMessageToClipboard: '' openLogMenu: '' + viewBisectOptions: 'b' stash: popStash: 'g' commitFiles: @@ -374,6 +378,39 @@ Alternatively you may have bold fonts disabled in your terminal, in which case e If you're still having trouble please raise an issue. +## 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': '#ff0000' # use red for John Smith +``` + +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': '#ff0000' + # use blue for other authors + '*': '#0000ff' +``` + +## Custom Branch Color + +You can customize the color of branches based on the branch prefix: + +```yaml +gui: + branchColors: + 'docs': '#11aaff' # use a light blue for branches beginning with 'docs/' +``` + ## Example Coloring ![border example](../../assets/colored-border-example.png) diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md index 8b0c769c2..1dd27fef3 100644 --- a/docs/Custom_Command_Keybindings.md +++ b/docs/Custom_Command_Keybindings.md @@ -69,6 +69,7 @@ For a given custom command, here are the allowed fields: | 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 | text to display in the keybindings menu that appears when you press 'x' | no | +| stream | whether you want to stream the command's output to the Command Log panel | no | ### Contexts diff --git a/docs/Integration_Tests.md b/docs/Integration_Tests.md index 9e550ece8..1bb797b3a 100644 --- a/docs/Integration_Tests.md +++ b/docs/Integration_Tests.md @@ -33,45 +33,68 @@ git commit -am "myfile1" ## Running tests -To run all tests +### From a TUI + +You can run/record/sandbox tests via a TUI with the following command: + ``` -go test pkg/gui/gui_test.go +go run test/lazyintegration/main.go +``` + +This TUI makes much of the following documentation redundant, but feel free to read through anyway! + +### From command line + +To run all tests - assuming you're at the project root: + +``` +go test ./pkg/gui/ ``` To run them in parallel + ``` -PARALLEL=true go test pkg/gui/gui_test.go +PARALLEL=true go test ./pkg/gui ``` To run a single test + ``` -go test pkg/gui/gui_test.go -run / +go test ./pkg/gui -run / +# For example, to run the `tags` test: +go test ./pkg/gui -run /tags ``` To run a test at a certain speed + ``` -SPEED=2 go test pkg/gui/gui_test.go -run / +SPEED=2 go test ./pkg/gui -run / ``` To update a snapshot + ``` -UPDATE_SNAPSHOTS=true go test pkg/gui/gui_test.go -run / +MODE=updateSnapshot go test ./pkg/gui -run / ``` ## Creating a new test To create a new test: -1) Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test. -2) Update the `setup.sh` any way you like -3) If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used. -4) From the lazygit root directory, run: + +1. Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test. +2. Update the `setup.sh` any way you like +3. If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used. +4. From the lazygit root directory, run: + ``` -RECORD_EVENTS=true go test pkg/gui/gui_test.go -run / +MODE=record go test ./pkg/gui -run / ``` -5) Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better! -6) Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created. + +5. Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better! +6. Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created. The resulting directory will look like: + ``` actual/ (the resulting repo after running the test, ignored by git) expected/ (the 'snapshot' repo) @@ -83,6 +106,14 @@ recording.json Feel free to create a hierarchy of directories in the `test/integration` directory to group tests by feature. +## Sandboxing + +The integration tests serve a secondary purpose of providing a setup for easy sandboxing. If you want to run a test in sandbox mode (meaning the session won't be recorded and we won't create/update snapshots), go: + +``` +MODE=sandbox go test ./pkg/gui -run / +``` + ## Feedback If you think this process can be improved, let me know! It shouldn't be too hard to change things. diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index 2478c6906..e855bb056 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -1,3 +1,5 @@ +_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._ + # Lazygit Keybindings ## Global Keybindings @@ -21,6 +23,8 @@ W: open diff menu ctrl+e: open diff menu @: open command log menu + }: Increase the size of the context shown around changes in the diff view + {: Decrease the size of the context shown around changes in the diff view ## List Panel Navigation @@ -119,9 +123,10 @@ ## Commits Panel (Commits)
+  ctrl+l: open log menu
   s: squash down
   r: reword commit
-  R: rename commit with editor
+  R: reword commit with editor
   g: reset to this commit
   f: fixup commit
   F: create fixup commit for this commit
@@ -143,6 +148,8 @@
   T: tag commit
   ctrl+r: reset cherry-picked (copied) commits selection
   ctrl+y: copy commit message to clipboard
+  o: open commit in browser
+  b: view bisect options
 
## Commits Panel (Reflog Tab) @@ -163,6 +170,12 @@ @: open command log menu +## Files Panel + +
+  ctrl+b: Filter commit files
+
+ ## Files Panel (Files)
@@ -194,7 +207,7 @@
 
   ctrl+o: copy submodule name to clipboard
   enter: enter submodule
-  d: view reset and remove submodule options
+  d: remove submodule
   u: update submodule
   n: add new submodule
   e: update submodule URL
@@ -205,6 +218,8 @@
 ## Main Panel (Merging)
 
 
+  H: scroll left
+  L: scroll right
   esc: return to files panel
   M: open external merge tool (git mergetool)
   space: pick hunk
@@ -232,10 +247,13 @@
   : select next line
   : select previous hunk
   : select next hunk
+  ctrl+o: copy the selected text to the clipboard
   space: add/remove line(s) to patch
   v: toggle drag select
   V: toggle drag select
   a: toggle select hunk
+  H: scroll left
+  L: scroll right
 
## Main Panel (Staging) @@ -250,11 +268,14 @@ : select next line : select previous hunk : select next hunk + ctrl+o: copy the selected text to the clipboard e: edit file o: open file v: toggle drag select V: toggle drag select a: toggle select hunk + H: scroll left + L: scroll right c: commit changes w: commit changes without pre-commit hook C: commit changes using git editor diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index 0f547524f..6f05b66b3 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -1,3 +1,5 @@ +_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._ + # Lazygit Sneltoetsen ## Globale Sneltoetsen @@ -21,6 +23,8 @@ W: open diff menu ctrl+e: open diff menu @: open command log menu + }: Increase the size of the context shown around changes in the diff view + {: Decrease the size of the context shown around changes in the diff view
## Lijstpaneel Navigatie @@ -119,6 +123,7 @@ ## Commits Paneel (Commits)
+  ctrl+l: open log menu
   s: squash beneden
   r: hernoem commit
   R: hernoem commit met editor
@@ -143,6 +148,8 @@
   T: tag commit
   ctrl+r: reset cherry-picked (gekopieerde) commits selectie
   ctrl+y: kopieer commit bericht naar klembord
+  o: open commit in browser
+  b: view bisect options
 
## Commits Paneel (Reflog Tabblad) @@ -163,6 +170,12 @@ @: open command log menu
+## Bestanden Paneel + +
+  ctrl+b: Commit dossiers filteren
+
+ ## Bestanden Paneel (Bestanden)
@@ -194,7 +207,7 @@
 
   ctrl+o: kopieer submodule naam naar klembord
   enter: enter submodule
-  d: bekijk reset en verwijder submodule opties
+  d: remove submodule
   u: update submodule
   n: voeg nieuwe submodule toe
   e: update submodule URL
@@ -205,6 +218,8 @@
 ## Hoofd Paneel (Mergen)
 
 
+  H: scroll left
+  L: scroll right
   esc: ga terug naar het bestanden paneel
   M: open external merge tool (git mergetool)
   space: kies hunk
@@ -232,10 +247,13 @@
   : selecteer de volgende lijn
   : selecteer de vorige hunk
   : selecteer de volgende hunk
+  ctrl+o: copy the selected text to the clipboard
   space: voeg toe/verwijder lijn(en) in patch
   v: toggle drag selecteer
   V: toggle drag selecteer
   a: toggle selecteer hunk
+  H: scroll left
+  L: scroll right
 
## Hoofd Paneel (Staging) @@ -250,11 +268,14 @@ : selecteer de volgende lijn : selecteer de vorige hunk : selecteer de volgende hunk + ctrl+o: copy the selected text to the clipboard e: verander bestand o: open bestand v: toggle drag selecteer V: toggle drag selecteer a: toggle selecteer hunk + H: scroll left + L: scroll right c: Commit veranderingen w: commit veranderingen zonder pre-commit hook C: commit veranderingen met de git editor diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 21f9ec093..95e713826 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -1,3 +1,5 @@ +_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._ + # Lazygit Keybindings ## Globalne @@ -6,7 +8,7 @@ ctrl+r: switch to a recent repo () pgup: scroll up main panel (fn+up) pgdown: scroll down main panel (fn+down) - m: view merge/rebase options + m: widok scalenia/opcje zmiany bazy ctrl+p: view custom patch options P: push p: pull @@ -16,11 +18,13 @@ ctrl+z: redo (via reflog) (experimental) +: next screen mode (normal/half/fullscreen) _: prev screen mode - :: execute custom command + :: wykonaj własną komendę ctrl+s: view filter-by-path options W: open diff menu ctrl+e: open diff menu @: open command log menu + }: Increase the size of the context shown around changes in the diff view + {: Decrease the size of the context shown around changes in the diff view
## List Panel Navigation @@ -39,18 +43,18 @@
   space: przełącz
-  o: utwórz żądanie wyciągnięcia
+  o: utwórz żądanie pobrania
   O: utwórz opcje żądania ściągnięcia
-  ctrl+y: skopiuj adres URL żądania ściągnięcia do schowka
+  ctrl+y: skopiuj adres URL żądania pobrania do schowka
   c: przełącz używając nazwy
   F: wymuś przełączenie
   n: nowa gałąź
   d: usuń gałąź
-  r: rebase branch
+  r: zmiana bazy gałęzi
   M: scal do obecnej gałęzi
   i: show git-flow options
   f: fast-forward this branch from its upstream
-  g: view reset options
+  g: wyświetl opcje resetu
   R: rename branch
   ctrl+o: copy branch name to clipboard
   enter: view commits
@@ -59,14 +63,14 @@
 ## Gałęzie Panel (Remote Branches (in Remotes tab))
 
 
-  esc: return to remotes list
-  g: view reset options
+  esc: wróć do listy repozytoriów zdalnych
+  g: wyświetl opcje resetu
   enter: view commits
   space: przełącz
   n: nowa gałąź
   M: scal do obecnej gałęzi
   d: usuń gałąź
-  r: rebase branch
+  r: zmiana bazy gałęzi
   u: set as upstream of checked-out branch
 
@@ -82,12 +86,12 @@ ## Gałęzie Panel (Sub-commits)
-  enter: view commit's files
+  enter: przeglądaj pliki commita
   space: checkout commit
-  g: view reset options
+  g: wyświetl opcje resetu
   n: nowa gałąź
-  c: copy commit (cherry-pick)
-  C: copy commit range (cherry-pick)
+  c: kopiuj commit (przebieranie)
+  C: kopiuj zakres commitów (przebieranie)
   ctrl+r: reset cherry-picked (copied) commits selection
   ctrl+o: copy commit SHA to clipboard
 
@@ -99,16 +103,16 @@ d: delete tag P: push tag n: create tag - g: view reset options + g: wyświetl opcje resetu enter: view commits
-## Commit files Panel +## Pliki commita Panel
   ctrl+o: copy the committed file name to the clipboard
-  c: checkout file
-  d: discard this commit's changes to this file
+  c: plik wybierania
+  d: porzuć zmiany commita dla tego pliku
   o: otwórz plik
   e: edytuj plik
   space: toggle file included in patch
@@ -119,40 +123,43 @@
 ## Commity Panel (Commity)
 
 
-  s: ściśnij w dół
-  r: przemianuj commit
-  R: przemianuj commit w edytorze
+  ctrl+l: open log menu
+  s: ściśnij
+  r: zmień nazwę commita
+  R: zmień nazwę commita w edytorze
   g: zresetuj do tego commita
   f: napraw commit
-  F: create fixup commit for this commit
-  S: squash all 'fixup!' commits above selected commits (autosquash)
-  d: delete commit
-  ctrl+j: move commit down one
-  ctrl+k: move commit up one
-  e: edit commit
-  A: amend commit with staged changes
-  p: pick commit (when mid-rebase)
-  t: revert commit
-  c: copy commit (cherry-pick)
+  F: utwórz commit naprawczy dla tego commita
+  S: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
+  d: usuń commit
+  ctrl+j: przenieś commit 1 w dół
+  ctrl+k: przenieś commit 1 w górę
+  e: edytuj commit
+  A: popraw commit zmianami z poczekalni
+  p: wybierz commit (podczas zmiany bazy)
+  t: odwróć commit
+  c: kopiuj commit (przebieranie)
   ctrl+o: copy commit SHA to clipboard
-  C: copy commit range (cherry-pick)
-  v: paste commits (cherry-pick)
-  enter: view commit's files
+  C: kopiuj zakres commitów (przebieranie)
+  v: wklej commity (przebieranie)
+  enter: przeglądaj pliki commita
   space: checkout commit
   n: create new branch off of commit
   T: tag commit
   ctrl+r: reset cherry-picked (copied) commits selection
   ctrl+y: copy commit message to clipboard
+  o: open commit in browser
+  b: view bisect options
 
## Commity Panel (Reflog Tab)
-  enter: view commit's files
+  enter: przeglądaj pliki commita
   space: checkout commit
-  g: view reset options
-  c: copy commit (cherry-pick)
-  C: copy commit range (cherry-pick)
+  g: wyświetl opcje resetu
+  c: kopiuj commit (przebieranie)
+  C: kopiuj zakres commitów (przebieranie)
   ctrl+r: reset cherry-picked (copied) commits selection
   ctrl+o: copy commit SHA to clipboard
 
@@ -163,25 +170,31 @@ @: open command log menu
+## Pliki Panel + +
+  ctrl+b: Filtrowanie commitów
+
+ ## Pliki Panel (Pliki)
-  c: commituj zmiany
-  w: commit changes without pre-commit hook
-  A: zmień ostatnie zatwierdzenie
-  C: commituj zmiany używając edytora z gita
-  space: przełącz zatwierdzenie
-  d: view 'discard changes' options
+  c: Zatwierdź zmiany
+  w: zatwierdź zmiany bez skryptu pre-commit
+  A: Zmień ostatni commit
+  C: Zatwierdź zmiany używając edytora
+  space: przełącz stan poczekalni
+  d: pokaż opcje porzucania zmian
   e: edytuj plik
   o: otwórz plik
   i: dodaj do .gitignore
   r: odśwież pliki
-  s: przechowaj pliki
-  S: view stash options
-  a: przełącz wszystkie zatwierdzenia
-  D: view reset options
+  s: przechowaj zmiany
+  S: wyświetl opcje schowka
+  a: przełącz stan poczekalni wszystkich
+  D: wyświetl opcje resetu
   enter: zatwierdź pojedyncze linie
-  f: fetch
+  f: pobierz
   ctrl+o: copy the file name to the clipboard
   g: view upstream reset options
   `: toggle file tree view
@@ -194,7 +207,7 @@
 
   ctrl+o: copy submodule name to clipboard
   enter: enter submodule
-  d: view reset and remove submodule options
+  d: remove submodule
   u: update submodule
   n: add new submodule
   e: update submodule URL
@@ -202,43 +215,48 @@
   b: view bulk submodule options
 
-## Main Panel (Merging) +## Główne Panel (Scalanie)
+  H: scroll left
+  L: scroll right
   esc: wróć do panelu plików
   M: open external merge tool (git mergetool)
-  space: pick hunk
-  b: pick all hunks
-  : select previous conflict
-  : select next conflict
-  : select previous hunk
-  : select next hunk
+  space: wybierz kawałek
+  b: wybierz wszystkie kawałki
+  : poprzedni konflikt
+  : następny konflikt
+  : wybierz poprzedni kawałek
+  : wybierz następny kawałek
   z: cofnij
 
-## Main Panel (Normal) +## Główne Panel (Zwykłe)
-  Ő: scroll down (fn+up)
-  ő: scroll up (fn+down)
+  Ő: przewiń w dół (fn+up)
+  ő: przewiń w górę (fn+down)
 
-## Main Panel (Patch Building) +## Główne Panel (Patch Building)
-  esc: exit line-by-line mode
+  esc: wyście z trybu "linia po linii"
   o: otwórz plik
-  : select previous line
-  : select next line
-  : select previous hunk
-  : select next hunk
+  : poprzednia linia
+  : następna linia
+  : poprzedni kawałek
+  : następny kawałek
+  ctrl+o: copy the selected text to the clipboard
   space: add/remove line(s) to patch
   v: toggle drag select
   V: toggle drag select
   a: toggle select hunk
+  H: scroll left
+  L: scroll right
 
-## Main Panel (Zatwierdzanie) +## Główne Panel (Poczekalnia)
   esc: wróć do panelu plików
@@ -246,18 +264,21 @@
   d: delete change (git reset)
   tab: switch to other panel
   o: otwórz plik
-  : select previous line
-  : select next line
-  : select previous hunk
-  : select next hunk
+  : poprzednia linia
+  : następna linia
+  : poprzedni kawałek
+  : następny kawałek
+  ctrl+o: copy the selected text to the clipboard
   e: edytuj plik
   o: otwórz plik
   v: toggle drag select
   V: toggle drag select
   a: toggle select hunk
-  c: commituj zmiany
-  w: commit changes without pre-commit hook
-  C: commituj zmiany używając edytora z gita
+  H: scroll left
+  L: scroll right
+  c: Zatwierdź zmiany
+  w: zatwierdź zmiany bez skryptu pre-commit
+  C: Zatwierdź zmiany używając edytora
 
## Menu Panel @@ -279,9 +300,9 @@ ## Status Panel
-  e: edytuj plik konfiguracyjny
-  o: otwórz plik konfiguracyjny
+  e: edytuj konfigurację
+  o: otwórz konfigurację
   u: sprawdź aktualizacje
   enter: switch to a recent repo
-  a: pokazywać wszystkie logi branżowe
+  a: pokaż wszystkie logi gałęzi
 
diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md new file mode 100644 index 000000000..42a4fd1c7 --- /dev/null +++ b/docs/keybindings/Keybindings_zh.md @@ -0,0 +1,308 @@ +_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._ + +# Lazygit 按键绑定 + +## 全局键绑定 + +
+  ctrl+r: 切换到最近的仓库 ()
+  pgup: 向上滚动主面板 (fn+up)
+  pgdown: 向下滚动主面板 (fn+down)
+  m: 查看 合并/变基 选项
+  ctrl+p: 查看自定义补丁选项
+  P: 推送
+  p: 拉取
+  R: 刷新
+  x: 打开菜单
+  z: (通过 reflog)撤销「实验功能」
+  ctrl+z: (通过 reflog)重做「实验功能」
+  +: 下一屏模式(正常/半屏/全屏)
+  _: 上一屏模式
+  :: 执行自定义命令
+  ctrl+s: 查看按路径过滤选项
+  W: 打开 diff 菜单
+  ctrl+e: 打开 diff 菜单
+  @: 打开命令日志菜单
+  }: Increase the size of the context shown around changes in the diff view
+  {: Decrease the size of the context shown around changes in the diff view
+
+ +## 列表面板导航 + +
+  .: 下一页
+  ,: 上一页
+  <: 滚动到顶部
+  >: 滚动到底部
+  /: 开始搜索
+  ]: 下一个标签
+  [: 上一个标签
+
+ +## 分支 面板 (分支标签) + +
+  space: 检出
+  o: 创建抓取请求
+  O: 创建抓取请求选项
+  ctrl+y: 将抓取请求 URL 复制到剪贴板
+  c: 按名称检出
+  F: 强制检出
+  n: 新分支
+  d: 删除分支
+  r: 将已检出的分支变基到该分支
+  M: 合并到当前检出的分支
+  i: 显示 git-flow 选项
+  f: 从上游快进此分支
+  g: 查看重置选项
+  R: 重命名分支
+  ctrl+o: 将分支名称复制到剪贴板
+  enter: 查看提交
+
+ +## 分支 面板 (远程分支(在远程页面中)) + +
+  esc: 返回远程仓库列表
+  g: 查看重置选项
+  enter: 查看提交
+  space: 检出
+  n: 新分支
+  M: 合并到当前检出的分支
+  d: 删除分支
+  r: 将已检出的分支变基到该分支
+  u: 设置为检出分支的上游
+
+ +## 分支 面板 (远程页面) + +
+  f: 抓取远程仓库
+  n: 添加新的远程仓库
+  d: 删除远程
+  e: 编辑远程仓库
+
+ +## 分支 面板 (子提交) + +
+  enter: 查看提交的文件
+  space: 检出提交
+  g: 查看重置选项
+  n: 新分支
+  c: 复制提交(拣选)
+  C: 复制提交范围(拣选)
+  ctrl+r: 重置已拣选(复制)的提交
+  ctrl+o: 将提交的 SHA 复制到剪贴板
+
+ +## 分支 面板 (标签页面) + +
+  space: 检出
+  d: 删除标签
+  P: 推送标签
+  n: 创建标签
+  g: 查看重置选项
+  enter: 查看提交
+
+ +## 提交文件 面板 + +
+  ctrl+o: 将提交的文件名复制到剪贴板
+  c: 检出文件
+  d: 放弃对此文件的提交更改
+  o: 打开文件
+  e: 编辑文件
+  space: 补丁中包含的切换文件
+  enter: 输入文件以将所选行添加到补丁中(或切换目录折叠)
+  `: 切换文件树视图
+
+ +## 提交 面板 (提交) + +
+  ctrl+l: open log menu
+  s: 向下压缩
+  r: 改写提交
+  R: 使用编辑器重命名提交
+  g: 重置为此提交
+  f: 修正提交(fixup)
+  F: 为此提交创建修正
+  S: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
+  d: 删除提交
+  ctrl+j: 下移提交
+  ctrl+k: 上移提交
+  e: 编辑提交
+  A: 用已暂存的更改来修补提交
+  p: 选择提交(变基过程中)
+  t: 还原提交
+  c: 复制提交(拣选)
+  ctrl+o: 将提交的 SHA 复制到剪贴板
+  C: 复制提交范围(拣选)
+  v: 粘贴提交(拣选)
+  enter: 查看提交的文件
+  space: 检出提交
+  n: 从提交创建新分支
+  T: 标签提交
+  ctrl+r: 重置已拣选(复制)的提交
+  ctrl+y: 将提交消息复制到剪贴板
+  o: open commit in browser
+  b: view bisect options
+
+ +## 提交 面板 (Reflog) + +
+  enter: 查看提交的文件
+  space: 检出提交
+  g: 查看重置选项
+  c: 复制提交(拣选)
+  C: 复制提交范围(拣选)
+  ctrl+r: 重置已拣选(复制)的提交
+  ctrl+o: 将提交的 SHA 复制到剪贴板
+
+ +## Extras 面板 + +
+  @: 打开命令日志菜单
+
+ +## 文件 面板 + +
+  ctrl+b: 过滤提交文件
+
+ +## 文件 面板 (文件) + +
+  c: 提交更改
+  w: 提交更改而无需预先提交钩子
+  A: 修补最后一次提交
+  C: 提交更改(使用编辑器编辑提交信息)
+  space: 切换暂存状态
+  d: 查看'放弃更改‘选项
+  e: 编辑文件
+  o: 打开文件
+  i: 添加到 .gitignore
+  r: 刷新文件
+  s: 将所有更改加入贮藏
+  S: 查看隐藏选项
+  a: 切换所有文件的暂存状态
+  D: 查看重置选项
+  enter: 暂存单个 块/行 用于文件, 或 折叠/展开 目录
+  f: 抓取
+  ctrl+o: 将文件名复制到剪贴板
+  g: 查看上游重置选项
+  `: 切换文件树视图
+  M: 打开合并工具
+  ctrl+w: 切换是否在差异视图中显示空白更改
+
+ +## 文件 面板 (子模块) + +
+  ctrl+o: 将子模块名称复制到剪贴板
+  enter: 输入子模块
+  d: 删除子模块
+  u: 更新子模块
+  n: 添加新的子模块
+  e: 更新子模块 URL
+  i: 初始化子模块
+  b: 查看批量子模块选项
+
+ +## 主要 面板 (合并中) + +
+  H: scroll left
+  L: scroll right
+  esc: 返回文件面板
+  M: 打开合并工具
+  space: 选中区块
+  b: 选中所有区块
+  : 选择上一个冲突
+  : 选择下一个冲突
+  : 选择顶部块
+  : 选择底部块
+  z: 撤销
+
+ +## 主要 面板 (正常) + +
+  Ő: 向下滚动 (fn+up)
+  ő: 向上滚动 (fn+down)
+
+ +## 主要 面板 (构建补丁中) + +
+  esc: 退出逐行模式
+  o: 打开文件
+  : 选择上一行
+  : 选择下一行
+  : 选择上一个区块
+  : 选择下一个区块
+  ctrl+o: copy the selected text to the clipboard
+  space: 添加/移除 行到补丁
+  v: 切换拖动选择
+  V: 切换拖动选择
+  a: 切换选择区块
+  H: scroll left
+  L: scroll right
+
+ +## 主要 面板 (正在暂存) + +
+  esc: 返回文件面板
+  space: 切换行暂存状态
+  d: 取消变更 (git reset)
+  tab: 切换到其他面板
+  o: 打开文件
+  : 选择上一行
+  : 选择下一行
+  : 选择上一个区块
+  : 选择下一个区块
+  ctrl+o: copy the selected text to the clipboard
+  e: 编辑文件
+  o: 打开文件
+  v: 切换拖动选择
+  V: 切换拖动选择
+  a: 切换选择区块
+  H: scroll left
+  L: scroll right
+  c: 提交更改
+  w: 提交更改而无需预先提交钩子
+  C: 提交更改(使用编辑器编辑提交信息)
+
+ +## 菜单 面板 + +
+  esc: 关闭菜单
+
+ +## 贮藏 面板 + +
+  enter: 查看贮藏条目中的文件
+  space: 应用
+  g: 应用并删除
+  d: 删除
+  n: 新分支
+
+ +## 状态 面板 + +
+  e: 编辑配置文件
+  o: 打开配置文件
+  u: 检查更新
+  enter: 切换到最近的仓库
+  a: 显示所有分支的日志
+
diff --git a/go.mod b/go.mod index 7f7c8a369..860b7e6db 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/creack/pty v1.1.11 github.com/fatih/color v1.9.0 // indirect github.com/fsnotify/fsnotify v1.4.7 + github.com/gdamore/tcell/v2 v2.4.1-0.20210926162909-66f061b1fc9b // indirect github.com/go-errors/errors v1.4.1 github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 @@ -20,7 +21,7 @@ require ( github.com/imdario/mergo v0.3.11 github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 - github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474 + github.com/jesseduffield/gocui v0.3.1-0.20220108045521-1945d7b9ed8b github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e github.com/jesseduffield/yaml v2.1.0+incompatible github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 @@ -33,14 +34,16 @@ require ( github.com/mgutz/str v1.2.0 github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/gomega v1.7.1 // indirect + github.com/pmezard/go-difflib v1.0.0 github.com/sahilm/fuzzy v0.1.0 + github.com/sanity-io/litter v1.5.2 github.com/sirupsen/logrus v1.4.2 github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad github.com/stretchr/testify v1.7.0 github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect - golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 diff --git a/go.sum b/go.sum index 61c62f973..69871e0ab 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,7 @@ github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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= @@ -34,6 +35,8 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM= github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= +github.com/gdamore/tcell/v2 v2.4.1-0.20210926162909-66f061b1fc9b h1:eoaSI4eEwM5eTx/HvmRSwmicxuMhL73AyoEfM1oCJLc= +github.com/gdamore/tcell/v2 v2.4.1-0.20210926162909-66f061b1fc9b/go.mod h1:ZPwXnysybtQqdqKcWMWXux9aGdtMHe+kr+cwEZEe+A4= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= @@ -71,8 +74,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg= github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474 h1:4H/oJcUmwJpqyXzqfn+lsjQ/bjpm/HszzLrVbCjgqj4= -github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU= +github.com/jesseduffield/gocui v0.3.1-0.20220108045521-1945d7b9ed8b h1:AUK5nDiPiaahBtGIsf8rITgZ9SC+uddvnNKs0/mrYA8= +github.com/jesseduffield/gocui v0.3.1-0.20220108045521-1945d7b9ed8b/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU= 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/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE= @@ -124,6 +127,7 @@ github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -131,6 +135,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +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/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -140,9 +146,9 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -178,8 +184,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w 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-20211102061401-a2f17f7b995c h1:QOfDMdrf/UwlVR0UBq2Mpr58UzNtvgJRXA4BgPfFACs= -golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/main.go b/main.go index 0b6a97e64..c6c097146 100644 --- a/main.go +++ b/main.go @@ -71,8 +71,12 @@ func main() { log.Fatal("--path option is incompatible with the --work-tree and --git-dir options") } - workTree = repoPath - gitDir = filepath.Join(repoPath, ".git") + absRepoPath, err := filepath.Abs(repoPath) + if err != nil { + log.Fatal(err) + } + workTree = absRepoPath + gitDir = filepath.Join(absRepoPath, ".git") } if customConfig != "" { diff --git a/pkg/app/app.go b/pkg/app/app.go index c3e4042c5..cb3220cee 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -17,6 +17,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands" "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/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/gui" @@ -27,14 +28,11 @@ import ( // App struct type App struct { - closers []io.Closer - + *common.Common + closers []io.Closer Config config.AppConfigurer - Log *logrus.Entry OSCommand *oscommands.OSCommand - GitCommand *commands.GitCommand Gui *gui.Gui - Tr *i18n.TranslationSet Updater *updates.Updater // may only need this on the Gui ClientContext string } @@ -44,7 +42,7 @@ type errorMapping struct { newError string } -func newProductionLogger(config config.AppConfigurer) *logrus.Logger { +func newProductionLogger() *logrus.Logger { log := logrus.New() log.Out = ioutil.Discard log.SetLevel(logrus.ErrorLevel) @@ -60,7 +58,7 @@ func getLogLevel() logrus.Level { return level } -func newDevelopmentLogger(configurer config.AppConfigurer) *logrus.Logger { +func newDevelopmentLogger() *logrus.Logger { logger := logrus.New() logger.SetLevel(getLogLevel()) logPath, err := config.LogPath() @@ -69,7 +67,7 @@ func newDevelopmentLogger(configurer config.AppConfigurer) *logrus.Logger { } file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { - panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function) + log.Fatalf("Unable to log to log file: %v", err) } logger.SetOutput(file) return logger @@ -78,9 +76,9 @@ func newDevelopmentLogger(configurer config.AppConfigurer) *logrus.Logger { func newLogger(config config.AppConfigurer) *logrus.Entry { var log *logrus.Logger if config.GetDebug() || os.Getenv("DEBUG") == "TRUE" { - log = newDevelopmentLogger(config) + log = newDevelopmentLogger() } else { - log = newProductionLogger(config) + log = newProductionLogger() } // highly recommended: tail -f development.log | humanlog @@ -97,27 +95,35 @@ func newLogger(config config.AppConfigurer) *logrus.Entry { // NewApp bootstrap a new application func NewApp(config config.AppConfigurer, filterPath string) (*App, error) { + userConfig := config.GetUserConfig() app := &App{ closers: []io.Closer{}, Config: config, } var err error - app.Log = newLogger(config) - app.Tr, err = i18n.NewTranslationSetFromConfig(app.Log, config.GetUserConfig().Gui.Language) + log := newLogger(config) + tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language) if err != nil { return app, err } + app.Common = &common.Common{ + Log: log, + Tr: tr, + UserConfig: userConfig, + Debug: config.GetDebug(), + } + // if we are being called in 'demon' mode, we can just return here app.ClientContext = os.Getenv("LAZYGIT_CLIENT_COMMAND") if app.ClientContext != "" { return app, nil } - app.OSCommand = oscommands.NewOSCommand(app.Log, config) + app.OSCommand = oscommands.NewOSCommand(app.Common, oscommands.GetPlatform(), oscommands.NewNullGuiIO(log)) - app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr) + app.Updater, err = updates.NewUpdater(app.Common, config, app.OSCommand) if err != nil { return app, err } @@ -127,18 +133,9 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) { return app, err } - app.GitCommand, err = commands.NewGitCommand( - app.Log, - app.OSCommand, - app.Tr, - app.Config, - git_config.NewStdCachedGitConfig(app.Log), - ) - if err != nil { - return app, err - } + gitConfig := git_config.NewStdCachedGitConfig(app.Log) - app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater, filterPath, showRecentRepos) + app.Gui, err = gui.NewGui(app.Common, config, gitConfig, app.Updater, filterPath, showRecentRepos) if err != nil { return app, err } @@ -168,7 +165,7 @@ func (app *App) validateGhVersion() error { } func (app *App) validateGitVersion() error { - output, err := app.OSCommand.RunCommandWithOutput("git --version") + output, err := app.OSCommand.Cmd.New("git --version").RunWithOutput() // if we get an error anywhere here we'll show the same status minVersionError := errors.New(app.Tr.MinGitVersionError) if err != nil { @@ -232,7 +229,7 @@ func (app *App) setupRepo() (bool, error) { } if env.GetGitDirEnv() != "" { - // we've been given the git dir directly. We'll verify this dir when initializing our GitCommand object + // we've been given the git dir directly. We'll verify this dir when initializing our Git object return false, nil } @@ -248,7 +245,7 @@ func (app *App) setupRepo() (bool, error) { } shouldInitRepo := true - notARepository := app.Config.GetUserConfig().NotARepository + notARepository := app.UserConfig.NotARepository if notARepository == "prompt" { // Offer to initialize a new repository in current directory. fmt.Print(app.Tr.CreateRepo) @@ -276,7 +273,7 @@ func (app *App) setupRepo() (bool, error) { os.Exit(1) } - if err := app.OSCommand.RunCommand("git init"); err != nil { + if err := app.OSCommand.Cmd.New("git init").Run(); err != nil { return false, err } } diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 2008e0a80..6415c847f 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -36,6 +36,7 @@ func TestIsGitVersionValid(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.versionStr, func(t *testing.T) { result := isGitVersionValid(s.versionStr) assert.Equal(t, result, s.expectedResult) diff --git a/pkg/app/logging.go b/pkg/app/logging.go index 40d58ad62..7df0bb0c2 100644 --- a/pkg/app/logging.go +++ b/pkg/app/logging.go @@ -4,10 +4,11 @@ package app import ( - "github.com/aybabtme/humanlog" - "github.com/jesseduffield/lazygit/pkg/secureexec" "log" "os" + + "github.com/aybabtme/humanlog" + "github.com/jesseduffield/lazygit/pkg/secureexec" ) func TailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) { diff --git a/pkg/cheatsheet/check.go b/pkg/cheatsheet/check.go new file mode 100644 index 000000000..03f65d910 --- /dev/null +++ b/pkg/cheatsheet/check.go @@ -0,0 +1,79 @@ +package cheatsheet + +import ( + "fmt" + "io/fs" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + + "github.com/pmezard/go-difflib/difflib" +) + +func Check() { + dir := GetDir() + tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet") + err := os.RemoveAll(tmpDir) + if err != nil { + log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err) + } + err = os.Mkdir(tmpDir, 0700) + if err != nil { + log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err) + } + + generateAtDir(tmpDir) + defer os.RemoveAll(tmpDir) + + actualContent := obtainContent(dir) + expectedContent := obtainContent(tmpDir) + + if expectedContent == "" { + log.Fatal("empty expected content") + } + + if actualContent != expectedContent { + err := difflib.WriteUnifiedDiff(os.Stdout, difflib.UnifiedDiff{ + A: difflib.SplitLines(expectedContent), + B: difflib.SplitLines(actualContent), + FromFile: "Expected", + FromDate: "", + ToFile: "Actual", + ToDate: "", + Context: 1, + }) + if err != nil { + log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err) + } + fmt.Printf("\nCheatsheets are out of date. Please run `%s` at the project root and commit the changes\n", CommandToRun()) + os.Exit(1) + } + + fmt.Println("\nCheatsheets are up to date") +} + +func obtainContent(dir string) string { + re := regexp.MustCompile(`Keybindings_\w+\.md$`) + + content := "" + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if re.MatchString(path) { + bytes, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err) + } + content += fmt.Sprintf("\n%s\n\n", filepath.Base(path)) + content += string(bytes) + } + + return nil + }) + + if err != nil { + log.Fatalf("Error occured while checking if cheatsheets are up to date: %v", err) + } + + return content +} diff --git a/scripts/generate_cheatsheet.go b/pkg/cheatsheet/generate.go similarity index 85% rename from scripts/generate_cheatsheet.go rename to pkg/cheatsheet/generate.go index e2200b694..94eea0687 100644 --- a/scripts/generate_cheatsheet.go +++ b/pkg/cheatsheet/generate.go @@ -4,20 +4,21 @@ // The content of this generated file is a keybindings cheatsheet. // // To generate cheatsheet in english run: -// LANG=en go run scripts/generate_cheatsheet.go +// go run scripts/generate_cheatsheet.go -package main +package cheatsheet import ( "fmt" "log" "os" "sort" - "strings" "github.com/jesseduffield/lazygit/pkg/app" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui" + "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/jesseduffield/lazygit/pkg/integration" ) type bindingSection struct { @@ -25,24 +26,41 @@ type bindingSection struct { bindings []*gui.Binding } -func main() { - langs := []string{"pl", "nl", "en"} - mConfig, _ := config.NewAppConfig("", "", "", "", "", true) +func CommandToRun() string { + return "go run scripts/cheatsheet/main.go generate" +} - for _, lang := range langs { +func GetDir() string { + return integration.GetRootDirectory() + "/docs/keybindings" +} + +func generateAtDir(cheatsheetDir string) { + os.Setenv("LANG", "en") + + translationSetsByLang := i18n.GetTranslationSets() + mConfig := config.NewDummyAppConfig() + + for lang := range translationSetsByLang { os.Setenv("LC_ALL", lang) mApp, _ := app.NewApp(mConfig, "") - file, err := os.Create(getProjectRoot() + "/docs/keybindings/Keybindings_" + lang + ".md") + path := cheatsheetDir + "/Keybindings_" + lang + ".md" + file, err := os.Create(path) if err != nil { panic(err) } bindingSections := getBindingSections(mApp) - content := formatSections(mApp, bindingSections) + 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(GetDir()) +} + func writeString(file *os.File, str string) { _, err := file.WriteString(str) if err != nil { @@ -231,8 +249,8 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi return append(bindingSections, section) } -func formatSections(mApp *app.App, bindingSections []*bindingSection) string { - content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.Keybindings) +func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string { + content := fmt.Sprintf("# Lazygit %s\n", tr.Keybindings) for _, section := range bindingSections { content += formatTitle(section.title) @@ -245,11 +263,3 @@ func formatSections(mApp *app.App, bindingSections []*bindingSection) string { return content } - -func getProjectRoot() string { - dir, err := os.Getwd() - if err != nil { - panic(err) - } - return strings.Split(dir, "lazygit")[0] + "lazygit" -} diff --git a/pkg/commands/branches.go b/pkg/commands/branches.go deleted file mode 100644 index 3f6820c09..000000000 --- a/pkg/commands/branches.go +++ /dev/null @@ -1,161 +0,0 @@ -package commands - -import ( - "fmt" - "regexp" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// NewBranch create new branch -func (c *GitCommand) NewBranch(name string, base string) error { - return c.RunCommand("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base)) -} - -// CurrentBranchName get the current branch name and displayname. -// the first returned string is the name and the second is the displayname -// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)' -func (c *GitCommand) CurrentBranchName() (string, string, error) { - branchName, err := c.RunCommandWithOutput("git symbolic-ref --short HEAD") - if err == nil && branchName != "HEAD\n" { - trimmedBranchName := strings.TrimSpace(branchName) - return trimmedBranchName, trimmedBranchName, nil - } - output, err := c.RunCommandWithOutput("git branch --contains") - if err != nil { - return "", "", err - } - for _, line := range utils.SplitLines(output) { - re := regexp.MustCompile(CurrentBranchNameRegex) - match := re.FindStringSubmatch(line) - if len(match) > 0 { - branchName = match[1] - displayBranchName := match[0][2:] - return branchName, displayBranchName, nil - } - } - return "HEAD", "HEAD", nil -} - -// DeleteBranch delete branch -func (c *GitCommand) DeleteBranch(branch string, force bool) error { - command := "git branch -d" - - if force { - command = "git branch -D" - } - - return c.OSCommand.RunCommand("%s %s", command, c.OSCommand.Quote(branch)) -} - -// 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 (c *GitCommand) Checkout(branch string, options CheckoutOptions) error { - forceArg := "" - if options.Force { - forceArg = " --force" - } - return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch)), oscommands.RunCommandOptions{EnvVars: options.EnvVars}) -} - -// GetBranchGraph 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 (c *GitCommand) GetBranchGraph(branchName string) (string, error) { - cmdStr := c.GetBranchGraphCmdStr(branchName) - return c.OSCommand.RunCommandWithOutput(cmdStr) -} - -func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) { - output, err := c.RunCommandWithOutput("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName)) - return strings.TrimSpace(output), err -} - -func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string { - branchLogCmdTemplate := c.Config.GetUserConfig().Git.BranchLogCmd - templateValues := map[string]string{ - "branchName": c.OSCommand.Quote(branchName), - } - return utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues) -} - -func (c *GitCommand) SetUpstreamBranch(upstream string) error { - return c.RunCommand("git branch -u %s", c.OSCommand.Quote(upstream)) -} - -func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error { - return c.RunCommand("git branch --set-upstream-to=%s/%s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName)) -} - -func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) { - return c.GetCommitDifferences("HEAD", "HEAD@{u}") -} - -func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) { - return c.GetCommitDifferences(branchName, branchName+"@{u}") -} - -// GetCommitDifferences checks how many pushables/pullables there are for the -// current branch -func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) { - command := "git rev-list %s..%s --count" - pushableCount, err := c.OSCommand.RunCommandWithOutput(command, to, from) - if err != nil { - return "?", "?" - } - pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to) - if err != nil { - return "?", "?" - } - return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) -} - -type MergeOpts struct { - FastForwardOnly bool -} - -// Merge merge -func (c *GitCommand) Merge(branchName string, opts MergeOpts) error { - mergeArgs := c.Config.GetUserConfig().Git.Merging.Args - - command := fmt.Sprintf("git merge --no-edit %s %s", mergeArgs, c.OSCommand.Quote(branchName)) - if opts.FastForwardOnly { - command = fmt.Sprintf("%s --ff-only", command) - } - - return c.OSCommand.RunCommand(command) -} - -// AbortMerge abort merge -func (c *GitCommand) AbortMerge() error { - return c.RunCommand("git merge --abort") -} - -func (c *GitCommand) IsHeadDetached() bool { - err := c.RunCommand("git symbolic-ref -q HEAD") - return err != nil -} - -// ResetHardHead runs `git reset --hard` -func (c *GitCommand) ResetHard(ref string) error { - return c.RunCommand("git reset --hard " + c.OSCommand.Quote(ref)) -} - -// ResetSoft runs `git reset --soft HEAD` -func (c *GitCommand) ResetSoft(ref string) error { - return c.RunCommand("git reset --soft " + c.OSCommand.Quote(ref)) -} - -func (c *GitCommand) ResetMixed(ref string) error { - return c.RunCommand("git reset --mixed " + c.OSCommand.Quote(ref)) -} - -func (c *GitCommand) RenameBranch(oldName string, newName string) error { - return c.RunCommand("git branch --move %s %s", c.OSCommand.Quote(oldName), c.OSCommand.Quote(newName)) -} diff --git a/pkg/commands/branches_test.go b/pkg/commands/branches_test.go deleted file mode 100644 index 85c477523..000000000 --- a/pkg/commands/branches_test.go +++ /dev/null @@ -1,338 +0,0 @@ -package commands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/jesseduffield/lazygit/pkg/test" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandGetCommitDifferences is a function. -func TestGitCommandGetCommitDifferences(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(string, string) - } - - scenarios := []scenario{ - { - "Can't retrieve pushable count", - func(string, ...string) *exec.Cmd { - return secureexec.Command("test") - }, - func(pushableCount string, pullableCount string) { - assert.EqualValues(t, "?", pushableCount) - assert.EqualValues(t, "?", pullableCount) - }, - }, - { - "Can't retrieve pullable count", - func(cmd string, args ...string) *exec.Cmd { - if args[1] == "HEAD..@{u}" { - return secureexec.Command("test") - } - - return secureexec.Command("echo") - }, - func(pushableCount string, pullableCount string) { - assert.EqualValues(t, "?", pushableCount) - assert.EqualValues(t, "?", pullableCount) - }, - }, - { - "Retrieve pullable and pushable count", - func(cmd string, args ...string) *exec.Cmd { - if args[1] == "HEAD..@{u}" { - return secureexec.Command("echo", "10") - } - - return secureexec.Command("echo", "11") - }, - func(pushableCount string, pullableCount string) { - assert.EqualValues(t, "11", pushableCount) - assert.EqualValues(t, "10", pullableCount) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.GetCommitDifferences("HEAD", "@{u}")) - }) - } -} - -// TestGitCommandNewBranch is a function. -func TestGitCommandNewBranch(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"checkout", "-b", "test", "master"}, args) - - return secureexec.Command("echo") - } - - assert.NoError(t, gitCmd.NewBranch("test", "master")) -} - -// TestGitCommandDeleteBranch is a function. -func TestGitCommandDeleteBranch(t *testing.T) { - type scenario struct { - testName string - branch string - force bool - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "Delete a branch", - "test", - false, - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"branch", "-d", "test"}, args) - - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Force delete a branch", - "test", - true, - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"branch", "-D", "test"}, args) - - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.DeleteBranch(s.branch, s.force)) - }) - } -} - -// TestGitCommandMerge is a function. -func TestGitCommandMerge(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"merge", "--no-edit", "test"}, args) - - return secureexec.Command("echo") - } - - assert.NoError(t, gitCmd.Merge("test", MergeOpts{})) -} - -// TestGitCommandCheckout is a function. -func TestGitCommandCheckout(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(error) - force bool - } - - scenarios := []scenario{ - { - "Checkout", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"checkout", "test"}, args) - - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - false, - }, - { - "Checkout forced", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"checkout", "--force", "test"}, args) - - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - true, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.Checkout("test", CheckoutOptions{Force: s.force})) - }) - } -} - -// TestGitCommandGetBranchGraph is a function. -func TestGitCommandGetBranchGraph(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--"}, args) - return secureexec.Command("echo") - } - _, err := gitCmd.GetBranchGraph("test") - assert.NoError(t, err) -} - -func TestGitCommandGetAllBranchGraph(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"log", "--graph", "--all", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium"}, args) - return secureexec.Command("echo") - } - cmdStr := gitCmd.Config.GetUserConfig().Git.AllBranchesLogCmd - _, err := gitCmd.OSCommand.RunCommandWithOutput(cmdStr) - assert.NoError(t, err) -} - -// TestGitCommandCurrentBranchName is a function. -func TestGitCommandCurrentBranchName(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(string, string, error) - } - - scenarios := []scenario{ - { - "says we are on the master branch if we are", - func(cmd string, args ...string) *exec.Cmd { - assert.Equal(t, "git", cmd) - return secureexec.Command("echo", "master") - }, - func(name string, displayname string, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "master", name) - assert.EqualValues(t, "master", displayname) - }, - }, - { - "falls back to git `git branch --contains` if symbolic-ref fails", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return secureexec.Command("test") - case "branch": - assert.EqualValues(t, []string{"branch", "--contains"}, args) - return secureexec.Command("echo", "* master") - } - - return nil - }, - func(name string, displayname string, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "master", name) - assert.EqualValues(t, "master", displayname) - }, - }, - { - "handles a detached head", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return secureexec.Command("test") - case "branch": - assert.EqualValues(t, []string{"branch", "--contains"}, args) - return secureexec.Command("echo", "* (HEAD detached at 123abcd)") - } - - return nil - }, - func(name string, displayname string, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "123abcd", name) - assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname) - }, - }, - { - "bubbles up error if there is one", - func(cmd string, args ...string) *exec.Cmd { - assert.Equal(t, "git", cmd) - return secureexec.Command("test") - }, - func(name string, displayname string, err error) { - assert.Error(t, err) - assert.EqualValues(t, "", name) - assert.EqualValues(t, "", displayname) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.CurrentBranchName()) - }) - } -} - -// TestGitCommandResetHard is a function. -func TestGitCommandResetHard(t *testing.T) { - type scenario struct { - testName string - ref string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "valid case", - "HEAD", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: `git reset --hard HEAD`, - Replace: "echo", - }, - }), - func(err error) { - assert.NoError(t, err) - }, - }, - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.ResetHard(s.ref)) - }) - } -} diff --git a/pkg/commands/commits.go b/pkg/commands/commits.go deleted file mode 100644 index 23a2f99a5..000000000 --- a/pkg/commands/commits.go +++ /dev/null @@ -1,98 +0,0 @@ -package commands - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -// RenameCommit renames the topmost commit with the given name -func (c *GitCommand) RenameCommit(name string) error { - return c.RunCommand("git commit --allow-empty --amend --only -m %s", c.OSCommand.Quote(name)) -} - -// ResetToCommit reset to commit -func (c *GitCommand) ResetToCommit(sha string, strength string, options oscommands.RunCommandOptions) error { - return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options) -} - -func (c *GitCommand) CommitCmdStr(message string, flags string) string { - splitMessage := strings.Split(message, "\n") - lineArgs := "" - for _, line := range splitMessage { - lineArgs += fmt.Sprintf(" -m %s", c.OSCommand.Quote(line)) - } - - flagsStr := "" - if flags != "" { - flagsStr = fmt.Sprintf(" %s", flags) - } - - return fmt.Sprintf("git commit%s%s", flagsStr, lineArgs) -} - -// Get the subject of the HEAD commit -func (c *GitCommand) GetHeadCommitMessage() (string, error) { - cmdStr := "git log -1 --pretty=%s" - message, err := c.OSCommand.RunCommandWithOutput(cmdStr) - return strings.TrimSpace(message), err -} - -func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) { - cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha - messageWithHeader, err := c.OSCommand.RunCommandWithOutput(cmdStr) - message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "\n") - return strings.TrimSpace(message), err -} - -func (c *GitCommand) GetCommitMessageFirstLine(sha string) (string, error) { - return c.RunCommandWithOutput("git show --no-patch --pretty=format:%%s %s", sha) -} - -// AmendHead amends HEAD with whatever is staged in your working tree -func (c *GitCommand) AmendHead() error { - return c.OSCommand.RunCommand(c.AmendHeadCmdStr()) -} - -func (c *GitCommand) AmendHeadCmdStr() string { - return "git commit --amend --no-edit --allow-empty" -} - -func (c *GitCommand) ShowCmdStr(sha string, filterPath string) string { - filterPathArg := "" - if filterPath != "" { - filterPathArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(filterPath)) - } - return fmt.Sprintf("git show --submodule --color=%s --no-renames --stat -p %s %s", c.colorArg(), sha, filterPathArg) -} - -// Revert reverts the selected commit by sha -func (c *GitCommand) Revert(sha string) error { - return c.RunCommand("git revert %s", sha) -} - -func (c *GitCommand) RevertMerge(sha string, parentNumber int) error { - return c.RunCommand("git revert %s -m %d", sha, parentNumber) -} - -// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD -func (c *GitCommand) CherryPickCommits(commits []*models.Commit) error { - todo := "" - for _, commit := range commits { - todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo - } - - cmd, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false) - if err != nil { - return err - } - - return c.OSCommand.RunPreparedCommand(cmd) -} - -// CreateFixupCommit creates a commit that fixes up a previous commit -func (c *GitCommand) CreateFixupCommit(sha string) error { - return c.RunCommand("git commit --fixup=%s", sha) -} diff --git a/pkg/commands/commits_test.go b/pkg/commands/commits_test.go deleted file mode 100644 index eed6f2207..000000000 --- a/pkg/commands/commits_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package commands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/jesseduffield/lazygit/pkg/test" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandRenameCommit is a function. -func TestGitCommandRenameCommit(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, args) - - return secureexec.Command("echo") - } - - assert.NoError(t, gitCmd.RenameCommit("test")) -} - -// TestGitCommandResetToCommit is a function. -func TestGitCommandResetToCommit(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"reset", "--hard", "78976bc"}, args) - - return secureexec.Command("echo") - } - - assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", oscommands.RunCommandOptions{})) -} - -// TestGitCommandCommitStr is a function. -func TestGitCommandCommitStr(t *testing.T) { - gitCmd := NewDummyGitCommand() - - type scenario struct { - testName string - message string - flags string - expected string - } - - scenarios := []scenario{ - { - testName: "Commit", - message: "test", - flags: "", - expected: "git commit -m " + gitCmd.OSCommand.Quote("test"), - }, - { - testName: "Commit with --no-verify flag", - message: "test", - flags: "--no-verify", - expected: "git commit --no-verify -m " + gitCmd.OSCommand.Quote("test"), - }, - { - testName: "Commit with multiline message", - message: "line1\nline2", - flags: "", - expected: "git commit -m " + gitCmd.OSCommand.Quote("line1") + " -m " + gitCmd.OSCommand.Quote("line2"), - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - cmdStr := gitCmd.CommitCmdStr(s.message, s.flags) - assert.Equal(t, s.expected, cmdStr) - }) - } -} - -// TestGitCommandCreateFixupCommit is a function. -func TestGitCommandCreateFixupCommit(t *testing.T) { - type scenario struct { - testName string - sha string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "valid case", - "12345", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: `git commit --fixup=12345`, - Replace: "echo", - }, - }), - func(err error) { - assert.NoError(t, err) - }, - }, - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.CreateFixupCommit(s.sha)) - }) - } -} diff --git a/pkg/commands/config.go b/pkg/commands/config.go deleted file mode 100644 index 922e4f580..000000000 --- a/pkg/commands/config.go +++ /dev/null @@ -1,50 +0,0 @@ -package commands - -import ( - "os" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func (c *GitCommand) ConfiguredPager() string { - if os.Getenv("GIT_PAGER") != "" { - return os.Getenv("GIT_PAGER") - } - if os.Getenv("PAGER") != "" { - return os.Getenv("PAGER") - } - output := c.GitConfig.Get("core.pager") - return strings.Split(output, "\n")[0] -} - -func (c *GitCommand) GetPager(width int) string { - useConfig := c.Config.GetUserConfig().Git.Paging.UseConfig - if useConfig { - pager := c.ConfiguredPager() - return strings.Split(pager, "| less")[0] - } - - templateValues := map[string]string{ - "columnWidth": strconv.Itoa(width/2 - 6), - } - - pagerTemplate := c.Config.GetUserConfig().Git.Paging.Pager - return utils.ResolvePlaceholderString(pagerTemplate, templateValues) -} - -func (c *GitCommand) colorArg() string { - return c.Config.GetUserConfig().Git.Paging.ColorArg -} - -// UsingGpg tells us whether the user has gpg enabled so that we can know -// whether we need to run a subprocess to allow them to enter their password -func (c *GitCommand) UsingGpg() bool { - overrideGpg := c.Config.GetUserConfig().Git.OverrideGpg - if overrideGpg { - return false - } - - return c.GitConfig.GetBool("commit.gpgsign") -} diff --git a/pkg/commands/dummies.go b/pkg/commands/dummies.go deleted file mode 100644 index ba052fe9b..000000000 --- a/pkg/commands/dummies.go +++ /dev/null @@ -1,30 +0,0 @@ -package commands - -import ( - "io" - "io/ioutil" - - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// NewDummyGitCommand creates a new dummy GitCommand for testing -func NewDummyGitCommand() *GitCommand { - return NewDummyGitCommandWithOSCommand(oscommands.NewDummyOSCommand()) -} - -// NewDummyGitCommandWithOSCommand creates a new dummy GitCommand for testing -func NewDummyGitCommandWithOSCommand(osCommand *oscommands.OSCommand) *GitCommand { - newAppConfig := config.NewDummyAppConfig() - return &GitCommand{ - Log: utils.NewDummyLog(), - OSCommand: osCommand, - Tr: i18n.NewTranslationSet(utils.NewDummyLog(), newAppConfig.GetUserConfig().Gui.Language), - Config: newAppConfig, - GitConfig: git_config.NewFakeGitConfig(map[string]string{}), - GetCmdWriter: func() io.Writer { return ioutil.Discard }, - } -} diff --git a/pkg/commands/files.go b/pkg/commands/files.go deleted file mode 100644 index 07c4c4d28..000000000 --- a/pkg/commands/files.go +++ /dev/null @@ -1,363 +0,0 @@ -package commands - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "time" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// CatFile obtains the content of a file -func (c *GitCommand) CatFile(fileName string) (string, error) { - buf, err := ioutil.ReadFile(fileName) - if err != nil { - return "", nil - } - return string(buf), nil -} - -func (c *GitCommand) OpenMergeToolCmd() string { - return "git mergetool" -} - -func (c *GitCommand) OpenMergeTool() error { - return c.OSCommand.RunCommand("git mergetool") -} - -// StageFile stages a file -func (c *GitCommand) StageFile(fileName string) error { - return c.RunCommand("git add -- %s", c.OSCommand.Quote(fileName)) -} - -// StageAll stages all files -func (c *GitCommand) StageAll() error { - return c.RunCommand("git add -A") -} - -// UnstageAll unstages all files -func (c *GitCommand) UnstageAll() error { - return c.RunCommand("git reset") -} - -// 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 (c *GitCommand) UnStageFile(fileNames []string, reset bool) error { - command := "git rm --cached --force -- %s" - if reset { - command = "git reset HEAD -- %s" - } - - for _, name := range fileNames { - if err := c.OSCommand.RunCommand(command, c.OSCommand.Quote(name)); err != nil { - return err - } - } - return nil -} - -func (c *GitCommand) 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 := c.GetStatusFiles(GetStatusFileOptions{NoRenames: true}) - var beforeFile *models.File - var afterFile *models.File - for _, f := range filesWithoutRenames { - if f.Name == file.PreviousName { - beforeFile = f - } - - if f.Name == file.Name { - 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 (c *GitCommand) DiscardAllFileChanges(file *models.File) error { - if file.IsRename() { - beforeFile, afterFile, err := c.BeforeAndAfterFileForRename(file) - if err != nil { - return err - } - - if err := c.DiscardAllFileChanges(beforeFile); err != nil { - return err - } - - if err := c.DiscardAllFileChanges(afterFile); err != nil { - return err - } - - return nil - } - - quotedFileName := c.OSCommand.Quote(file.Name) - - if file.ShortStatus == "AA" { - if err := c.RunCommand("git checkout --ours -- %s", quotedFileName); err != nil { - return err - } - if err := c.RunCommand("git add -- %s", quotedFileName); err != nil { - return err - } - return nil - } - - if file.ShortStatus == "DU" { - return c.RunCommand("git rm -- %s", quotedFileName) - } - - // if the file isn't tracked, we assume you want to delete it - if file.HasStagedChanges || file.HasMergeConflicts { - if err := c.RunCommand("git reset -- %s", quotedFileName); err != nil { - return err - } - } - - if file.ShortStatus == "DD" || file.ShortStatus == "AU" { - return nil - } - - if file.Added { - return c.OSCommand.RemoveFile(file.Name) - } - return c.DiscardUnstagedFileChanges(file) -} - -func (c *GitCommand) DiscardAllDirChanges(node *filetree.FileNode) error { - // this could be more efficient but we would need to handle all the edge cases - return node.ForEachFile(c.DiscardAllFileChanges) -} - -func (c *GitCommand) DiscardUnstagedDirChanges(node *filetree.FileNode) error { - if err := c.RemoveUntrackedDirFiles(node); err != nil { - return err - } - - quotedPath := c.OSCommand.Quote(node.GetPath()) - if err := c.RunCommand("git checkout -- %s", quotedPath); err != nil { - return err - } - - return nil -} - -func (c *GitCommand) RemoveUntrackedDirFiles(node *filetree.FileNode) error { - untrackedFilePaths := node.GetPathsMatching( - func(n *filetree.FileNode) bool { return n.File != nil && !n.File.GetIsTracked() }, - ) - - for _, path := range untrackedFilePaths { - err := os.Remove(path) - if err != nil { - return err - } - } - - return nil -} - -// DiscardUnstagedFileChanges directly -func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error { - quotedFileName := c.OSCommand.Quote(file.Name) - return c.RunCommand("git checkout -- %s", quotedFileName) -} - -// Ignore adds a file to the gitignore for the repo -func (c *GitCommand) Ignore(filename string) error { - return c.OSCommand.AppendLineToFile(".gitignore", filename) -} - -// WorktreeFileDiff returns the diff of a file -func (c *GitCommand) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string { - // for now we assume an error means the file was deleted - s, _ := c.OSCommand.RunCommandWithOutput(c.WorktreeFileDiffCmdStr(file, plain, cached, ignoreWhitespace)) - return s -} - -func (c *GitCommand) WorktreeFileDiffCmdStr(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) string { - cachedArg := "" - trackedArg := "--" - colorArg := c.colorArg() - quotedPath := c.OSCommand.Quote(node.GetPath()) - ignoreWhitespaceArg := "" - if cached { - cachedArg = "--cached" - } - if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached { - trackedArg = "--no-index -- /dev/null" - } - if plain { - colorArg = "never" - } - if ignoreWhitespace { - ignoreWhitespaceArg = "--ignore-all-space" - } - - return fmt.Sprintf("git diff --submodule --no-ext-diff --color=%s %s %s %s %s", colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath) -} - -func (c *GitCommand) ApplyPatch(patch string, flags ...string) error { - filepath := filepath.Join(c.Config.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch") - c.Log.Infof("saving temporary patch to %s", filepath) - if err := c.OSCommand.CreateFileWithContent(filepath, patch); err != nil { - return err - } - - flagStr := "" - for _, flag := range flags { - flagStr += " --" + flag - } - - return c.RunCommand("git apply %s %s", flagStr, c.OSCommand.Quote(filepath)) -} - -// 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 (c *GitCommand) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) { - cmdStr := c.ShowFileDiffCmdStr(from, to, reverse, fileName, plain) - return c.OSCommand.RunCommandWithOutput(cmdStr) -} - -func (c *GitCommand) ShowFileDiffCmdStr(from string, to string, reverse bool, fileName string, plain bool) string { - colorArg := c.colorArg() - if plain { - colorArg = "never" - } - - reverseFlag := "" - if reverse { - reverseFlag = " -R " - } - - return fmt.Sprintf("git diff --submodule --no-ext-diff --no-renames --color=%s %s %s %s -- %s", colorArg, from, to, reverseFlag, c.OSCommand.Quote(fileName)) -} - -// CheckoutFile checks out the file for the given commit -func (c *GitCommand) CheckoutFile(commitSha, fileName string) error { - return c.RunCommand("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName)) -} - -// DiscardOldFileChanges discards changes to a file from an old commit -func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, fileName string) error { - if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil { - return err - } - - // check if file exists in previous commit (this command returns an error if the file doesn't exist) - if err := c.RunCommand("git cat-file -e HEAD^:%s", c.OSCommand.Quote(fileName)); err != nil { - if err := c.OSCommand.Remove(fileName); err != nil { - return err - } - if err := c.StageFile(fileName); err != nil { - return err - } - } else if err := c.CheckoutFile("HEAD^", fileName); err != nil { - return err - } - - // amend the commit - err := c.AmendHead() - if err != nil { - return err - } - - // continue - return c.GenericMergeOrRebaseAction("rebase", "continue") -} - -// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .` -func (c *GitCommand) DiscardAnyUnstagedFileChanges() error { - return c.RunCommand("git checkout -- .") -} - -// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked -func (c *GitCommand) RemoveTrackedFiles(name string) error { - return c.RunCommand("git rm -r --cached -- %s", c.OSCommand.Quote(name)) -} - -// RemoveUntrackedFiles runs `git clean -fd` -func (c *GitCommand) RemoveUntrackedFiles() error { - return c.RunCommand("git clean -fd") -} - -// ResetAndClean removes all unstaged changes and removes all untracked files -func (c *GitCommand) ResetAndClean() error { - submoduleConfigs, err := c.GetSubmoduleConfigs() - if err != nil { - return err - } - - if len(submoduleConfigs) > 0 { - if err := c.ResetSubmodules(submoduleConfigs); err != nil { - return err - } - } - - if err := c.ResetHard("HEAD"); err != nil { - return err - } - - return c.RemoveUntrackedFiles() -} - -func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, error) { - editor := c.Config.GetUserConfig().OS.EditCommand - - if editor == "" { - editor = c.GitConfig.Get("core.editor") - } - - if editor == "" { - editor = c.OSCommand.Getenv("GIT_EDITOR") - } - if editor == "" { - editor = c.OSCommand.Getenv("VISUAL") - } - if editor == "" { - editor = c.OSCommand.Getenv("EDITOR") - } - if editor == "" { - if err := c.OSCommand.RunCommand("which vi"); err == nil { - editor = "vi" - } - } - if editor == "" { - return "", errors.New("No editor defined in config file, $GIT_EDITOR, $VISUAL, $EDITOR, or git config") - } - - templateValues := map[string]string{ - "editor": editor, - "filename": c.OSCommand.Quote(filename), - "line": strconv.Itoa(lineNumber), - } - - editCmdTemplate := c.Config.GetUserConfig().OS.EditCommandTemplate - return utils.ResolvePlaceholderString(editCmdTemplate, templateValues), nil -} diff --git a/pkg/commands/files_test.go b/pkg/commands/files_test.go deleted file mode 100644 index f72b47ca0..000000000 --- a/pkg/commands/files_test.go +++ /dev/null @@ -1,878 +0,0 @@ -package commands - -import ( - "fmt" - "io/ioutil" - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/jesseduffield/lazygit/pkg/test" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandStageFile is a function. -func TestGitCommandStageFile(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"add", "--", "test.txt"}, args) - - return secureexec.Command("echo") - } - - assert.NoError(t, gitCmd.StageFile("test.txt")) -} - -// TestGitCommandUnstageFile is a function. -func TestGitCommandUnstageFile(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(error) - reset bool - } - - scenarios := []scenario{ - { - "Remove an untracked file from staging", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"rm", "--cached", "--force", "--", "test.txt"}, args) - - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - false, - }, - { - "Remove a tracked file from staging", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"reset", "HEAD", "--", "test.txt"}, args) - - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - true, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.UnStageFile([]string{"test.txt"}, s.reset)) - }) - } -} - -// TestGitCommandDiscardAllFileChanges is a function. -// 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 TestGitCommandDiscardAllFileChanges(t *testing.T) { - type scenario struct { - testName string - command func() (func(string, ...string) *exec.Cmd, *[][]string) - test func(*[][]string, error) - file *models.File - removeFile func(string) error - } - - scenarios := []scenario{ - { - "An error occurred when resetting", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("test") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.Error(t, err) - assert.Len(t, *cmdsCalled, 1) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"reset", "--", "test"}, - }) - }, - &models.File{ - Name: "test", - HasStagedChanges: true, - }, - func(string) error { - return nil - }, - }, - { - "An error occurred when removing file", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("test") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.Error(t, err) - assert.EqualError(t, err, "an error occurred when removing file") - assert.Len(t, *cmdsCalled, 0) - }, - &models.File{ - Name: "test", - Tracked: false, - Added: true, - }, - func(string) error { - return fmt.Errorf("an error occurred when removing file") - }, - }, - { - "An error occurred with checkout", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("test") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.Error(t, err) - assert.Len(t, *cmdsCalled, 1) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"checkout", "--", "test"}, - }) - }, - &models.File{ - Name: "test", - Tracked: true, - HasStagedChanges: false, - }, - func(string) error { - return nil - }, - }, - { - "Checkout only", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("echo") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.NoError(t, err) - assert.Len(t, *cmdsCalled, 1) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"checkout", "--", "test"}, - }) - }, - &models.File{ - Name: "test", - Tracked: true, - HasStagedChanges: false, - }, - func(string) error { - return nil - }, - }, - { - "Reset and checkout staged changes", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("echo") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.NoError(t, err) - assert.Len(t, *cmdsCalled, 2) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"reset", "--", "test"}, - {"checkout", "--", "test"}, - }) - }, - &models.File{ - Name: "test", - Tracked: true, - HasStagedChanges: true, - }, - func(string) error { - return nil - }, - }, - { - "Reset and checkout merge conflicts", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("echo") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.NoError(t, err) - assert.Len(t, *cmdsCalled, 2) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"reset", "--", "test"}, - {"checkout", "--", "test"}, - }) - }, - &models.File{ - Name: "test", - Tracked: true, - HasMergeConflicts: true, - }, - func(string) error { - return nil - }, - }, - { - "Reset and remove", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("echo") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.NoError(t, err) - assert.Len(t, *cmdsCalled, 1) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"reset", "--", "test"}, - }) - }, - &models.File{ - Name: "test", - Tracked: false, - Added: true, - HasStagedChanges: true, - }, - func(filename string) error { - assert.Equal(t, "test", filename) - return nil - }, - }, - { - "Remove only", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - - return secureexec.Command("echo") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.NoError(t, err) - assert.Len(t, *cmdsCalled, 0) - }, - &models.File{ - Name: "test", - Tracked: false, - Added: true, - HasStagedChanges: false, - }, - func(filename string) error { - assert.Equal(t, "test", filename) - return nil - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - var cmdsCalled *[][]string - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command, cmdsCalled = s.command() - gitCmd.OSCommand.SetRemoveFile(s.removeFile) - s.test(cmdsCalled, gitCmd.DiscardAllFileChanges(s.file)) - }) - } -} - -// TestGitCommandDiff is a function. -func TestGitCommandDiff(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - file *models.File - plain bool - cached bool - ignoreWhitespace bool - } - - scenarios := []scenario{ - { - "Default case", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--", "test.txt"}, args) - - return secureexec.Command("echo") - }, - &models.File{ - Name: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - false, - false, - false, - }, - { - "cached", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--cached", "--", "test.txt"}, args) - - return secureexec.Command("echo") - }, - &models.File{ - Name: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - false, - true, - false, - }, - { - "plain", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=never", "--", "test.txt"}, args) - - return secureexec.Command("echo") - }, - &models.File{ - Name: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - true, - false, - false, - }, - { - "File not tracked and file has no staged changes", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, args) - - return secureexec.Command("echo") - }, - &models.File{ - Name: "test.txt", - HasStagedChanges: false, - Tracked: false, - }, - false, - false, - false, - }, - { - "Default case (ignore whitespace)", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"diff", "--submodule", "--no-ext-diff", "--color=always", "--ignore-all-space", "--", "test.txt"}, args) - - return secureexec.Command("echo") - }, - &models.File{ - Name: "test.txt", - HasStagedChanges: false, - Tracked: true, - }, - false, - false, - true, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - gitCmd.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace) - }) - } -} - -// TestGitCommandCheckoutFile is a function. -func TestGitCommandCheckoutFile(t *testing.T) { - type scenario struct { - testName string - commitSha string - fileName string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "typical case", - "11af912", - "test999.txt", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: "git checkout 11af912 -- test999.txt", - Replace: "echo", - }, - }), - func(err error) { - assert.NoError(t, err) - }, - }, - { - "returns error if there is one", - "11af912", - "test999.txt", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: "git checkout 11af912 -- test999.txt", - Replace: "test", - }, - }), - func(err error) { - assert.Error(t, err) - }, - }, - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.CheckoutFile(s.commitSha, s.fileName)) - }) - } -} - -func TestGitCommandApplyPatch(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "valid case", - func(cmd string, args ...string) *exec.Cmd { - assert.Equal(t, "git", cmd) - assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2]) - filename := args[2] - content, err := ioutil.ReadFile(filename) - assert.NoError(t, err) - - assert.Equal(t, "test", string(content)) - - return secureexec.Command("echo", "done") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "command returns error", - func(cmd string, args ...string) *exec.Cmd { - assert.Equal(t, "git", cmd) - assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2]) - filename := args[2] - // TODO: Ideally we want to mock out OSCommand here so that we're not - // double handling testing it's CreateTempFile functionality, - // but it is going to take a bit of work to make a proper mock for it - // so I'm leaving it for another PR - content, err := ioutil.ReadFile(filename) - assert.NoError(t, err) - - assert.Equal(t, "test", string(content)) - - return secureexec.Command("test") - }, - func(err error) { - assert.Error(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.ApplyPatch("test", "cached")) - }) - } -} - -func TestGitCommandDiscardOldFileChanges(t *testing.T) { - type scenario struct { - testName string - gitConfigMockResponses map[string]string - commits []*models.Commit - commitIndex int - fileName string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "returns error when index outside of range of commits", - nil, - []*models.Commit{}, - 0, - "test999.txt", - nil, - func(err error) { - assert.Error(t, err) - }, - }, - { - "returns error when using gpg", - map[string]string{"commit.gpgsign": "true"}, - []*models.Commit{{Name: "commit", Sha: "123456"}}, - 0, - "test999.txt", - nil, - func(err error) { - assert.Error(t, err) - }, - }, - { - "checks out file if it already existed", - nil, - []*models.Commit{ - {Name: "commit", Sha: "123456"}, - {Name: "commit2", Sha: "abcdef"}, - }, - 0, - "test999.txt", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: "git rebase --interactive --autostash --keep-empty abcdef", - Replace: "echo", - }, - { - Expect: "git cat-file -e HEAD^:test999.txt", - Replace: "echo", - }, - { - Expect: "git checkout HEAD^ -- test999.txt", - Replace: "echo", - }, - { - Expect: "git commit --amend --no-edit --allow-empty", - Replace: "echo", - }, - { - Expect: "git rebase --continue", - Replace: "echo", - }, - }), - 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 - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - gitCmd.GitConfig = git_config.NewFakeGitConfig(s.gitConfigMockResponses) - s.test(gitCmd.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName)) - }) - } -} - -// TestGitCommandDiscardUnstagedFileChanges is a function. -func TestGitCommandDiscardUnstagedFileChanges(t *testing.T) { - type scenario struct { - testName string - file *models.File - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "valid case", - &models.File{Name: "test.txt"}, - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: `git checkout -- "test.txt"`, - Replace: "echo", - }, - }), - func(err error) { - assert.NoError(t, err) - }, - }, - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.DiscardUnstagedFileChanges(s.file)) - }) - } -} - -// TestGitCommandDiscardAnyUnstagedFileChanges is a function. -func TestGitCommandDiscardAnyUnstagedFileChanges(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "valid case", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: `git checkout -- .`, - Replace: "echo", - }, - }), - func(err error) { - assert.NoError(t, err) - }, - }, - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.DiscardAnyUnstagedFileChanges()) - }) - } -} - -// TestGitCommandRemoveUntrackedFiles is a function. -func TestGitCommandRemoveUntrackedFiles(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "valid case", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: `git clean -fd`, - Replace: "echo", - }, - }), - func(err error) { - assert.NoError(t, err) - }, - }, - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.RemoveUntrackedFiles()) - }) - } -} - -// TestEditFileCmdStr is a function. -func TestEditFileCmdStr(t *testing.T) { - gitCmd := NewDummyGitCommand() - - type scenario struct { - filename string - configEditCommand string - configEditCommandTemplate string - command func(string, ...string) *exec.Cmd - getenv func(string) string - gitConfigMockResponses map[string]string - test func(string, error) - } - - scenarios := []scenario{ - { - "test", - "", - "{{editor}} {{filename}}", - func(name string, arg ...string) *exec.Cmd { - return secureexec.Command("exit", "1") - }, - func(env string) string { - return "" - }, - nil, - func(cmdStr string, err error) { - assert.EqualError(t, err, "No editor defined in config file, $GIT_EDITOR, $VISUAL, $EDITOR, or git config") - }, - }, - { - "test", - "nano", - "{{editor}} {{filename}}", - func(name string, args ...string) *exec.Cmd { - assert.Equal(t, "which", name) - return secureexec.Command("echo") - }, - func(env string) string { - return "" - }, - nil, - func(cmdStr string, err error) { - assert.NoError(t, err) - assert.Equal(t, "nano "+gitCmd.OSCommand.Quote("test"), cmdStr) - }, - }, - { - "test", - "", - "{{editor}} {{filename}}", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "which", name) - return secureexec.Command("exit", "1") - }, - func(env string) string { - return "" - }, - map[string]string{"core.editor": "nano"}, - func(cmdStr string, err error) { - assert.NoError(t, err) - assert.Equal(t, "nano "+gitCmd.OSCommand.Quote("test"), cmdStr) - }, - }, - { - "test", - "", - "{{editor}} {{filename}}", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "which", name) - return secureexec.Command("exit", "1") - }, - func(env string) string { - if env == "VISUAL" { - return "nano" - } - - return "" - }, - nil, - func(cmdStr string, err error) { - assert.NoError(t, err) - }, - }, - { - "test", - "", - "{{editor}} {{filename}}", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "which", name) - return secureexec.Command("exit", "1") - }, - func(env string) string { - if env == "EDITOR" { - return "emacs" - } - - return "" - }, - nil, - func(cmdStr string, err error) { - assert.NoError(t, err) - assert.Equal(t, "emacs "+gitCmd.OSCommand.Quote("test"), cmdStr) - }, - }, - { - "test", - "", - "{{editor}} {{filename}}", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "which", name) - return secureexec.Command("echo") - }, - func(env string) string { - return "" - }, - nil, - func(cmdStr string, err error) { - assert.NoError(t, err) - assert.Equal(t, "vi "+gitCmd.OSCommand.Quote("test"), cmdStr) - }, - }, - { - "file/with space", - "", - "{{editor}} {{filename}}", - func(name string, args ...string) *exec.Cmd { - assert.Equal(t, "which", name) - return secureexec.Command("echo") - }, - func(env string) string { - return "" - }, - nil, - func(cmdStr string, err error) { - assert.NoError(t, err) - assert.Equal(t, "vi "+gitCmd.OSCommand.Quote("file/with space"), cmdStr) - }, - }, - { - "open file/at line", - "vim", - "{{editor}} +{{line}} {{filename}}", - func(name string, args ...string) *exec.Cmd { - assert.Equal(t, "which", name) - return secureexec.Command("echo") - }, - func(env string) string { - return "" - }, - nil, - func(cmdStr string, err error) { - assert.NoError(t, err) - assert.Equal(t, "vim +1 "+gitCmd.OSCommand.Quote("open file/at line"), cmdStr) - }, - }, - } - - for _, s := range scenarios { - gitCmd.Config.GetUserConfig().OS.EditCommand = s.configEditCommand - gitCmd.Config.GetUserConfig().OS.EditCommandTemplate = s.configEditCommandTemplate - gitCmd.OSCommand.Command = s.command - gitCmd.OSCommand.Getenv = s.getenv - gitCmd.GitConfig = git_config.NewFakeGitConfig(s.gitConfigMockResponses) - s.test(gitCmd.EditFileCmdStr(s.filename, 1)) - } -} diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 8750ecf51..f6812e254 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -1,71 +1,68 @@ package commands import ( - "io" "io/ioutil" "os" "path/filepath" "strings" - "time" "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/loaders" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/env" - "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sirupsen/logrus" ) -// this takes something like: -// * (HEAD detached at 264fc6f5) -// remotes -// and returns '264fc6f5' as the second match -const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$` - // GitCommand is our main git interface type GitCommand struct { - Log *logrus.Entry - OSCommand *oscommands.OSCommand - Repo *gogit.Repository - Tr *i18n.TranslationSet - Config config.AppConfigurer - DotGitDir string - onSuccessfulContinue func() error - PatchManager *patch.PatchManager - GitConfig git_config.IGitConfig + Branch *git_commands.BranchCommands + Commit *git_commands.CommitCommands + Config *git_commands.ConfigCommands + Custom *git_commands.CustomCommands + 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 - // Push to current determines whether the user has configured to push to the remote branch of the same name as the current or not - PushToCurrent bool - - // this is just a view that we write to when running certain commands. - // Coincidentally at the moment it's the same view that OnRunCommand logs to - // but that need not always be the case. - GetCmdWriter func() io.Writer + Loaders Loaders +} + +type Loaders struct { + Branches *loaders.BranchLoader + CommitFiles *loaders.CommitFileLoader + Commits *loaders.CommitLoader + Files *loaders.FileLoader + ReflogCommits *loaders.ReflogCommitLoader + Remotes *loaders.RemoteLoader + Stash *loaders.StashLoader + Tags *loaders.TagLoader } -// NewGitCommand it runs git commands func NewGitCommand( - log *logrus.Entry, + cmn *common.Common, osCommand *oscommands.OSCommand, - tr *i18n.TranslationSet, - config config.AppConfigurer, gitConfig git_config.IGitConfig, ) (*GitCommand, error) { - var repo *gogit.Repository - - pushToCurrent := gitConfig.Get("push.default") == "current" - if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil { return nil, err } - var err error - if repo, err = setupRepository(gogit.PlainOpen, tr.GitconfigParseErr); err != nil { + repo, err := setupRepository(gogit.PlainOpen, cmn.Tr.GitconfigParseErr) + if err != nil { return nil, err } @@ -74,42 +71,79 @@ func NewGitCommand( return nil, err } - gitCommand := &GitCommand{ - Log: log, - OSCommand: osCommand, - Tr: tr, - Repo: repo, - Config: config, - DotGitDir: dotGitDir, - PushToCurrent: pushToCurrent, - GitConfig: gitConfig, - GetCmdWriter: func() io.Writer { return ioutil.Discard }, - } - - gitCommand.PatchManager = patch.NewPatchManager(log, gitCommand.ApplyPatch, gitCommand.ShowFileDiff) - - return gitCommand, nil + return NewGitCommandAux( + cmn, + osCommand, + gitConfig, + dotGitDir, + repo, + ), nil } -func (c *GitCommand) WithSpan(span string) *GitCommand { - // sometimes .WithSpan(span) will be called where span actually is empty, in - // which case we don't need to log anything so we can just return early here - // with the original struct - if span == "" { - return c +func NewGitCommandAux( + cmn *common.Common, + osCommand *oscommands.OSCommand, + gitConfig git_config.IGitConfig, + dotGitDir string, + repo *gogit.Repository, +) *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, cmd, osCommand, dotGitDir, repo, configCommands) + + statusCommands := git_commands.NewStatusCommands(gitCommon) + fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands) + 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) + 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) + // TODO: have patch manager take workingTreeCommands in its entirety + patchManager := patch.NewPatchManager(cmn.Log, workingTreeCommands.ApplyPatch, workingTreeCommands.ShowFileDiff) + patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchManager) + bisectCommands := git_commands.NewBisectCommands(gitCommon) + + return &GitCommand{ + Branch: branchCommands, + Commit: commitCommands, + Config: configCommands, + Custom: customCommands, + File: fileCommands, + Flow: flowCommands, + Patch: patchCommands, + Rebase: rebaseCommands, + Remote: remoteCommands, + Stash: stashCommands, + Status: statusCommands, + Submodule: submoduleCommands, + Sync: syncCommands, + Tag: tagCommands, + Bisect: bisectCommands, + WorkingTree: workingTreeCommands, + Loaders: Loaders{ + Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName, configCommands), + CommitFiles: loaders.NewCommitFileLoader(cmn, cmd), + Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode), + Files: fileLoader, + ReflogCommits: loaders.NewReflogCommitLoader(cmn, cmd), + Remotes: loaders.NewRemoteLoader(cmn, cmd, repo.Remotes), + Stash: loaders.NewStashLoader(cmn, cmd), + Tags: loaders.NewTagLoader(cmn, cmd), + }, } - - newGitCommand := &GitCommand{} - *newGitCommand = *c - newGitCommand.OSCommand = c.OSCommand.WithSpan(span) - - // NOTE: unlike the other things here which create shallow clones, this will - // actually update the PatchManager on the original struct to have the new span. - // This means each time we call ApplyPatch in PatchManager, we need to ensure - // we've called .WithSpan() ahead of time with the new span value - newGitCommand.PatchManager.ApplyPatch = newGitCommand.ApplyPatch - - return newGitCommand } func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error { @@ -223,38 +257,5 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam } func VerifyInGitRepo(osCommand *oscommands.OSCommand) error { - return osCommand.RunCommand("git rev-parse --git-dir") -} - -func (c *GitCommand) RunCommand(formatString string, formatArgs ...interface{}) error { - _, err := c.RunCommandWithOutput(formatString, formatArgs...) - return err -} - -func (c *GitCommand) RunCommandWithOutput(formatString string, formatArgs ...interface{}) (string, error) { - // TODO: have this retry logic in other places we run the command - waitTime := 50 * time.Millisecond - retryCount := 5 - attempt := 0 - - for { - output, err := c.OSCommand.RunCommandWithOutput(formatString, formatArgs...) - if err != nil { - // if we have an error based on the index lock, we should wait a bit and then retry - if strings.Contains(output, ".git/index.lock") { - c.Log.Error(output) - c.Log.Info("index.lock prevented command from running. Retrying command after a small wait") - attempt++ - time.Sleep(waitTime) - if attempt < retryCount { - continue - } - } - } - return output, err - } -} - -func (c *GitCommand) NewCmdObjFromStr(cmdStr string) oscommands.ICmdObj { - return c.OSCommand.NewCmdObjFromStr(cmdStr).AddEnvVars("GIT_OPTIONAL_LOCKS=0") + return osCommand.Cmd.New("git rev-parse --git-dir").DontLog().Run() } diff --git a/pkg/commands/git_cmd_obj_builder.go b/pkg/commands/git_cmd_obj_builder.go new file mode 100644 index 000000000..487dd2303 --- /dev/null +++ b/pkg/commands/git_cmd_obj_builder.go @@ -0,0 +1,47 @@ +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(cmdStr string) oscommands.ICmdObj { + return self.innerBuilder.New(cmdStr).AddEnvVars(defaultEnvVar) +} + +func (self *gitCmdObjBuilder) NewFromArgs(args []string) oscommands.ICmdObj { + return self.innerBuilder.NewFromArgs(args).AddEnvVars(defaultEnvVar) +} + +func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj { + return self.innerBuilder.NewShell(cmdStr).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 new file mode 100644 index 000000000..c57c2d2be --- /dev/null +++ b/pkg/commands/git_cmd_obj_runner.go @@ -0,0 +1,26 @@ +package commands + +import ( + "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 + +type gitCmdObjRunner struct { + log *logrus.Entry + innerRunner oscommands.ICmdObjRunner +} + +func (self *gitCmdObjRunner) Run(cmdObj oscommands.ICmdObj) error { + _, err := self.RunWithOutput(cmdObj) + return err +} + +func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, error) { + return self.innerRunner.RunWithOutput(cmdObj) +} + +func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj oscommands.ICmdObj, 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 new file mode 100644 index 000000000..b09a50bf5 --- /dev/null +++ b/pkg/commands/git_commands/bisect.go @@ -0,0 +1,175 @@ +package git_commands + +import ( + "fmt" + "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 { + 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(self.dotGitDir, "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(self.dotGitDir, "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(self.dotGitDir, "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 + } + + sha := 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[sha] = status + } + + currentContent, err := os.ReadFile(filepath.Join(self.dotGitDir, "BISECT_EXPECTED_REV")) + if err != nil { + self.Log.Infof("error getting git bisect info: %s", err.Error()) + return info + } + currentSha := strings.TrimSpace(string(currentContent)) + info.current = currentSha + + return info +} + +func (self *BisectCommands) Reset() error { + return self.cmd.New("git bisect reset").StreamOutput().Run() +} + +func (self *BisectCommands) Mark(ref string, term string) error { + return self.cmd.New( + fmt.Sprintf("git bisect %s %s", term, ref), + ). + IgnoreEmptyError(). + StreamOutput(). + Run() +} + +func (self *BisectCommands) Skip(ref string) error { + return self.Mark(ref, "skip") +} + +func (self *BisectCommands) Start() error { + return self.cmd.New("git bisect start").StreamOutput().Run() +} + +// tells us whether we've found our problem commit(s). We return a string slice of +// commit sha's 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 + } + + newSha := info.GetNewSha() + if newSha == "" { + 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{} + + err := self.cmd.New(fmt.Sprintf("git rev-list %s", newSha)).RunAndProcessLines(func(line string) (bool, error) { + sha := strings.TrimSpace(line) + + if status, ok := info.statusMap[sha]; ok { + switch status { + case BisectStatusSkipped, BisectStatusNew: + candidates = append(candidates, sha) + 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 { + err := self.cmd.New( + fmt.Sprintf("git merge-base --is-ancestor %s %s", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()), + ).DontLog().Run() + + return err == nil +} diff --git a/pkg/commands/git_commands/bisect_info.go b/pkg/commands/git_commands/bisect_info.go new file mode 100644 index 000000000..5b3b1f028 --- /dev/null +++ b/pkg/commands/git_commands/bisect_info.go @@ -0,0 +1,103 @@ +package git_commands + +import "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 sha's to their status + statusMap map[string]BisectStatus + + // the sha 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) GetNewSha() string { + for sha, status := range self.statusMap { + if status == BisectStatusNew { + return sha + } + } + + return "" +} + +func (self *BisectInfo) GetCurrentSha() string { + return self.current +} + +func (self *BisectInfo) GetStartSha() string { + return self.start +} + +func (self *BisectInfo) Status(commitSha string) (BisectStatus, bool) { + status, ok := self.statusMap[commitSha] + 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.GetNewSha() == "" { + return false + } + + for _, status := range self.statusMap { + if status == BisectStatusOld { + return true + } + } + + return false +} diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go new file mode 100644 index 000000000..f6b084c9f --- /dev/null +++ b/pkg/commands/git_commands/branch.go @@ -0,0 +1,168 @@ +package git_commands + +import ( + "fmt" + "regexp" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// this takes something like: +// * (HEAD detached at 264fc6f5) +// remotes +// and returns '264fc6f5' as the second match +const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$` + +type BranchCommands struct { + *GitCommon +} + +func NewBranchCommands(gitCommon *GitCommon) *BranchCommands { + return &BranchCommands{ + GitCommon: gitCommon, + } +} + +// New creates a new branch +func (self *BranchCommands) New(name string, base string) error { + return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run() +} + +// CurrentBranchName get the current branch name and displayname. +// the first returned string is the name and the second is the displayname +// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)' +func (self *BranchCommands) CurrentBranchName() (string, string, error) { + branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput() + if err == nil && branchName != "HEAD\n" { + trimmedBranchName := strings.TrimSpace(branchName) + return trimmedBranchName, trimmedBranchName, nil + } + output, err := self.cmd.New("git branch --contains").DontLog().RunWithOutput() + if err != nil { + return "", "", err + } + for _, line := range utils.SplitLines(output) { + re := regexp.MustCompile(CurrentBranchNameRegex) + match := re.FindStringSubmatch(line) + if len(match) > 0 { + branchName = match[1] + displayBranchName := match[0][2:] + return branchName, displayBranchName, nil + } + } + return "HEAD", "HEAD", nil +} + +// Delete delete branch +func (self *BranchCommands) Delete(branch string, force bool) error { + command := "git branch -d" + + if force { + command = "git branch -D" + } + + return self.cmd.New(fmt.Sprintf("%s %s", command, self.cmd.Quote(branch))).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 { + forceArg := "" + if options.Force { + forceArg = " --force" + } + + return self.cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, self.cmd.Quote(branch))). + // 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.ICmdObj { + branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd + templateValues := map[string]string{ + "branchName": self.cmd.Quote(branchName), + } + return self.cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)).DontLog() +} + +func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error { + return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).Run() +} + +func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error { + return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))).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) { + command := "git rev-list %s..%s --count" + pushableCount, err := self.cmd.New(fmt.Sprintf(command, to, from)).DontLog().RunWithOutput() + if err != nil { + return "?", "?" + } + pullableCount, err := self.cmd.New(fmt.Sprintf(command, from, to)).DontLog().RunWithOutput() + if err != nil { + return "?", "?" + } + return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) +} + +func (self *BranchCommands) IsHeadDetached() bool { + err := self.cmd.New("git symbolic-ref -q HEAD").DontLog().Run() + return err != nil +} + +func (self *BranchCommands) Rename(oldName string, newName string) error { + return self.cmd.New(fmt.Sprintf("git branch --move %s %s", self.cmd.Quote(oldName), self.cmd.Quote(newName))).Run() +} + +func (self *BranchCommands) GetRawBranches() (string, error) { + return self.cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`).DontLog().RunWithOutput() +} + +type MergeOpts struct { + FastForwardOnly bool +} + +func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error { + mergeArg := "" + if self.UserConfig.Git.Merging.Args != "" { + mergeArg = " " + self.UserConfig.Git.Merging.Args + } + + command := fmt.Sprintf("git merge --no-edit%s %s", mergeArg, self.cmd.Quote(branchName)) + if opts.FastForwardOnly { + command = fmt.Sprintf("%s --ff-only", command) + } + + return self.cmd.New(command).Run() +} + +func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj { + return self.cmd.New(self.UserConfig.Git.AllBranchesLogCmd).DontLog() +} diff --git a/pkg/commands/git_commands/branch_test.go b/pkg/commands/git_commands/branch_test.go new file mode 100644 index 000000000..757a8a83d --- /dev/null +++ b/pkg/commands/git_commands/branch_test.go @@ -0,0 +1,225 @@ +package git_commands + +import ( + "testing" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "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). + Expect("git rev-list @{u}..HEAD --count", "", errors.New("error")), + "?", "?", + }, + { + "Can't retrieve pullable count", + oscommands.NewFakeRunner(t). + Expect("git rev-list @{u}..HEAD --count", "1\n", nil). + Expect("git rev-list HEAD..@{u} --count", "", errors.New("error")), + "?", "?", + }, + { + "Retrieve pullable and pushable count", + oscommands.NewFakeRunner(t). + Expect("git rev-list @{u}..HEAD --count", "1\n", nil). + Expect("git rev-list HEAD..@{u} --count", "2\n", nil), + "1", "2", + }, + } + + for _, s := range scenarios { + s := s + 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). + Expect(`git checkout -b "test" "master"`, "", nil) + instance := buildBranchCommands(commonDeps{runner: runner}) + + assert.NoError(t, instance.New("test", "master")) + runner.CheckForMissingCalls() +} + +func TestBranchDeleteBranch(t *testing.T) { + type scenario struct { + testName string + force bool + runner *oscommands.FakeCmdObjRunner + test func(error) + } + + scenarios := []scenario{ + { + "Delete a branch", + false, + oscommands.NewFakeRunner(t).Expect(`git branch -d "test"`, "", nil), + func(err error) { + assert.NoError(t, err) + }, + }, + { + "Force delete a branch", + true, + oscommands.NewFakeRunner(t).Expect(`git branch -D "test"`, "", nil), + func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildBranchCommands(commonDeps{runner: s.runner}) + + s.test(instance.Delete("test", s.force)) + s.runner.CheckForMissingCalls() + }) + } +} + +func TestBranchMerge(t *testing.T) { + runner := oscommands.NewFakeRunner(t). + Expect(`git merge --no-edit "test"`, "", nil) + instance := buildBranchCommands(commonDeps{runner: runner}) + + assert.NoError(t, instance.Merge("test", MergeOpts{})) + 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).Expect(`git checkout "test"`, "", nil), + func(err error) { + assert.NoError(t, err) + }, + false, + }, + { + "Checkout forced", + oscommands.NewFakeRunner(t).Expect(`git checkout --force "test"`, "", nil), + func(err error) { + assert.NoError(t, err) + }, + true, + }, + } + + for _, s := range scenarios { + s := s + 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 TestBranchCurrentBranchName(t *testing.T) { + type scenario struct { + testName string + runner *oscommands.FakeCmdObjRunner + test func(string, string, error) + } + + scenarios := []scenario{ + { + "says we are on the master branch if we are", + oscommands.NewFakeRunner(t).Expect(`git symbolic-ref --short HEAD`, "master", nil), + func(name string, displayname string, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "master", name) + assert.EqualValues(t, "master", displayname) + }, + }, + { + "falls back to git `git branch --contains` if symbolic-ref fails", + oscommands.NewFakeRunner(t). + Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")). + Expect(`git branch --contains`, "* master", nil), + func(name string, displayname string, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "master", name) + assert.EqualValues(t, "master", displayname) + }, + }, + { + "handles a detached head", + oscommands.NewFakeRunner(t). + Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")). + Expect(`git branch --contains`, "* (HEAD detached at 123abcd)", nil), + func(name string, displayname string, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "123abcd", name) + assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname) + }, + }, + { + "bubbles up error if there is one", + oscommands.NewFakeRunner(t). + Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")). + Expect(`git branch --contains`, "", errors.New("error")), + func(name string, displayname string, err error) { + assert.Error(t, err) + assert.EqualValues(t, "", name) + assert.EqualValues(t, "", displayname) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildBranchCommands(commonDeps{runner: s.runner}) + s.test(instance.CurrentBranchName()) + s.runner.CheckForMissingCalls() + }) + } +} diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go new file mode 100644 index 000000000..38f50bdda --- /dev/null +++ b/pkg/commands/git_commands/commit.go @@ -0,0 +1,125 @@ +package git_commands + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" +) + +type CommitCommands struct { + *GitCommon +} + +func NewCommitCommands(gitCommon *GitCommon) *CommitCommands { + return &CommitCommands{ + GitCommon: gitCommon, + } +} + +// RewordLastCommit rewords the topmost commit with the given message +func (self *CommitCommands) RewordLastCommit(message string) error { + return self.cmd.New("git commit --allow-empty --amend --only -m " + self.cmd.Quote(message)).Run() +} + +// ResetToCommit reset to commit +func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error { + return self.cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)). + // 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(message string) oscommands.ICmdObj { + splitMessage := strings.Split(message, "\n") + lineArgs := "" + for _, line := range splitMessage { + lineArgs += fmt.Sprintf(" -m %s", self.cmd.Quote(line)) + } + + skipHookPrefix := self.UserConfig.Git.SkipHookPrefix + noVerifyFlag := "" + if skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix) { + noVerifyFlag = " --no-verify" + } + + return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), lineArgs)) +} + +// runs git commit without the -m argument meaning it will invoke the user's editor +func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj { + return self.cmd.New(fmt.Sprintf("git commit%s", self.signoffFlag())) +} + +func (self *CommitCommands) signoffFlag() string { + if self.UserConfig.Git.Commit.SignOff { + return " --signoff" + } else { + return "" + } +} + +// Get the subject of the HEAD commit +func (self *CommitCommands) GetHeadCommitMessage() (string, error) { + message, err := self.cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput() + return strings.TrimSpace(message), err +} + +func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) { + cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha + messageWithHeader, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() + message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "\n") + return strings.TrimSpace(message), err +} + +func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error) { + return self.GetCommitMessagesFirstLine([]string{sha}) +} + +func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) { + return self.cmd.New( + fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", strings.Join(shas, " ")), + ).DontLog().RunWithOutput() +} + +func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) { + return self.cmd.New( + fmt.Sprintf("git show --no-patch --oneline %s", strings.Join(shas, " ")), + ).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.ICmdObj { + return self.cmd.New("git commit --amend --no-edit --allow-empty") +} + +func (self *CommitCommands) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj { + contextSize := self.UserConfig.Git.DiffContextSize + filterPathArg := "" + if filterPath != "" { + filterPathArg = fmt.Sprintf(" -- %s", self.cmd.Quote(filterPath)) + } + + cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --no-renames --stat -p %s %s", self.UserConfig.Git.Paging.ColorArg, contextSize, sha, filterPathArg) + return self.cmd.New(cmdStr).DontLog() +} + +// Revert reverts the selected commit by sha +func (self *CommitCommands) Revert(sha string) error { + return self.cmd.New(fmt.Sprintf("git revert %s", sha)).Run() +} + +func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error { + return self.cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run() +} + +// CreateFixupCommit creates a commit that fixes up a previous commit +func (self *CommitCommands) CreateFixupCommit(sha string) error { + return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run() +} diff --git a/pkg/commands/git_commands/commit_test.go b/pkg/commands/git_commands/commit_test.go new file mode 100644 index 000000000..08ee7e5cc --- /dev/null +++ b/pkg/commands/git_commands/commit_test.go @@ -0,0 +1,163 @@ +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) { + runner := oscommands.NewFakeRunner(t). + ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil) + instance := buildCommitCommands(commonDeps{runner: runner}) + + assert.NoError(t, instance.RewordLastCommit("test")) + 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 TestCommitCommitObj(t *testing.T) { + type scenario struct { + testName string + message string + configSignoff bool + configSkipHookPrefix string + expected string + } + + scenarios := []scenario{ + { + testName: "Commit", + message: "test", + configSignoff: false, + configSkipHookPrefix: "", + expected: `git commit -m "test"`, + }, + { + testName: "Commit with --no-verify flag", + message: "WIP: test", + configSignoff: false, + configSkipHookPrefix: "WIP", + expected: `git commit --no-verify -m "WIP: test"`, + }, + { + testName: "Commit with multiline message", + message: "line1\nline2", + configSignoff: false, + configSkipHookPrefix: "", + expected: `git commit -m "line1" -m "line2"`, + }, + { + testName: "Commit with signoff", + message: "test", + configSignoff: true, + configSkipHookPrefix: "", + expected: `git commit --signoff -m "test"`, + }, + { + testName: "Commit with signoff and no-verify", + message: "WIP: test", + configSignoff: true, + configSkipHookPrefix: "WIP", + expected: `git commit --no-verify --signoff -m "WIP: test"`, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + userConfig := config.GetDefaultConfig() + userConfig.Git.Commit.SignOff = s.configSignoff + userConfig.Git.SkipHookPrefix = s.configSkipHookPrefix + + instance := buildCommitCommands(commonDeps{userConfig: userConfig}) + + cmdStr := instance.CommitCmdObj(s.message).ToString() + assert.Equal(t, s.expected, cmdStr) + }) + } +} + +func TestCommitCreateFixupCommit(t *testing.T) { + type scenario struct { + testName string + sha string + runner *oscommands.FakeCmdObjRunner + test func(error) + } + + scenarios := []scenario{ + { + testName: "valid case", + sha: "12345", + runner: oscommands.NewFakeRunner(t). + Expect(`git commit --fixup=12345`, "", nil), + test: func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildCommitCommands(commonDeps{runner: s.runner}) + s.test(instance.CreateFixupCommit(s.sha)) + s.runner.CheckForMissingCalls() + }) + } +} + +func TestCommitShowCmdObj(t *testing.T) { + type scenario struct { + testName string + filterPath string + contextSize int + expected string + } + + scenarios := []scenario{ + { + testName: "Default case without filter path", + filterPath: "", + contextSize: 3, + expected: "git show --submodule --color=always --unified=3 --no-renames --stat -p 1234567890 ", + }, + { + testName: "Default case with filter path", + filterPath: "file.txt", + contextSize: 3, + expected: `git show --submodule --color=always --unified=3 --no-renames --stat -p 1234567890 -- "file.txt"`, + }, + { + testName: "Show diff with custom context size", + filterPath: "", + contextSize: 77, + expected: "git show --submodule --color=always --unified=77 --no-renames --stat -p 1234567890 ", + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + userConfig := config.GetDefaultConfig() + userConfig.Git.DiffContextSize = s.contextSize + + instance := buildCommitCommands(commonDeps{userConfig: userConfig}) + + cmdStr := instance.ShowCmdObj("1234567890", s.filterPath).ToString() + assert.Equal(t, s.expected, cmdStr) + }) + } +} diff --git a/pkg/commands/git_commands/common.go b/pkg/commands/git_commands/common.go new file mode 100644 index 000000000..a045be75a --- /dev/null +++ b/pkg/commands/git_commands/common.go @@ -0,0 +1,34 @@ +package git_commands + +import ( + gogit "github.com/jesseduffield/go-git/v5" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/common" +) + +type GitCommon struct { + *common.Common + cmd oscommands.ICmdObjBuilder + os *oscommands.OSCommand + dotGitDir string + repo *gogit.Repository + config *ConfigCommands +} + +func NewGitCommon( + cmn *common.Common, + cmd oscommands.ICmdObjBuilder, + osCommand *oscommands.OSCommand, + dotGitDir string, + repo *gogit.Repository, + config *ConfigCommands, +) *GitCommon { + return &GitCommon{ + Common: cmn, + cmd: cmd, + os: osCommand, + dotGitDir: dotGitDir, + repo: repo, + config: config, + } +} diff --git a/pkg/commands/git_commands/config.go b/pkg/commands/git_commands/config.go new file mode 100644 index 000000000..e22333541 --- /dev/null +++ b/pkg/commands/git_commands/config.go @@ -0,0 +1,101 @@ +package git_commands + +import ( + "os" + "strconv" + "strings" + + 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" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +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, + } +} + +func (self *ConfigCommands) ConfiguredPager() string { + if os.Getenv("GIT_PAGER") != "" { + return os.Getenv("GIT_PAGER") + } + if os.Getenv("PAGER") != "" { + return os.Getenv("PAGER") + } + output := self.gitConfig.Get("core.pager") + return strings.Split(output, "\n")[0] +} + +func (self *ConfigCommands) GetPager(width int) string { + useConfig := self.UserConfig.Git.Paging.UseConfig + if useConfig { + pager := self.ConfiguredPager() + return strings.Split(pager, "| less")[0] + } + + templateValues := map[string]string{ + "columnWidth": strconv.Itoa(width/2 - 6), + } + + pagerTemplate := self.UserConfig.Git.Paging.Pager + return utils.ResolvePlaceholderString(pagerTemplate, templateValues) +} + +// UsingGpg tells us whether the user has gpg enabled so that we can know +// whether we need to run a subprocess to allow them to enter their password +func (self *ConfigCommands) UsingGpg() bool { + overrideGpg := self.UserConfig.Git.OverrideGpg + if overrideGpg { + return false + } + + return self.gitConfig.GetBool("commit.gpgsign") +} + +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") +} diff --git a/pkg/commands/git_commands/custom.go b/pkg/commands/git_commands/custom.go new file mode 100644 index 000000000..6dff6e771 --- /dev/null +++ b/pkg/commands/git_commands/custom.go @@ -0,0 +1,18 @@ +package git_commands + +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(cmdStr).RunWithOutput() +} diff --git a/pkg/commands/git_commands/deps_test.go b/pkg/commands/git_commands/deps_test.go new file mode 100644 index 000000000..834d51b55 --- /dev/null +++ b/pkg/commands/git_commands/deps_test.go @@ -0,0 +1,144 @@ +package git_commands + +import ( + "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/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +type commonDeps struct { + runner *oscommands.FakeCmdObjRunner + userConfig *config.UserConfig + gitConfig *git_config.FakeGitConfig + getenv func(string) string + removeFile func(string) error + dotGitDir string + common *common.Common + cmd *oscommands.CmdObjBuilder +} + +func buildGitCommon(deps commonDeps) *GitCommon { + gitCommon := &GitCommon{} + + gitCommon.Common = deps.common + if gitCommon.Common == nil { + gitCommon.Common = utils.NewDummyCommonWithUserConfig(deps.userConfig) + } + + 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.UserConfig = deps.userConfig + if gitCommon.Common.UserConfig == nil { + gitCommon.Common.UserConfig = config.GetDefaultConfig() + } + + 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, + }) + + gitCommon.dotGitDir = deps.dotGitDir + if gitCommon.dotGitDir == "" { + gitCommon.dotGitDir = ".git" + } + + return gitCommon +} + +func buildRepo() *gogit.Repository { + // TODO: think of a way to actually mock this out + var repo *gogit.Repository = nil + return repo +} + +func buildFileLoader(gitCommon *GitCommon) *loaders.FileLoader { + return loaders.NewFileLoader(gitCommon.Common, 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) +} diff --git a/pkg/commands/git_commands/file.go b/pkg/commands/git_commands/file.go new file mode 100644 index 000000000..026d79cb0 --- /dev/null +++ b/pkg/commands/git_commands/file.go @@ -0,0 +1,62 @@ +package git_commands + +import ( + "io/ioutil" + "strconv" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +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 := ioutil.ReadFile(fileName) + if err != nil { + return "", nil + } + return string(buf), nil +} + +func (self *FileCommands) GetEditCmdStr(filename string, lineNumber int) (string, error) { + editor := self.UserConfig.OS.EditCommand + + if editor == "" { + 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 == "" { + if err := self.cmd.New("which vi").DontLog().Run(); err == nil { + editor = "vi" + } + } + if editor == "" { + return "", errors.New("No editor defined in config file, $GIT_EDITOR, $VISUAL, $EDITOR, or git config") + } + + templateValues := map[string]string{ + "editor": editor, + "filename": self.cmd.Quote(filename), + "line": strconv.Itoa(lineNumber), + } + + editCmdTemplate := self.UserConfig.OS.EditCommandTemplate + return utils.ResolvePlaceholderString(editCmdTemplate, templateValues), nil +} diff --git a/pkg/commands/git_commands/file_test.go b/pkg/commands/git_commands/file_test.go new file mode 100644 index 000000000..a26699b3e --- /dev/null +++ b/pkg/commands/git_commands/file_test.go @@ -0,0 +1,163 @@ +package git_commands + +import ( + "testing" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/commands/git_config" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestEditFileCmdStr(t *testing.T) { + type scenario struct { + filename string + configEditCommand string + configEditCommandTemplate string + runner *oscommands.FakeCmdObjRunner + getenv func(string) string + gitConfigMockResponses map[string]string + test func(string, error) + } + + scenarios := []scenario{ + { + filename: "test", + configEditCommand: "", + configEditCommandTemplate: "{{editor}} {{filename}}", + runner: oscommands.NewFakeRunner(t). + Expect(`which vi`, "", errors.New("error")), + getenv: func(env string) string { + return "" + }, + gitConfigMockResponses: nil, + test: func(cmdStr string, err error) { + assert.EqualError(t, err, "No editor defined in config file, $GIT_EDITOR, $VISUAL, $EDITOR, or git config") + }, + }, + { + filename: "test", + configEditCommand: "nano", + configEditCommandTemplate: "{{editor}} {{filename}}", + runner: oscommands.NewFakeRunner(t), + getenv: func(env string) string { + return "" + }, + gitConfigMockResponses: nil, + test: func(cmdStr string, err error) { + assert.NoError(t, err) + assert.Equal(t, `nano "test"`, cmdStr) + }, + }, + { + filename: "test", + configEditCommand: "", + configEditCommandTemplate: "{{editor}} {{filename}}", + runner: oscommands.NewFakeRunner(t), + getenv: func(env string) string { + return "" + }, + gitConfigMockResponses: map[string]string{"core.editor": "nano"}, + test: func(cmdStr string, err error) { + assert.NoError(t, err) + assert.Equal(t, `nano "test"`, cmdStr) + }, + }, + { + filename: "test", + configEditCommand: "", + configEditCommandTemplate: "{{editor}} {{filename}}", + runner: oscommands.NewFakeRunner(t), + getenv: func(env string) string { + if env == "VISUAL" { + return "nano" + } + + return "" + }, + gitConfigMockResponses: nil, + test: func(cmdStr string, err error) { + assert.NoError(t, err) + }, + }, + { + filename: "test", + configEditCommand: "", + configEditCommandTemplate: "{{editor}} {{filename}}", + runner: oscommands.NewFakeRunner(t), + getenv: func(env string) string { + if env == "EDITOR" { + return "emacs" + } + + return "" + }, + gitConfigMockResponses: nil, + test: func(cmdStr string, err error) { + assert.NoError(t, err) + assert.Equal(t, `emacs "test"`, cmdStr) + }, + }, + { + filename: "test", + configEditCommand: "", + configEditCommandTemplate: "{{editor}} {{filename}}", + runner: oscommands.NewFakeRunner(t). + Expect(`which vi`, "/usr/bin/vi", nil), + getenv: func(env string) string { + return "" + }, + gitConfigMockResponses: nil, + test: func(cmdStr string, err error) { + assert.NoError(t, err) + assert.Equal(t, `vi "test"`, cmdStr) + }, + }, + { + filename: "file/with space", + configEditCommand: "", + configEditCommandTemplate: "{{editor}} {{filename}}", + runner: oscommands.NewFakeRunner(t). + Expect(`which vi`, "/usr/bin/vi", nil), + getenv: func(env string) string { + return "" + }, + gitConfigMockResponses: nil, + test: func(cmdStr string, err error) { + assert.NoError(t, err) + assert.Equal(t, `vi "file/with space"`, cmdStr) + }, + }, + { + filename: "open file/at line", + configEditCommand: "vim", + configEditCommandTemplate: "{{editor}} +{{line}} {{filename}}", + runner: oscommands.NewFakeRunner(t), + getenv: func(env string) string { + return "" + }, + gitConfigMockResponses: nil, + test: func(cmdStr string, err error) { + assert.NoError(t, err) + assert.Equal(t, `vim +1 "open file/at line"`, cmdStr) + }, + }, + } + + for _, s := range scenarios { + userConfig := config.GetDefaultConfig() + userConfig.OS.EditCommand = s.configEditCommand + userConfig.OS.EditCommandTemplate = s.configEditCommandTemplate + + instance := buildFileCommands(commonDeps{ + runner: s.runner, + userConfig: userConfig, + gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses), + getenv: s.getenv, + }) + + s.test(instance.GetEditCmdStr(s.filename, 1)) + s.runner.CheckForMissingCalls() + } +} diff --git a/pkg/commands/git_commands/flow.go b/pkg/commands/git_commands/flow.go new file mode 100644 index 000000000..fd97707ec --- /dev/null +++ b/pkg/commands/git_commands/flow.go @@ -0,0 +1,56 @@ +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.ICmdObj, 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) + } + + return self.cmd.New("git flow " + branchType + " finish " + suffix), nil +} + +func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj { + return self.cmd.New("git flow " + branchType + " start " + name) +} diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go new file mode 100644 index 000000000..8f9ce5784 --- /dev/null +++ b/pkg/commands/git_commands/patch.go @@ -0,0 +1,256 @@ +package git_commands + +import ( + "fmt" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/patch" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" +) + +type PatchCommands struct { + *GitCommon + rebase *RebaseCommands + commit *CommitCommands + status *StatusCommands + stash *StashCommands + + PatchManager *patch.PatchManager +} + +func NewPatchCommands( + gitCommon *GitCommon, + rebase *RebaseCommands, + commit *CommitCommands, + status *StatusCommands, + stash *StashCommands, + patchManager *patch.PatchManager, +) *PatchCommands { + return &PatchCommands{ + GitCommon: gitCommon, + rebase: rebase, + commit: commit, + status: status, + stash: stash, + PatchManager: patchManager, + } +} + +// 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); err != nil { + return err + } + + // apply each patch in reverse + if err := self.PatchManager.ApplyPatches(true); err != nil { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + return err + } + + // time to amend the selected commit + if err := self.commit.AmendHead(); err != nil { + return err + } + + self.rebase.onSuccessfulContinue = func() error { + self.PatchManager.Reset() + return nil + } + + // continue + return self.rebase.ContinueRebase() +} + +func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, sourceCommitIdx int, destinationCommitIdx int) error { + if sourceCommitIdx < destinationCommitIdx { + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx); err != nil { + return err + } + + // apply each patch forward + if err := self.PatchManager.ApplyPatches(false); err != nil { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + return err + } + + // amend the destination commit + if err := self.commit.AmendHead(); err != nil { + return err + } + + self.rebase.onSuccessfulContinue = func() error { + self.PatchManager.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.UsingGpg() { + return errors.New(self.Tr.DisabledForGPG) + } + + baseIndex := sourceCommitIdx + 1 + todo := "" + for i, commit := range commits[0:baseIndex] { + a := "pick" + if i == sourceCommitIdx || i == destinationCommitIdx { + a = "edit" + } + todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo + } + + err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true).Run() + if err != nil { + return err + } + + // apply each patch in reverse + if err := self.PatchManager.ApplyPatches(true); err != nil { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + return err + } + + // amend the source commit + if err := self.commit.AmendHead(); 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.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.PatchManager.ApplyPatches(false); err != nil { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + return err + } + + // amend the destination commit + if err := self.commit.AmendHead(); err != nil { + return err + } + + self.rebase.onSuccessfulContinue = func() error { + self.PatchManager.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.Save(self.Tr.StashPrefix + commits[commitIdx].Sha); err != nil { + return err + } + } + + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil { + return err + } + + if err := self.PatchManager.ApplyPatches(true); err != nil { + if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + } + return err + } + + // amend the commit + if err := self.commit.AmendHead(); 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.rebase.onSuccessfulContinue = func() error { + // add patches to index + if err := self.PatchManager.ApplyPatches(false); err != nil { + if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + } + return err + } + + if stash { + if err := self.stash.Apply(0); err != nil { + return err + } + } + + self.PatchManager.Reset() + return nil + } + + return self.rebase.ContinueRebase() +} + +func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx int) error { + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil { + return err + } + + if err := self.PatchManager.ApplyPatches(true); err != nil { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + return err + } + + // amend the commit + if err := self.commit.AmendHead(); err != nil { + return err + } + + // add patches to index + if err := self.PatchManager.ApplyPatches(false); err != nil { + if err := self.rebase.AbortRebase(); err != nil { + return err + } + return err + } + + head_message, _ := self.commit.GetHeadCommitMessage() + new_message := fmt.Sprintf("Split from \"%s\"", head_message) + err := self.commit.CommitCmdObj(new_message).Run() + if 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.PatchManager.Reset() + return self.rebase.ContinueRebase() +} diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go new file mode 100644 index 000000000..c726cad7e --- /dev/null +++ b/pkg/commands/git_commands/rebase.go @@ -0,0 +1,345 @@ +package git_commands + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" +) + +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, message string) error { + if index == 0 { + // we've selected the top commit so no rebase is required + return self.commit.RewordLastCommit(message) + } + + err := self.BeginInteractiveRebaseForCommit(commits, index) + 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(message) + if err != nil { + return err + } + + return self.ContinueRebase() +} + +func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) { + todo, sha, err := self.GenerateGenericRebaseTodo(commits, index, "reword") + if err != nil { + return nil, err + } + + return self.PrepareInteractiveRebaseCommand(sha, todo, false), nil +} + +func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error { + // we must ensure that we have at least two commits after the selected one + if len(commits) <= index+2 { + // assuming they aren't picking the bottom commit + return errors.New(self.Tr.NoRoom) + } + + todo := "" + orderedCommits := append(commits[0:index], commits[index+1], commits[index]) + for _, commit := range orderedCommits { + todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo + } + + return self.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true).Run() +} + +func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error { + todo, sha, err := self.GenerateGenericRebaseTodo(commits, index, action) + if err != nil { + return err + } + + return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run() +} + +// 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 a todo string to write to the todo file +func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) oscommands.ICmdObj { + ex := oscommands.GetLazygitPath() + + debug := "FALSE" + if self.Debug { + debug = "TRUE" + } + + cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha) + self.Log.WithField("command", cmdStr).Info("RunCommand") + + cmdObj := self.cmd.New(cmdStr) + + gitSequenceEditor := ex + if todo == "" { + gitSequenceEditor = "true" + } else { + self.os.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false) + } + + cmdObj.AddEnvVars( + "LAZYGIT_CLIENT_COMMAND=INTERACTIVE_REBASE", + "LAZYGIT_REBASE_TODO="+todo, + "DEBUG="+debug, + "LANG=en_US.UTF-8", // Force using EN as language + "LC_ALL=en_US.UTF-8", // Force using EN as language + "GIT_SEQUENCE_EDITOR="+gitSequenceEditor, + ) + + if overrideEditor { + cmdObj.AddEnvVars("GIT_EDITOR=" + ex) + } + + return cmdObj +} + +func (self *RebaseCommands) GenerateGenericRebaseTodo(commits []*models.Commit, actionIndex int, action string) (string, string, error) { + baseIndex := actionIndex + 1 + + if len(commits) <= baseIndex { + return "", "", errors.New(self.Tr.CannotRebaseOntoFirstCommit) + } + + if action == "squash" || action == "fixup" { + baseIndex++ + + if len(commits) <= baseIndex { + return "", "", errors.New(self.Tr.CannotSquashOntoSecondCommit) + } + } + + todo := "" + for i, commit := range commits[0:baseIndex] { + var commitAction string + if i == actionIndex { + commitAction = action + } else if commit.IsMerge() { + // your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary! + // doing this means we don't need to worry about rebasing over merges which always causes problems. + // you typically shouldn't be doing rebases that pass over merge commits anyway. + commitAction = "drop" + } else { + commitAction = "pick" + } + todo = commitAction + " " + commit.Sha + " " + commit.Name + "\n" + todo + } + + return todo, commits[baseIndex].Sha, nil +} + +// AmendTo amends the given commit with whatever files are staged +func (self *RebaseCommands) AmendTo(sha string) error { + if err := self.commit.CreateFixupCommit(sha); err != nil { + return err + } + + return self.SquashAllAboveFixupCommits(sha) +} + +// EditRebaseTodo sets the action at a given index in the git-rebase-todo file +func (self *RebaseCommands) EditRebaseTodo(index int, action string) error { + fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo") + bytes, err := ioutil.ReadFile(fileName) + if err != nil { + return err + } + + content := strings.Split(string(bytes), "\n") + commitCount := self.getTodoCommitCount(content) + + // we have the most recent commit at the bottom whereas the todo file has + // it at the bottom, so we need to subtract our index from the commit count + contentIndex := commitCount - 1 - index + splitLine := strings.Split(content[contentIndex], " ") + content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ") + result := strings.Join(content, "\n") + + return ioutil.WriteFile(fileName, []byte(result), 0644) +} + +func (self *RebaseCommands) getTodoCommitCount(content []string) int { + // count lines that are not blank and are not comments + commitCount := 0 + for _, line := range content { + if line != "" && !strings.HasPrefix(line, "#") { + commitCount++ + } + } + return commitCount +} + +// MoveTodoDown moves a rebase todo item down by one position +func (self *RebaseCommands) MoveTodoDown(index int) error { + fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo") + bytes, err := ioutil.ReadFile(fileName) + if err != nil { + return err + } + + content := strings.Split(string(bytes), "\n") + commitCount := self.getTodoCommitCount(content) + contentIndex := commitCount - 1 - index + + rearrangedContent := append(content[0:contentIndex-1], content[contentIndex], content[contentIndex-1]) + rearrangedContent = append(rearrangedContent, content[contentIndex+1:]...) + result := strings.Join(rearrangedContent, "\n") + + return ioutil.WriteFile(fileName, []byte(result), 0644) +} + +// SquashAllAboveFixupCommits squashes all fixup! commits above the given one +func (self *RebaseCommands) SquashAllAboveFixupCommits(sha string) error { + return self.runSkipEditorCommand( + self.cmd.New( + fmt.Sprintf( + "git rebase --interactive --autostash --autosquash %s^", + sha, + ), + ), + ) +} + +// 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) error { + if len(commits)-1 < commitIndex { + 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.UsingGpg() { + return errors.New(self.Tr.DisabledForGPG) + } + + todo, sha, err := self.GenerateGenericRebaseTodo(commits, commitIndex, "edit") + if err != nil { + return err + } + + return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run() +} + +// RebaseBranch interactive rebases onto a branch +func (self *RebaseCommands) RebaseBranch(branchName string) error { + return self.PrepareInteractiveRebaseCommand(branchName, "", false).Run() +} + +func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj { + return self.cmd.New("git " + commandType + " --" + command) +} + +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.ICmdObj) error { + lazyGitPath := oscommands.GetLazygitPath() + return cmdObj. + AddEnvVars( + "LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY", + "GIT_EDITOR="+lazyGitPath, + "EDITOR="+lazyGitPath, + "VISUAL="+lazyGitPath, + ). + Run() +} + +// DiscardOldFileChanges discards changes to a file from an old commit +func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, fileName string) error { + if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil { + return err + } + + // check if file exists in previous commit (this command returns an error if the file doesn't exist) + if err := self.cmd.New("git cat-file -e HEAD^:" + self.cmd.Quote(fileName)).Run(); err != nil { + if err := self.os.Remove(fileName); err != nil { + return err + } + if err := self.workingTree.StageFile(fileName); err != nil { + return err + } + } else if err := self.workingTree.CheckoutFile("HEAD^", fileName); 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 shas being cherry picked onto HEAD +func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { + todo := "" + for _, commit := range commits { + todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo + } + + return self.PrepareInteractiveRebaseCommand("HEAD", todo, false).Run() +} diff --git a/pkg/commands/git_commands/rebase_test.go b/pkg/commands/git_commands/rebase_test.go new file mode 100644 index 000000000..56df77a86 --- /dev/null +++ b/pkg/commands/git_commands/rebase_test.go @@ -0,0 +1,151 @@ +package git_commands + +import ( + "regexp" + "testing" + + "github.com/go-errors/errors" + "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/stretchr/testify/assert" +) + +func TestRebaseRebaseBranch(t *testing.T) { + type scenario struct { + testName string + arg string + runner *oscommands.FakeCmdObjRunner + test func(error) + } + + scenarios := []scenario{ + { + testName: "successful rebase", + arg: "master", + runner: oscommands.NewFakeRunner(t). + Expect(`git rebase --interactive --autostash --keep-empty master`, "", nil), + test: func(err error) { + assert.NoError(t, err) + }, + }, + { + testName: "unsuccessful rebase", + arg: "master", + runner: oscommands.NewFakeRunner(t). + Expect(`git rebase --interactive --autostash --keep-empty master`, "", errors.New("error")), + test: func(err error) { + assert.Error(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildRebaseCommands(commonDeps{runner: s.runner}) + s.test(instance.RebaseBranch(s.arg)) + }) + } +} + +// TestRebaseSkipEditorCommand confirms that SkipEditorCommand injects +// environment variables that suppress an interactive editor +func TestRebaseSkipEditorCommand(t *testing.T) { + commandStr := "git blah" + runner := oscommands.NewFakeRunner(t).ExpectFunc(func(cmdObj oscommands.ICmdObj) (string, error) { + assert.Equal(t, commandStr, cmdObj.ToString()) + envVars := cmdObj.GetEnvVars() + for _, regexStr := range []string{ + `^VISUAL=.*$`, + `^EDITOR=.*$`, + `^GIT_EDITOR=.*$`, + "^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$", + } { + regexStr := regexStr + foundMatch := utils.IncludesStringFunc(envVars, func(envVar string) bool { + return regexp.MustCompile(regexStr).MatchString(envVar) + }) + if !foundMatch { + t.Errorf("expected environment variable %s to be set", regexStr) + } + } + return "", nil + }) + instance := buildRebaseCommands(commonDeps{runner: runner}) + err := instance.runSkipEditorCommand(instance.cmd.New(commandStr)) + assert.NoError(t, err) + runner.CheckForMissingCalls() +} + +func TestRebaseDiscardOldFileChanges(t *testing.T) { + type scenario struct { + testName string + gitConfigMockResponses map[string]string + commits []*models.Commit + commitIndex int + fileName string + runner *oscommands.FakeCmdObjRunner + test func(error) + } + + scenarios := []scenario{ + { + testName: "returns error when index outside of range of commits", + gitConfigMockResponses: nil, + commits: []*models.Commit{}, + commitIndex: 0, + fileName: "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"}, + commits: []*models.Commit{{Name: "commit", Sha: "123456"}}, + commitIndex: 0, + fileName: "test999.txt", + runner: oscommands.NewFakeRunner(t), + test: func(err error) { + assert.Error(t, err) + }, + }, + { + testName: "checks out file if it already existed", + gitConfigMockResponses: nil, + commits: []*models.Commit{ + {Name: "commit", Sha: "123456"}, + {Name: "commit2", Sha: "abcdef"}, + }, + commitIndex: 0, + fileName: "test999.txt", + runner: oscommands.NewFakeRunner(t). + Expect(`git rebase --interactive --autostash --keep-empty abcdef`, "", nil). + Expect(`git cat-file -e HEAD^:"test999.txt"`, "", nil). + Expect(`git checkout HEAD^ -- "test999.txt"`, "", nil). + Expect(`git commit --amend --no-edit --allow-empty`, "", nil). + Expect(`git 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 { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildRebaseCommands(commonDeps{ + runner: s.runner, + gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses), + }) + + s.test(instance.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName)) + s.runner.CheckForMissingCalls() + }) + } +} diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go new file mode 100644 index 000000000..3116c764a --- /dev/null +++ b/pkg/commands/git_commands/remote.go @@ -0,0 +1,58 @@ +package git_commands + +import ( + "fmt" +) + +type RemoteCommands struct { + *GitCommon +} + +func NewRemoteCommands(gitCommon *GitCommon) *RemoteCommands { + return &RemoteCommands{ + GitCommon: gitCommon, + } +} + +func (self *RemoteCommands) AddRemote(name string, url string) error { + return self.cmd. + New(fmt.Sprintf("git remote add %s %s", self.cmd.Quote(name), self.cmd.Quote(url))). + Run() +} + +func (self *RemoteCommands) RemoveRemote(name string) error { + return self.cmd. + New(fmt.Sprintf("git remote remove %s", self.cmd.Quote(name))). + Run() +} + +func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error { + return self.cmd. + New(fmt.Sprintf("git remote rename %s %s", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName))). + Run() +} + +func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error { + return self.cmd. + New(fmt.Sprintf("git remote set-url %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl))). + Run() +} + +func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error { + command := fmt.Sprintf("git push %s --delete %s", self.cmd.Quote(remoteName), self.cmd.Quote(branchName)) + return self.cmd.New(command).PromptOnCredentialRequest().Run() +} + +// CheckRemoteBranchExists Returns remote branch +func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool { + _, err := self.cmd. + New( + fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s", + self.cmd.Quote(branchName), + ), + ). + DontLog(). + RunWithOutput() + + return err == nil +} diff --git a/pkg/commands/git_commands/stash.go b/pkg/commands/git_commands/stash.go new file mode 100644 index 000000000..d20024aa9 --- /dev/null +++ b/pkg/commands/git_commands/stash.go @@ -0,0 +1,91 @@ +package git_commands + +import ( + "fmt" + + "github.com/jesseduffield/lazygit/pkg/commands/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" +) + +type StashCommands struct { + *GitCommon + fileLoader *loaders.FileLoader + workingTree *WorkingTreeCommands +} + +func NewStashCommands( + gitCommon *GitCommon, + fileLoader *loaders.FileLoader, + workingTree *WorkingTreeCommands, +) *StashCommands { + return &StashCommands{ + GitCommon: gitCommon, + fileLoader: fileLoader, + workingTree: workingTree, + } +} + +func (self *StashCommands) Drop(index int) error { + return self.cmd.New(fmt.Sprintf("git stash drop stash@{%d}", index)).Run() +} + +func (self *StashCommands) Pop(index int) error { + return self.cmd.New(fmt.Sprintf("git stash pop stash@{%d}", index)).Run() +} + +func (self *StashCommands) Apply(index int) error { + return self.cmd.New(fmt.Sprintf("git stash apply stash@{%d}", index)).Run() +} + +// Save save stash +// TODO: before calling this, check if there is anything to save +func (self *StashCommands) Save(message string) error { + return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run() +} + +func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj { + cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index) + + return self.cmd.New(cmdStr).DontLog() +} + +// SaveStagedChanges stashes only the currently staged changes. This takes a few steps +// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible +func (self *StashCommands) SaveStagedChanges(message string) error { + // wrap in 'writing', which uses a mutex + if err := self.cmd.New("git stash --keep-index").Run(); err != nil { + return err + } + + if err := self.Save(message); err != nil { + return err + } + + if err := self.cmd.New("git stash apply stash@{1}").Run(); err != nil { + return err + } + + if err := self.os.PipeCommands("git stash show -p", "git apply -R"); err != nil { + return err + } + + if err := self.cmd.New("git stash drop stash@{1}").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(loaders.GetStatusFileOptions{}) + + for _, file := range files { + if file.ShortStatus == "AD" { + if err := self.workingTree.UnStageFile(file.Names(), false); err != nil { + return err + } + } + } + + return nil +} diff --git a/pkg/commands/git_commands/stash_test.go b/pkg/commands/git_commands/stash_test.go new file mode 100644 index 000000000..b41f7815b --- /dev/null +++ b/pkg/commands/git_commands/stash_test.go @@ -0,0 +1,81 @@ +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", "stash@{1}"}, "", 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", "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", "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", "save", "A stash message"}, "", nil) + instance := buildStashCommands(commonDeps{runner: runner}) + + assert.NoError(t, instance.Save("A stash message")) + runner.CheckForMissingCalls() +} + +func TestStashStashEntryCmdObj(t *testing.T) { + type scenario struct { + testName string + index int + contextSize int + expected string + } + + scenarios := []scenario{ + { + testName: "Default case", + index: 5, + contextSize: 3, + expected: "git stash show -p --stat --color=always --unified=3 stash@{5}", + }, + { + testName: "Show diff with custom context size", + index: 5, + contextSize: 77, + expected: "git stash show -p --stat --color=always --unified=77 stash@{5}", + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + userConfig := config.GetDefaultConfig() + userConfig.Git.DiffContextSize = s.contextSize + instance := buildStashCommands(commonDeps{userConfig: userConfig}) + + cmdStr := instance.ShowStashEntryCmdObj(s.index).ToString() + assert.Equal(t, s.expected, cmdStr) + }) + } +} diff --git a/pkg/commands/git_commands/status.go b/pkg/commands/git_commands/status.go new file mode 100644 index 000000000..50b1fab57 --- /dev/null +++ b/pkg/commands/git_commands/status.go @@ -0,0 +1,61 @@ +package git_commands + +import ( + "path/filepath" + + gogit "github.com/jesseduffield/go-git/v5" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" +) + +type StatusCommands struct { + *GitCommon +} + +func NewStatusCommands( + gitCommon *GitCommon, +) *StatusCommands { + return &StatusCommands{ + GitCommon: gitCommon, + } +} + +// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase +// and "interactive" for interactive rebase +func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) { + exists, err := self.os.FileExists(filepath.Join(self.dotGitDir, "rebase-apply")) + if err != nil { + return enums.REBASE_MODE_NONE, err + } + if exists { + return enums.REBASE_MODE_NORMAL, nil + } + exists, err = self.os.FileExists(filepath.Join(self.dotGitDir, "rebase-merge")) + if exists { + return enums.REBASE_MODE_INTERACTIVE, err + } else { + return enums.REBASE_MODE_NONE, err + } +} + +func (self *StatusCommands) WorkingTreeState() enums.RebaseMode { + rebaseMode, _ := self.RebaseMode() + if rebaseMode != enums.REBASE_MODE_NONE { + return enums.REBASE_MODE_REBASING + } + merging, _ := self.IsInMergeState() + if merging { + return enums.REBASE_MODE_MERGING + } + return enums.REBASE_MODE_NONE +} + +// IsInMergeState states whether we are still mid-merge +func (self *StatusCommands) IsInMergeState() (bool, error) { + return self.os.FileExists(filepath.Join(self.dotGitDir, "MERGE_HEAD")) +} + +func (self *StatusCommands) IsBareRepo() bool { + // note: could use `git rev-parse --is-bare-repository` if we wanna drop go-git + _, err := self.repo.Worktree() + return err == gogit.ErrIsBareRepository +} diff --git a/pkg/commands/git_commands/submodule.go b/pkg/commands/git_commands/submodule.go new file mode 100644 index 000000000..82744a338 --- /dev/null +++ b/pkg/commands/git_commands/submodule.go @@ -0,0 +1,181 @@ +package git_commands + +import ( + "bufio" + "fmt" + "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() ([]*models.SubmoduleConfig, error) { + file, err := os.Open(".gitmodules") + 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 + } else { + return "", false + } + } + + configs := []*models.SubmoduleConfig{} + for scanner.Scan() { + line := scanner.Text() + + if name, ok := firstMatch(line, `\[submodule "(.*)"\]`); ok { + configs = append(configs, &models.SubmoduleConfig{Name: name}) + continue + } + + if len(configs) > 0 { + lastConfig := configs[len(configs)-1] + + if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok { + lastConfig.Path = path + } else if url, ok := firstMatch(line, `\s*url\s*=\s*(.*)\s*`); ok { + lastConfig.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.Path) + return nil + } + + return self.cmd.New("git -C " + self.cmd.Quote(submodule.Path) + " stash --include-untracked").Run() +} + +func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error { + return self.cmd.New("git submodule update --init --force -- " + self.cmd.Quote(submodule.Path)).Run() +} + +func (self *SubmoduleCommands) UpdateAll() error { + // not doing an --init here because the user probably doesn't want that + return self.cmd.New("git submodule update --force").Run() +} + +func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { + // based on https://gist.github.com/myusuf3/7f645819ded92bda6677 + + if err := self.cmd.New("git submodule deinit --force -- " + self.cmd.Quote(submodule.Path)).Run(); err != nil { + if strings.Contains(err.Error(), "did not match any file(s) known to git") { + if err := self.cmd.New("git config --file .gitmodules --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil { + return err + } + + if err := self.cmd.New("git config --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil { + return err + } + + // if there's an error here about it not existing then we'll just continue to do `git rm` + } else { + return err + } + } + + if err := self.cmd.New("git rm --force -r " + submodule.Path).Run(); err != nil { + // if the directory isn't there then that's fine + self.Log.Error(err) + } + + return os.RemoveAll(filepath.Join(self.dotGitDir, "modules", submodule.Path)) +} + +func (self *SubmoduleCommands) Add(name string, path string, url string) error { + return self.cmd. + New( + fmt.Sprintf( + "git submodule add --force --name %s -- %s %s ", + self.cmd.Quote(name), + self.cmd.Quote(url), + self.cmd.Quote(path), + )). + Run() +} + +func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error { + // the set-url command is only for later git versions so we're doing it manually here + if err := self.cmd.New("git config --file .gitmodules submodule." + self.cmd.Quote(name) + ".url " + self.cmd.Quote(newUrl)).Run(); err != nil { + return err + } + + if err := self.cmd.New("git submodule sync -- " + self.cmd.Quote(path)).Run(); err != nil { + return err + } + + return nil +} + +func (self *SubmoduleCommands) Init(path string) error { + return self.cmd.New("git submodule init -- " + self.cmd.Quote(path)).Run() +} + +func (self *SubmoduleCommands) Update(path string) error { + return self.cmd.New("git submodule update --init -- " + self.cmd.Quote(path)).Run() +} + +func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj { + return self.cmd.New("git submodule init") +} + +func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj { + return self.cmd.New("git submodule update") +} + +func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj { + return self.cmd.New("git submodule update --force") +} + +func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj { + return self.cmd.New("git submodule deinit --all --force") +} + +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 new file mode 100644 index 000000000..8a6933522 --- /dev/null +++ b/pkg/commands/git_commands/sync.go @@ -0,0 +1,122 @@ +package git_commands + +import ( + "fmt" + + "github.com/go-errors/errors" + "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 + UpstreamRemote string + UpstreamBranch string + SetUpstream bool +} + +func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) { + cmdStr := "git push" + + if opts.Force { + cmdStr += " --force-with-lease" + } + + if opts.SetUpstream { + cmdStr += " --set-upstream" + } + + if opts.UpstreamRemote != "" { + cmdStr += " " + self.cmd.Quote(opts.UpstreamRemote) + } + + if opts.UpstreamBranch != "" { + if opts.UpstreamRemote == "" { + return nil, errors.New(self.Tr.MustSpecifyOriginError) + } + cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch) + } + + cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest() + return cmdObj, nil +} + +func (self *SyncCommands) Push(opts PushOpts) error { + cmdObj, err := self.PushCmdObj(opts) + if err != nil { + return err + } + + return cmdObj.Run() +} + +type FetchOptions struct { + Background bool + RemoteName string + BranchName string +} + +// Fetch fetch git repo +func (self *SyncCommands) Fetch(opts FetchOptions) error { + cmdStr := "git fetch" + + if opts.RemoteName != "" { + cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName)) + } + if opts.BranchName != "" { + cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName)) + } + + cmdObj := self.cmd.New(cmdStr) + if opts.Background { + cmdObj.DontLog().FailOnCredentialRequest() + } else { + cmdObj.PromptOnCredentialRequest() + } + return cmdObj.Run() +} + +type PullOptions struct { + RemoteName string + BranchName string + FastForwardOnly bool +} + +func (self *SyncCommands) Pull(opts PullOptions) error { + cmdStr := "git pull --no-edit" + + if opts.FastForwardOnly { + cmdStr += " --ff-only" + } + + if opts.RemoteName != "" { + cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName)) + } + if opts.BranchName != "" { + cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName)) + } + + // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user + // has 'pull.rebase = interactive' configured. + return self.cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().Run() +} + +func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error { + cmdStr := fmt.Sprintf("git fetch %s %s:%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName)) + return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run() +} + +func (self *SyncCommands) FetchRemote(remoteName string) error { + cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName)) + return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run() +} diff --git a/pkg/commands/git_commands/sync_test.go b/pkg/commands/git_commands/sync_test.go new file mode 100644 index 000000000..9c33381fe --- /dev/null +++ b/pkg/commands/git_commands/sync_test.go @@ -0,0 +1,94 @@ +package git_commands + +import ( + "testing" + + "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.ICmdObj, error) + } + + scenarios := []scenario{ + { + testName: "Push with force disabled", + opts: PushOpts{Force: false}, + test: func(cmdObj oscommands.ICmdObj, err error) { + assert.Equal(t, cmdObj.ToString(), "git push") + assert.NoError(t, err) + }, + }, + { + testName: "Push with force enabled", + opts: PushOpts{Force: true}, + test: func(cmdObj oscommands.ICmdObj, err error) { + assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease") + assert.NoError(t, err) + }, + }, + { + testName: "Push with force disabled, upstream supplied", + opts: PushOpts{ + Force: false, + UpstreamRemote: "origin", + UpstreamBranch: "master", + }, + test: func(cmdObj oscommands.ICmdObj, err error) { + assert.Equal(t, cmdObj.ToString(), `git push "origin" "master"`) + assert.NoError(t, err) + }, + }, + { + testName: "Push with force disabled, setting upstream", + opts: PushOpts{ + Force: false, + UpstreamRemote: "origin", + UpstreamBranch: "master", + SetUpstream: true, + }, + test: func(cmdObj oscommands.ICmdObj, err error) { + assert.Equal(t, cmdObj.ToString(), `git push --set-upstream "origin" "master"`) + assert.NoError(t, err) + }, + }, + { + testName: "Push with force enabled, setting upstream", + opts: PushOpts{ + Force: true, + UpstreamRemote: "origin", + UpstreamBranch: "master", + SetUpstream: true, + }, + test: func(cmdObj oscommands.ICmdObj, err error) { + assert.Equal(t, cmdObj.ToString(), `git push --force-with-lease --set-upstream "origin" "master"`) + assert.NoError(t, err) + }, + }, + { + testName: "Push with remote branch but no origin", + opts: PushOpts{ + Force: true, + UpstreamRemote: "", + UpstreamBranch: "master", + SetUpstream: true, + }, + test: func(cmdObj oscommands.ICmdObj, err error) { + assert.Error(t, err) + assert.EqualValues(t, "Must specify a remote if specifying a branch", err.Error()) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildSyncCommands(commonDeps{}) + s.test(instance.PushCmdObj(s.opts)) + }) + } +} diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go new file mode 100644 index 000000000..94b0d8ac1 --- /dev/null +++ b/pkg/commands/git_commands/tag.go @@ -0,0 +1,31 @@ +package git_commands + +import ( + "fmt" +) + +type TagCommands struct { + *GitCommon +} + +func NewTagCommands(gitCommon *GitCommon) *TagCommands { + return &TagCommands{ + GitCommon: gitCommon, + } +} + +func (self *TagCommands) CreateLightweight(tagName string, commitSha string) error { + return self.cmd.New(fmt.Sprintf("git tag -- %s %s", self.cmd.Quote(tagName), commitSha)).Run() +} + +func (self *TagCommands) CreateAnnotated(tagName, commitSha, msg string) error { + return self.cmd.New(fmt.Sprintf("git tag %s %s -m %s", tagName, commitSha, self.cmd.Quote(msg))).Run() +} + +func (self *TagCommands) Delete(tagName string) error { + return self.cmd.New(fmt.Sprintf("git tag -d %s", self.cmd.Quote(tagName))).Run() +} + +func (self *TagCommands) Push(remoteName string, tagName string) error { + return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().Run() +} diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go new file mode 100644 index 000000000..f594a639b --- /dev/null +++ b/pkg/commands/git_commands/working_tree.go @@ -0,0 +1,356 @@ +package git_commands + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/commands/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +type WorkingTreeCommands struct { + *GitCommon + submodule *SubmoduleCommands + fileLoader *loaders.FileLoader +} + +func NewWorkingTreeCommands( + gitCommon *GitCommon, + submodule *SubmoduleCommands, + fileLoader *loaders.FileLoader, +) *WorkingTreeCommands { + return &WorkingTreeCommands{ + GitCommon: gitCommon, + submodule: submodule, + fileLoader: fileLoader, + } +} + +func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj { + return self.cmd.New("git mergetool") +} + +func (self *WorkingTreeCommands) OpenMergeTool() error { + return self.OpenMergeToolCmdObj().Run() +} + +// StageFile stages a file +func (self *WorkingTreeCommands) StageFile(path string) error { + return self.StageFiles([]string{path}) +} + +func (self *WorkingTreeCommands) StageFiles(paths []string) error { + quotedPaths := make([]string, len(paths)) + for i, path := range paths { + quotedPaths[i] = self.cmd.Quote(path) + } + return self.cmd.New(fmt.Sprintf("git add -- %s", strings.Join(quotedPaths, " "))).Run() +} + +// StageAll stages all files +func (self *WorkingTreeCommands) StageAll() error { + return self.cmd.New("git add -A").Run() +} + +// UnstageAll unstages all files +func (self *WorkingTreeCommands) UnstageAll() error { + return self.cmd.New("git reset").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(fileNames []string, reset bool) error { + command := "git rm --cached --force -- %s" + if reset { + command = "git reset HEAD -- %s" + } + + for _, name := range fileNames { + err := self.cmd.New(fmt.Sprintf(command, self.cmd.Quote(name))).Run() + if err != nil { + return err + } + } + return nil +} + +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(loaders.GetStatusFileOptions{NoRenames: true}) + + var beforeFile *models.File + var afterFile *models.File + for _, f := range filesWithoutRenames { + if f.Name == file.PreviousName { + beforeFile = f + } + + if f.Name == file.Name { + 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 + } + + quotedFileName := self.cmd.Quote(file.Name) + + if file.ShortStatus == "AA" { + if err := self.cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil { + return err + } + if err := self.cmd.New("git add -- " + quotedFileName).Run(); err != nil { + return err + } + return nil + } + + if file.ShortStatus == "DU" { + return self.cmd.New("git rm -- " + quotedFileName).Run() + } + + // if the file isn't tracked, we assume you want to delete it + if file.HasStagedChanges || file.HasMergeConflicts { + if err := self.cmd.New("git reset -- " + quotedFileName).Run(); err != nil { + return err + } + } + + if file.ShortStatus == "DD" || file.ShortStatus == "AU" { + return nil + } + + if file.Added { + return self.os.RemoveFile(file.Name) + } + return self.DiscardUnstagedFileChanges(file) +} + +type IFileNode interface { + ForEachFile(cb func(*models.File) error) error + GetFilePathsMatching(test func(*models.File) bool) []string + GetPath() string +} + +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 { + if err := self.RemoveUntrackedDirFiles(node); err != nil { + return err + } + + quotedPath := self.cmd.Quote(node.GetPath()) + if err := self.cmd.New("git checkout -- " + quotedPath).Run(); 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 +} + +// DiscardUnstagedFileChanges directly +func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error { + quotedFileName := self.cmd.Quote(file.Name) + return self.cmd.New("git checkout -- " + quotedFileName).Run() +} + +// Ignore adds a file to the gitignore for the repo +func (self *WorkingTreeCommands) Ignore(filename string) error { + return self.os.AppendLineToFile(".gitignore", filename) +} + +// WorktreeFileDiff returns the diff of a file +func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string { + // for now we assume an error means the file was deleted + s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput() + return s +} + +func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj { + cachedArg := "" + trackedArg := "--" + colorArg := self.UserConfig.Git.Paging.ColorArg + quotedPath := self.cmd.Quote(node.GetPath()) + ignoreWhitespaceArg := "" + contextSize := self.UserConfig.Git.DiffContextSize + if cached { + cachedArg = " --cached" + } + if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached { + trackedArg = "--no-index -- /dev/null" + } + if plain { + colorArg = "never" + } + if ignoreWhitespace { + ignoreWhitespaceArg = " --ignore-all-space" + } + + cmdStr := fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s%s%s %s %s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath) + + return self.cmd.New(cmdStr).DontLog() +} + +func (self *WorkingTreeCommands) ApplyPatch(patch string, flags ...string) error { + filepath := filepath.Join(oscommands.GetTempDir(), utils.GetCurrentRepoName(), 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 + } + + flagStr := "" + for _, flag := range flags { + flagStr += " --" + flag + } + + return self.cmd.New(fmt.Sprintf("git apply%s %s", flagStr, self.cmd.Quote(filepath))).Run() +} + +// 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.ICmdObj { + colorArg := self.UserConfig.Git.Paging.ColorArg + contextSize := self.UserConfig.Git.DiffContextSize + if plain { + colorArg = "never" + } + + reverseFlag := "" + if reverse { + reverseFlag = " -R" + } + + return self.cmd. + New( + fmt.Sprintf( + "git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s%s%s%s -- %s", + contextSize, colorArg, pad(from), pad(to), reverseFlag, self.cmd.Quote(fileName)), + ). + DontLog() +} + +// CheckoutFile checks out the file for the given commit +func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error { + return self.cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, self.cmd.Quote(fileName))).Run() +} + +// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .` +func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error { + return self.cmd.New("git checkout -- .").Run() +} + +// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked +func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error { + return self.cmd.New("git rm -r --cached -- " + self.cmd.Quote(name)).Run() +} + +// RemoveUntrackedFiles runs `git clean -fd` +func (self *WorkingTreeCommands) RemoveUntrackedFiles() error { + return self.cmd.New("git clean -fd").Run() +} + +// ResetAndClean removes all unstaged changes and removes all untracked files +func (self *WorkingTreeCommands) ResetAndClean() error { + submoduleConfigs, err := self.submodule.GetConfigs() + 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() +} + +// ResetHardHead runs `git reset --hard` +func (self *WorkingTreeCommands) ResetHard(ref string) error { + return self.cmd.New("git reset --hard " + self.cmd.Quote(ref)).Run() +} + +// ResetSoft runs `git reset --soft HEAD` +func (self *WorkingTreeCommands) ResetSoft(ref string) error { + return self.cmd.New("git reset --soft " + self.cmd.Quote(ref)).Run() +} + +func (self *WorkingTreeCommands) ResetMixed(ref string) error { + return self.cmd.New("git reset --mixed " + self.cmd.Quote(ref)).Run() +} + +// so that we don't have unnecessary space in our commands we use this helper function to prepend spaces to args so that in the format string we can go '%s%s%s' and if any args are missing we won't have gaps. +func pad(str string) string { + if str == "" { + return "" + } + + return " " + str +} diff --git a/pkg/commands/git_commands/working_tree_test.go b/pkg/commands/git_commands/working_tree_test.go new file mode 100644 index 000000000..e4e884ce4 --- /dev/null +++ b/pkg/commands/git_commands/working_tree_test.go @@ -0,0 +1,586 @@ +package git_commands + +import ( + "fmt" + "io/ioutil" + "regexp" + "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). + Expect(`git 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). + Expect(`git add -- "test.txt" "test2.txt"`, "", nil) + + instance := buildWorkingTreeCommands(commonDeps{runner: runner}) + + assert.NoError(t, instance.StageFiles([]string{"test.txt", "test2.txt"})) + 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). + Expect(`git 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). + Expect(`git reset HEAD -- "test.txt"`, "", nil), + test: func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + 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{ + Name: "test", + HasStagedChanges: true, + }, + removeFile: func(string) error { return nil }, + runner: oscommands.NewFakeRunner(t). + Expect(`git reset -- "test"`, "", errors.New("error")), + expectedError: "error", + }, + { + testName: "An error occurred when removing file", + file: &models.File{ + Name: "test", + Tracked: false, + Added: true, + }, + removeFile: func(string) error { + return fmt.Errorf("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{ + Name: "test", + Tracked: true, + HasStagedChanges: false, + }, + removeFile: func(string) error { return nil }, + runner: oscommands.NewFakeRunner(t). + Expect(`git checkout -- "test"`, "", errors.New("error")), + expectedError: "error", + }, + { + testName: "Checkout only", + file: &models.File{ + Name: "test", + Tracked: true, + HasStagedChanges: false, + }, + removeFile: func(string) error { return nil }, + runner: oscommands.NewFakeRunner(t). + Expect(`git checkout -- "test"`, "", nil), + expectedError: "", + }, + { + testName: "Reset and checkout staged changes", + file: &models.File{ + Name: "test", + Tracked: true, + HasStagedChanges: true, + }, + removeFile: func(string) error { return nil }, + runner: oscommands.NewFakeRunner(t). + Expect(`git reset -- "test"`, "", nil). + Expect(`git checkout -- "test"`, "", nil), + expectedError: "", + }, + { + testName: "Reset and checkout merge conflicts", + file: &models.File{ + Name: "test", + Tracked: true, + HasMergeConflicts: true, + }, + removeFile: func(string) error { return nil }, + runner: oscommands.NewFakeRunner(t). + Expect(`git reset -- "test"`, "", nil). + Expect(`git checkout -- "test"`, "", nil), + expectedError: "", + }, + { + testName: "Reset and remove", + file: &models.File{ + Name: "test", + Tracked: false, + Added: true, + HasStagedChanges: true, + }, + removeFile: func(filename string) error { + assert.Equal(t, "test", filename) + return nil + }, + runner: oscommands.NewFakeRunner(t). + Expect(`git reset -- "test"`, "", nil), + expectedError: "", + }, + { + testName: "Remove only", + file: &models.File{ + Name: "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 { + s := s + 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 int + runner *oscommands.FakeCmdObjRunner + } + + const expectedResult = "pretend this is an actual git diff" + + scenarios := []scenario{ + { + testName: "Default case", + file: &models.File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: true, + }, + plain: false, + cached: false, + ignoreWhitespace: false, + contextSize: 3, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always -- "test.txt"`, expectedResult, nil), + }, + { + testName: "cached", + file: &models.File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: true, + }, + plain: false, + cached: true, + ignoreWhitespace: false, + contextSize: 3, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always --cached -- "test.txt"`, expectedResult, nil), + }, + { + testName: "plain", + file: &models.File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: true, + }, + plain: true, + cached: false, + ignoreWhitespace: false, + contextSize: 3, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --unified=3 --color=never -- "test.txt"`, expectedResult, nil), + }, + { + testName: "File not tracked and file has no staged changes", + file: &models.File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: false, + }, + plain: false, + cached: false, + ignoreWhitespace: false, + contextSize: 3, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always --no-index -- /dev/null "test.txt"`, expectedResult, nil), + }, + { + testName: "Default case (ignore whitespace)", + file: &models.File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: true, + }, + plain: false, + cached: false, + ignoreWhitespace: true, + contextSize: 3, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always --ignore-all-space -- "test.txt"`, expectedResult, nil), + }, + { + testName: "Show diff with custom context size", + file: &models.File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: true, + }, + plain: false, + cached: false, + ignoreWhitespace: false, + contextSize: 17, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --unified=17 --color=always -- "test.txt"`, expectedResult, nil), + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + userConfig := config.GetDefaultConfig() + userConfig.Git.DiffContextSize = s.contextSize + + instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig}) + result := instance.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace) + 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 + contextSize int + 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, + contextSize: 3, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --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, + contextSize: 123, + runner: oscommands.NewFakeRunner(t). + Expect(`git diff --submodule --no-ext-diff --unified=123 --no-renames --color=always 1234567890 0987654321 -- "test.txt"`, expectedResult, nil), + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + userConfig := config.GetDefaultConfig() + userConfig.Git.DiffContextSize = s.contextSize + + instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig}) + + 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 + commitSha string + fileName string + runner *oscommands.FakeCmdObjRunner + test func(error) + } + + scenarios := []scenario{ + { + testName: "typical case", + commitSha: "11af912", + fileName: "test999.txt", + runner: oscommands.NewFakeRunner(t). + Expect(`git checkout 11af912 -- "test999.txt"`, "", nil), + test: func(err error) { + assert.NoError(t, err) + }, + }, + { + testName: "returns error if there is one", + commitSha: "11af912", + fileName: "test999.txt", + runner: oscommands.NewFakeRunner(t). + Expect(`git checkout 11af912 -- "test999.txt"`, "", errors.New("error")), + test: func(err error) { + assert.Error(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) + + s.test(instance.CheckoutFile(s.commitSha, s.fileName)) + s.runner.CheckForMissingCalls() + }) + } +} + +func TestWorkingTreeApplyPatch(t *testing.T) { + type scenario struct { + testName string + runner *oscommands.FakeCmdObjRunner + test func(error) + } + + expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) { + return func(cmdObj oscommands.ICmdObj) (string, error) { + re := regexp.MustCompile(regexStr) + cmdStr := cmdObj.ToString() + matches := re.FindStringSubmatch(cmdStr) + assert.Equal(t, 2, len(matches), fmt.Sprintf("unexpected command: %s", cmdStr)) + + filename := matches[1] + + content, err := ioutil.ReadFile(filename) + assert.NoError(t, err) + + assert.Equal(t, "test", string(content)) + + return "", errToReturn + } + } + + scenarios := []scenario{ + { + testName: "valid case", + runner: oscommands.NewFakeRunner(t). + ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)), + test: func(err error) { + assert.NoError(t, err) + }, + }, + { + testName: "command returns error", + runner: oscommands.NewFakeRunner(t). + ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))), + test: func(err error) { + assert.Error(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) + s.test(instance.ApplyPatch("test", "cached")) + 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{Name: "test.txt"}, + runner: oscommands.NewFakeRunner(t). + Expect(`git checkout -- "test.txt"`, "", nil), + test: func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + 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). + Expect(`git checkout -- .`, "", nil), + test: func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + 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). + Expect(`git clean -fd`, "", nil), + test: func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + 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). + Expect(`git reset --hard "HEAD"`, "", nil), + func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s := s + 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_config/cached_git_config.go b/pkg/commands/git_config/cached_git_config.go index e2b83bad8..fe3bc1eca 100644 --- a/pkg/commands/git_config/cached_git_config.go +++ b/pkg/commands/git_config/cached_git_config.go @@ -1,31 +1,36 @@ package git_config import ( + "os/exec" "strings" "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 } type CachedGitConfig struct { - cache map[string]string - getKey func(string) (string, error) - log *logrus.Entry + cache map[string]string + runGitConfigCmd func(*exec.Cmd) (string, error) + log *logrus.Entry } func NewStdCachedGitConfig(log *logrus.Entry) *CachedGitConfig { - return NewCachedGitConfig(getGitConfigValue, log) + return NewCachedGitConfig(runGitConfigCmd, log) } -func NewCachedGitConfig(getKey func(string) (string, error), log *logrus.Entry) *CachedGitConfig { +func NewCachedGitConfig(runGitConfigCmd func(*exec.Cmd) (string, error), log *logrus.Entry) *CachedGitConfig { return &CachedGitConfig{ - cache: make(map[string]string), - getKey: getKey, - log: log, + cache: make(map[string]string), + runGitConfigCmd: runGitConfigCmd, + log: log, } } @@ -40,8 +45,30 @@ func (self *CachedGitConfig) Get(key string) string { return value } +func (self *CachedGitConfig) GetGeneral(args string) string { + if value, ok := self.cache[args]; ok { + self.log.Debugf("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: " + args + ". Error: " + err.Error()) + return "" + } + return strings.TrimSpace(value) +} + func (self *CachedGitConfig) getAux(key string) string { - value, err := self.getKey(key) + cmd := getGitConfigCmd(key) + value, err := self.runGitConfigCmd(cmd) if err != nil { self.log.Debugf("Error getting git config value for key: " + key + ". Error: " + err.Error()) return "" diff --git a/pkg/commands/git_config/cached_git_config_test.go b/pkg/commands/git_config/cached_git_config_test.go index f03535a6e..6ba2c5b91 100644 --- a/pkg/commands/git_config/cached_git_config_test.go +++ b/pkg/commands/git_config/cached_git_config_test.go @@ -1,6 +1,8 @@ package git_config import ( + "os/exec" + "strings" "testing" "github.com/jesseduffield/lazygit/pkg/utils" @@ -52,8 +54,9 @@ func TestGetBool(t *testing.T) { t.Run(s.testName, func(t *testing.T) { fake := NewFakeGitConfig(s.mockResponses) real := NewCachedGitConfig( - func(key string) (string, error) { - return fake.Get(key), nil + 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(), ) @@ -88,8 +91,9 @@ func TestGet(t *testing.T) { t.Run(s.testName, func(t *testing.T) { fake := NewFakeGitConfig(s.mockResponses) real := NewCachedGitConfig( - func(key string) (string, error) { - return fake.Get(key), nil + 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(), ) @@ -101,9 +105,9 @@ func TestGet(t *testing.T) { // verifying that the cache is used count := 0 real := NewCachedGitConfig( - func(key string) (string, error) { + func(cmd *exec.Cmd) (string, error) { count++ - assert.Equal(t, "commit.gpgsign", key) + assert.Equal(t, "config --get --null commit.gpgsign", strings.Join(cmd.Args[1:], " ")) return "blah", nil }, utils.NewDummyLog(), diff --git a/pkg/commands/git_config/fake_git_config.go b/pkg/commands/git_config/fake_git_config.go index f010efd8c..78ba62c54 100644 --- a/pkg/commands/git_config/fake_git_config.go +++ b/pkg/commands/git_config/fake_git_config.go @@ -17,6 +17,13 @@ func (self *FakeGitConfig) Get(key string) string { 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)) } diff --git a/pkg/commands/git_config/get_key.go b/pkg/commands/git_config/get_key.go index 85892ea2d..bd6f59248 100644 --- a/pkg/commands/git_config/get_key.go +++ b/pkg/commands/git_config/get_key.go @@ -35,10 +35,8 @@ import ( // 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 getGitConfigValue(key string) (string, error) { - gitArgs := []string{"config", "--get", "--null", key} +func runGitConfigCmd(cmd *exec.Cmd) (string, error) { var stdout bytes.Buffer - cmd := secureexec.Command("git", gitArgs...) cmd.Stdout = &stdout cmd.Stderr = ioutil.Discard @@ -46,7 +44,7 @@ func getGitConfigValue(key string) (string, error) { if exitError, ok := err.(*exec.ExitError); ok { if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok { if waitStatus.ExitStatus() == 1 { - return "", fmt.Errorf("the key `%s` is not found", key) + return "", fmt.Errorf("the key is not found for %s", cmd.Args) } } return "", err @@ -54,3 +52,13 @@ func getGitConfigValue(key string) (string, error) { return strings.TrimRight(stdout.String(), "\000"), nil } + +func getGitConfigCmd(key string) *exec.Cmd { + gitArgs := []string{"config", "--get", "--null", key} + return secureexec.Command("git", gitArgs...) +} + +func getGitConfigGeneralCmd(args string) *exec.Cmd { + gitArgs := append([]string{"config"}, strings.Split(args, " ")...) + return secureexec.Command("git", gitArgs...) +} diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 19eabd583..684696a8c 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -10,8 +10,6 @@ import ( 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/config" - "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/stretchr/testify/assert" ) @@ -106,6 +104,7 @@ func TestNavigateToRepoRootDirectory(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { s.test(navigateToRepoRootDirectory(s.stat, s.chdir)) }) @@ -161,6 +160,7 @@ func TestSetupRepository(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { s.test(setupRepository(s.openGitRepository, s.errorStr)) }) @@ -208,10 +208,10 @@ func TestNewGitCommand(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { s.setup() - newAppConfig := config.NewDummyAppConfig() - s.test(NewGitCommand(utils.NewDummyLog(), oscommands.NewDummyOSCommand(), i18n.NewTranslationSet(utils.NewDummyLog(), newAppConfig.GetUserConfig().Gui.Language), newAppConfig, git_config.NewFakeGitConfig(nil))) + s.test(NewGitCommand(utils.NewDummyCommon(), oscommands.NewDummyOSCommand(), git_config.NewFakeGitConfig(nil))) }) } } @@ -285,6 +285,7 @@ func TestFindDotGitDir(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { s.test(findDotGitDir(s.stat, s.readFile)) }) diff --git a/pkg/commands/hosting_service/definitions.go b/pkg/commands/hosting_service/definitions.go new file mode 100644 index 000000000..c70062a67 --- /dev/null +++ b/pkg/commands/hosting_service/definitions.go @@ -0,0 +1,54 @@ +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)?$`, + `^git@.*:(?P.*)/(?P.*?)(?:\.git)?$`, +} + +// 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/{{.CommitSha}}", + regexStrings: defaultUrlRegexStrings, +} + +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/{{.CommitSha}}", + regexStrings: defaultUrlRegexStrings, +} + +var gitLabServiceDef = ServiceDefinition{ + provider: "gitlab", + pullRequestURLIntoDefaultBranch: "/merge_requests/new?merge_request[source_branch]={{.From}}", + pullRequestURLIntoTargetBranch: "/merge_requests/new?merge_request[source_branch]={{.From}}&merge_request[target_branch]={{.To}}", + commitURL: "/commit/{{.CommitSha}}", + regexStrings: defaultUrlRegexStrings, +} + +var serviceDefinitions = []ServiceDefinition{githubServiceDef, bitbucketServiceDef, gitLabServiceDef} + +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", + }, +} diff --git a/pkg/commands/hosting_service/hosting_service.go b/pkg/commands/hosting_service/hosting_service.go new file mode 100644 index 000000000..01e07e9eb --- /dev/null +++ b/pkg/commands/hosting_service/hosting_service.go @@ -0,0 +1,200 @@ +package hosting_service + +import ( + "fmt" + "net/url" + "regexp" + "strings" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/sirupsen/logrus" +) + +// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, 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 + } else { + return gitService.getPullRequestURLIntoTargetBranch(url.QueryEscape(from), url.QueryEscape(to)), nil + } +} + +func (self *HostingServiceMgr) GetCommitURL(commitSha string) (string, error) { + gitService, err := self.getService() + if err != nil { + return "", err + } + + pullRequestURL := gitService.getCommitURL(commitSha) + + return pullRequestURL, nil +} + +func (self *HostingServiceMgr) getService() (*Service, error) { + serviceDomain, err := self.getServiceDomain(self.remoteURL) + if err != nil { + return nil, err + } + + root, err := serviceDomain.getRootFromRemoteURL(self.remoteURL) + if err != nil { + return nil, err + } + + return &Service{ + root: root, + 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 + } + + var serviceDomains = make([]ServiceDomain, len(defaultServiceDomains)) + copy(serviceDomains, defaultServiceDomains) + + if len(self.configServiceDomains) > 0 { + for gitDomain, typeAndDomain := range self.configServiceDomains { + splitData := strings.Split(typeAndDomain, ":") + if len(splitData) != 2 { + self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain) + continue + } + + provider := splitData[0] + webDomain := splitData[1] + + serviceDefinition, ok := serviceDefinitionByProvider[provider] + if !ok { + providerNames := []string{} + for _, serviceDefinition := range serviceDefinitions { + providerNames = append(providerNames, 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 +} + +func (self ServiceDomain) getRootFromRemoteURL(repoURL string) (string, error) { + // we may want to make this more specific to the service in future e.g. if + // some new service comes along which has a different root url structure. + repoInfo, err := self.serviceDefinition.getRepoInfoFromURL(repoURL) + if err != nil { + return "", err + } + return fmt.Sprintf("https://%s/%s/%s", self.webDomain, repoInfo.Owner, repoInfo.Repository), nil +} + +// RepoInformation holds some basic information about the repo +type RepoInformation struct { + Owner string + Repository string +} + +type ServiceDefinition struct { + provider string + pullRequestURLIntoDefaultBranch string + pullRequestURLIntoTargetBranch string + commitURL string + regexStrings []string +} + +func (self ServiceDefinition) getRepoInfoFromURL(url string) (*RepoInformation, error) { + for _, regexStr := range self.regexStrings { + re := regexp.MustCompile(regexStr) + matches := utils.FindNamedMatches(re, url) + if matches != nil { + return &RepoInformation{ + Owner: matches["owner"], + Repository: matches["repo"], + }, nil + } + } + + return nil, errors.New("Failed to parse repo information from url") +} + +type Service struct { + root 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(commitSha string) string { + return self.resolveUrl(self.commitURL, map[string]string{"CommitSha": commitSha}) +} + +func (self *Service) resolveUrl(templateString string, args map[string]string) string { + return self.root + utils.ResolvePlaceholderString(templateString, args) +} diff --git a/pkg/commands/hosting_service/hosting_service_test.go b/pkg/commands/hosting_service/hosting_service_test.go new file mode 100644 index 000000000..98c097a33 --- /dev/null +++ b/pkg/commands/hosting_service/hosting_service_test.go @@ -0,0 +1,255 @@ +package hosting_service + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/jesseduffield/lazygit/pkg/test" + "github.com/stretchr/testify/assert" +) + +func TestGetRepoInfoFromURL(t *testing.T) { + type scenario struct { + serviceDefinition ServiceDefinition + testName string + repoURL string + test func(*RepoInformation) + } + + scenarios := []scenario{ + { + githubServiceDef, + "Returns repository information for git remote url", + "git@github.com:petersmith/super_calculator", + func(repoInfo *RepoInformation) { + assert.EqualValues(t, repoInfo.Owner, "petersmith") + assert.EqualValues(t, repoInfo.Repository, "super_calculator") + }, + }, + { + githubServiceDef, + "Returns repository information for git remote url, trimming trailing '.git'", + "git@github.com:petersmith/super_calculator.git", + func(repoInfo *RepoInformation) { + assert.EqualValues(t, repoInfo.Owner, "petersmith") + assert.EqualValues(t, repoInfo.Repository, "super_calculator") + }, + }, + { + githubServiceDef, + "Returns repository information for ssh remote url", + "ssh://git@github.com/petersmith/super_calculator", + func(repoInfo *RepoInformation) { + assert.EqualValues(t, repoInfo.Owner, "petersmith") + assert.EqualValues(t, repoInfo.Repository, "super_calculator") + }, + }, + { + githubServiceDef, + "Returns repository information for http remote url", + "https://my_username@bitbucket.org/johndoe/social_network.git", + func(repoInfo *RepoInformation) { + assert.EqualValues(t, repoInfo.Owner, "johndoe") + assert.EqualValues(t, repoInfo.Repository, "social_network") + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + result, err := s.serviceDefinition.getRepoInfoFromURL(s.repoURL) + assert.NoError(t, err) + s.test(result) + }) + } +} + +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 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 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[source_branch]=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[source_branch]=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[source_branch]=feature%2Fcommit-ui&merge_request[target_branch]=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[source_branch]=feature%2Fcommit-ui&merge_request[target_branch]=epic%2Fui", 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: "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"}, + }, + { + 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[source_branch]=feature%2FsomeIssue%23123&merge_request[target_branch]=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[source_branch]=yolo&merge_request[target_branch]=archive%2Fnever-ending-feature%23666", url) + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + tr := i18n.EnglishTranslationSet() + log := &test.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/loading_branches.go b/pkg/commands/loaders/branches.go similarity index 62% rename from pkg/commands/loading_branches.go rename to pkg/commands/loaders/branches.go index 09db0de42..c54e65ee9 100644 --- a/pkg/commands/loading_branches.go +++ b/pkg/commands/loaders/branches.go @@ -1,12 +1,13 @@ -package commands +package loaders import ( "regexp" "strings" + "github.com/jesseduffield/go-git/v5/config" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sirupsen/logrus" ) // context: @@ -20,89 +21,37 @@ import ( // 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 -// BranchListBuilder returns a list of Branch objects for the current repo -type BranchListBuilder struct { - Log *logrus.Entry - GitCommand *GitCommand - ReflogCommits []*models.Commit +type BranchLoaderConfigCommands interface { + Branches() (map[string]*config.Branch, error) } -// NewBranchListBuilder builds a new branch list builder -func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand, reflogCommits []*models.Commit) (*BranchListBuilder, error) { - return &BranchListBuilder{ - Log: log, - GitCommand: gitCommand, - ReflogCommits: reflogCommits, - }, nil +// BranchLoader returns a list of Branch objects for the current repo +type BranchLoader struct { + *common.Common + getRawBranches func() (string, error) + getCurrentBranchName func() (string, string, error) + config BranchLoaderConfigCommands } -func (b *BranchListBuilder) obtainBranches() []*models.Branch { - cmdStr := `git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads` - output, err := b.GitCommand.OSCommand.RunCommandWithOutput(cmdStr) - if err != nil { - panic(err) +func NewBranchLoader( + cmn *common.Common, + getRawBranches func() (string, error), + getCurrentBranchName func() (string, string, error), + config BranchLoaderConfigCommands, +) *BranchLoader { + return &BranchLoader{ + Common: cmn, + getRawBranches: getRawBranches, + getCurrentBranchName: getCurrentBranchName, + config: config, } - - trimmedOutput := strings.TrimSpace(output) - outputLines := strings.Split(trimmedOutput, "\n") - branches := make([]*models.Branch, 0, len(outputLines)) - for _, line := range outputLines { - if line == "" { - continue - } - - split := strings.Split(line, SEPARATION_CHAR) - if len(split) != 4 { - // Ignore line if it isn't separated into 4 parts - // This is probably a warning message, for more info see: - // https://github.com/jesseduffield/lazygit/issues/1385#issuecomment-885580439 - continue - } - - name := strings.TrimPrefix(split[1], "heads/") - branch := &models.Branch{ - Name: name, - Pullables: "?", - Pushables: "?", - Head: split[0] == "*", - } - - upstreamName := split[2] - if upstreamName == "" { - branches = append(branches, branch) - continue - } - - branch.UpstreamName = upstreamName - - track := split[3] - re := regexp.MustCompile(`ahead (\d+)`) - match := re.FindStringSubmatch(track) - if len(match) > 1 { - branch.Pushables = match[1] - } else { - branch.Pushables = "0" - } - - re = regexp.MustCompile(`behind (\d+)`) - match = re.FindStringSubmatch(track) - if len(match) > 1 { - branch.Pullables = match[1] - } else { - branch.Pullables = "0" - } - - branches = append(branches, branch) - } - - return branches } -// Build the list of branches for the current repo -func (b *BranchListBuilder) Build() []*models.Branch { - branches := b.obtainBranches() +// Load the list of branches for the current repo +func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) { + branches := self.obtainBranches() - reflogBranches := b.obtainReflogBranches() + 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) @@ -134,16 +83,86 @@ outer: } } if !foundHead { - currentBranchName, currentBranchDisplayName, err := b.GitCommand.CurrentBranchName() + currentBranchName, currentBranchDisplayName, err := self.getCurrentBranchName() if err != nil { - panic(err) + return nil, err } - branches = append([]*models.Branch{{ - Name: currentBranchName, - DisplayName: currentBranchDisplayName, - Head: true, - Recency: " *", - }}, branches...) + branches = append([]*models.Branch{{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"}}, branches...) + } + + 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() + } + } + + return branches, 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") + branches := make([]*models.Branch, 0, len(outputLines)) + for _, line := range outputLines { + if line == "" { + continue + } + + split := strings.Split(line, SEPARATION_CHAR) + if len(split) != 4 { + // Ignore line if it isn't separated into 4 parts + // This is probably a warning message, for more info see: + // https://github.com/jesseduffield/lazygit/issues/1385#issuecomment-885580439 + continue + } + + name := strings.TrimPrefix(split[1], "heads/") + branch := &models.Branch{ + Name: name, + Pullables: "?", + Pushables: "?", + Head: split[0] == "*", + } + + upstreamName := split[2] + 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 + branches = append(branches, branch) + continue + } + + track := split[3] + re := regexp.MustCompile(`ahead (\d+)`) + match := re.FindStringSubmatch(track) + if len(match) > 1 { + branch.Pushables = match[1] + } else { + branch.Pushables = "0" + } + + re = regexp.MustCompile(`behind (\d+)`) + match = re.FindStringSubmatch(track) + if len(match) > 1 { + branch.Pullables = match[1] + } else { + branch.Pullables = "0" + } + + branches = append(branches, branch) } return branches @@ -151,11 +170,11 @@ outer: // 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 (b *BranchListBuilder) obtainReflogBranches() []*models.Branch { +func (self *BranchLoader) obtainReflogBranches(reflogCommits []*models.Commit) []*models.Branch { foundBranchesMap := map[string]bool{} re := regexp.MustCompile(`checkout: moving from ([\S]+) to ([\S]+)`) - reflogBranches := make([]*models.Branch, 0, len(b.ReflogCommits)) - for _, commit := range b.ReflogCommits { + reflogBranches := make([]*models.Branch, 0, len(reflogCommits)) + for _, commit := range reflogCommits { if match := re.FindStringSubmatch(commit.Name); len(match) == 3 { recency := utils.UnixToTimeAgo(commit.UnixTimestamp) for _, branchName := range match[1:] { diff --git a/pkg/commands/loaders/commit_files.go b/pkg/commands/loaders/commit_files.go new file mode 100644 index 000000000..755db768d --- /dev/null +++ b/pkg/commands/loaders/commit_files.go @@ -0,0 +1,57 @@ +package loaders + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/common" +) + +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) { + reverseFlag := "" + if reverse { + reverseFlag = " -R " + } + + filenames, err := self.cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).DontLog().RunWithOutput() + if err != nil { + return nil, err + } + + return self.getCommitFilesFromFilenames(filenames), nil +} + +// filenames string is something like "file1\nfile2\nfile3" +func (self *CommitFileLoader) getCommitFilesFromFilenames(filenames string) []*models.CommitFile { + commitFiles := make([]*models.CommitFile, 0) + + lines := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00") + n := len(lines) + for i := 0; i < n-1; i += 2 { + // typical result looks like 'A my_file' meaning my_file was added + changeStatus := lines[i] + name := lines[i+1] + + commitFiles = append(commitFiles, &models.CommitFile{ + Name: name, + ChangeStatus: changeStatus, + }) + } + + return commitFiles +} diff --git a/pkg/commands/loading_commits.go b/pkg/commands/loaders/commits.go similarity index 65% rename from pkg/commands/loading_commits.go rename to pkg/commands/loaders/commits.go index 204e4c9da..ea54a4e76 100644 --- a/pkg/commands/loading_commits.go +++ b/pkg/commands/loaders/commits.go @@ -1,10 +1,9 @@ -package commands +package loaders import ( "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" "regexp" "strconv" @@ -12,9 +11,9 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" + "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/sirupsen/logrus" ) // context: @@ -23,39 +22,142 @@ import ( // 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 -// 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 - const SEPARATION_CHAR = "|" -// CommitListBuilder returns a list of Branch objects for the current repo -type CommitListBuilder struct { - Log *logrus.Entry - GitCommand *GitCommand - OSCommand *oscommands.OSCommand - Tr *i18n.TranslationSet +// CommitLoader returns a list of Commit objects for the current repo +type CommitLoader struct { + *common.Common + cmd oscommands.ICmdObjBuilder + + getCurrentBranchName func() (string, string, error) + getRebaseMode func() (enums.RebaseMode, error) + readFile func(filename string) ([]byte, error) + walkFiles func(root string, fn filepath.WalkFunc) error + dotGitDir string } -// NewCommitListBuilder builds a new commit list builder -func NewCommitListBuilder( - log *logrus.Entry, - gitCommand *GitCommand, - osCommand *oscommands.OSCommand, - tr *i18n.TranslationSet, -) *CommitListBuilder { - return &CommitListBuilder{ - Log: log, - GitCommand: gitCommand, - OSCommand: osCommand, - Tr: tr, +// making our dependencies explicit for the sake of easier testing +func NewCommitLoader( + cmn *common.Common, + cmd oscommands.ICmdObjBuilder, + dotGitDir string, + getCurrentBranchName func() (string, string, error), + getRebaseMode func() (enums.RebaseMode, error), +) *CommitLoader { + return &CommitLoader{ + Common: cmn, + cmd: cmd, + getCurrentBranchName: getCurrentBranchName, + getRebaseMode: getRebaseMode, + readFile: ioutil.ReadFile, + walkFiles: filepath.Walk, + dotGitDir: dotGitDir, } } +type GetCommitsOptions struct { + Limit bool + FilterPath string + IncludeRebaseCommits bool + RefName string // e.g. "HEAD" or "my_branch" + // determines if we show the whole git graph i.e. pass the '--all' flag + All bool +} + +// GetCommits obtains the commits of the current branch +func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) { + commits := []*models.Commit{} + var rebasingCommits []*models.Commit + rebaseMode, err := self.getRebaseMode() + if err != nil { + return nil, err + } + + if opts.IncludeRebaseCommits && opts.FilterPath == "" { + var err error + rebasingCommits, err = self.MergeRebasingCommits(commits) + if err != nil { + return nil, err + } + commits = append(commits, rebasingCommits...) + } + + passedFirstPushedCommit := false + firstPushedCommit, err := self.getFirstPushedCommit(opts.RefName) + if err != nil { + // must have no upstream branch so we'll consider everything as pushed + passedFirstPushedCommit = true + } + + err = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) { + if canExtractCommit(line) { + commit := self.extractCommitFromLine(line) + if commit.Sha == firstPushedCommit { + passedFirstPushedCommit = true + } + commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[!passedFirstPushedCommit] + commits = append(commits, commit) + } + return false, nil + }) + if err != nil { + return nil, err + } + + if len(commits) == 0 { + return commits, nil + } + + if rebaseMode != enums.REBASE_MODE_NONE { + currentCommit := commits[len(rebasingCommits)] + youAreHere := style.FgYellow.Sprintf("<-- %s ---", self.Tr.YouAreHere) + currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name) + } + + commits, err = self.setCommitMergedStatuses(opts.RefName, commits) + if err != nil { + return nil, err + } + + return commits, nil +} + +func (self *CommitLoader) MergeRebasingCommits(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.Status != "rebasing" { // removing the existing rebase commits so we can add the refreshed ones + result = append(result, commits[i:]...) + break + } + } + + rebaseMode, err := self.getRebaseMode() + if err != nil { + return nil, err + } + + if rebaseMode == enums.REBASE_MODE_NONE { + // not in rebase mode so return original commits + return result, nil + } + + rebasingCommits, err := self.getHydratedRebasingCommits(rebaseMode) + 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 sha, 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 (c *CommitListBuilder) extractCommitFromLine(line string) *models.Commit { +func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit { split := strings.Split(line, SEPARATION_CHAR) sha := split[0] @@ -88,104 +190,8 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *models.Commit { } } -type GetCommitsOptions struct { - Limit bool - FilterPath string - IncludeRebaseCommits bool - RefName string // e.g. "HEAD" or "my_branch" - // determines if we show the whole git graph i.e. pass the '--all' flag - All bool -} - -func (c *CommitListBuilder) MergeRebasingCommits(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.Status != "rebasing" { // removing the existing rebase commits so we can add the refreshed ones - result = append(result, commits[i:]...) - break - } - } - - rebaseMode, err := c.GitCommand.RebaseMode() - if err != nil { - return nil, err - } - - if rebaseMode == "" { - // not in rebase mode so return original commits - return result, nil - } - - rebasingCommits, err := c.getHydratedRebasingCommits(rebaseMode) - if err != nil { - return nil, err - } - if len(rebasingCommits) > 0 { - result = append(rebasingCommits, result...) - } - - return result, nil -} - -// GetCommits obtains the commits of the current branch -func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) { - commits := []*models.Commit{} - var rebasingCommits []*models.Commit - rebaseMode, err := c.GitCommand.RebaseMode() - if err != nil { - return nil, err - } - - if opts.IncludeRebaseCommits && opts.FilterPath == "" { - var err error - rebasingCommits, err = c.MergeRebasingCommits(commits) - if err != nil { - return nil, err - } - commits = append(commits, rebasingCommits...) - } - - passedFirstPushedCommit := false - firstPushedCommit, err := c.getFirstPushedCommit(opts.RefName) - if err != nil { - // must have no upstream branch so we'll consider everything as pushed - passedFirstPushedCommit = true - } - - cmd := c.getLogCmd(opts) - - err = oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) { - if canExtractCommit(line) { - commit := c.extractCommitFromLine(line) - if commit.Sha == firstPushedCommit { - passedFirstPushedCommit = true - } - commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[!passedFirstPushedCommit] - commits = append(commits, commit) - } - return false, nil - }) - if err != nil { - return nil, err - } - - if rebaseMode != "" { - currentCommit := commits[len(rebasingCommits)] - youAreHere := style.FgYellow.Sprintf("<-- %s ---", c.Tr.YouAreHere) - currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name) - } - - commits, err = c.setCommitMergedStatuses(opts.RefName, commits) - if err != nil { - return nil, err - } - - return commits, nil -} - -func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*models.Commit, error) { - commits, err := c.getRebasingCommits(rebaseMode) +func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode) ([]*models.Commit, error) { + commits, err := self.getRebasingCommits(rebaseMode) if err != nil { return nil, err } @@ -201,20 +207,20 @@ func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*mo // note that we're not filtering these as we do non-rebasing commits just because // I suspect that will cause some damage - cmd := c.OSCommand.ExecutableFromString( + cmdObj := self.cmd.New( fmt.Sprintf( "git show %s --no-patch --oneline %s --abbrev=%d", strings.Join(commitShas, " "), prettyFormat, 20, ), - ) + ).DontLog() hydratedCommits := make([]*models.Commit, 0, len(commits)) i := 0 - err = oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) { + err = cmdObj.RunAndProcessLines(func(line string) (bool, error) { if canExtractCommit(line) { - commit := c.extractCommitFromLine(line) + commit := self.extractCommitFromLine(line) matchingCommit := commits[i] commit.Action = matchingCommit.Action commit.Status = matchingCommit.Status @@ -230,20 +236,20 @@ func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*mo } // getRebasingCommits obtains the commits that we're in the process of rebasing -func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*models.Commit, error) { +func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) ([]*models.Commit, error) { switch rebaseMode { - case REBASE_MODE_MERGING: - return c.getNormalRebasingCommits() - case REBASE_MODE_INTERACTIVE: - return c.getInteractiveRebasingCommits() + case enums.REBASE_MODE_MERGING: + return self.getNormalRebasingCommits() + case enums.REBASE_MODE_INTERACTIVE: + return self.getInteractiveRebasingCommits() default: return nil, nil } } -func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error) { +func (self *CommitLoader) getNormalRebasingCommits() ([]*models.Commit, error) { rewrittenCount := 0 - bytesContent, err := ioutil.ReadFile(filepath.Join(c.GitCommand.DotGitDir, "rebase-apply/rewritten")) + bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-apply/rewritten")) if err == nil { content := string(bytesContent) rewrittenCount = len(strings.Split(content, "\n")) @@ -251,7 +257,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error) // we know we're rebasing, so lets get all the files whose names have numbers commits := []*models.Commit{} - err = filepath.Walk(filepath.Join(c.GitCommand.DotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error { + err = self.walkFiles(filepath.Join(self.dotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error { if rewrittenCount > 0 { rewrittenCount-- return nil @@ -263,15 +269,12 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error) if !re.MatchString(f.Name()) { return nil } - bytesContent, err := ioutil.ReadFile(path) + bytesContent, err := self.readFile(path) if err != nil { return err } content := string(bytesContent) - commit, err := c.commitFromPatch(content) - if err != nil { - return err - } + commit := self.commitFromPatch(content) commits = append([]*models.Commit{commit}, commits...) return nil }) @@ -294,10 +297,10 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error) // getInteractiveRebasingCommits takes our git-rebase-todo and our git-rebase-todo.backup files // and extracts out the sha and names of commits that we still have to go // in the rebase: -func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*models.Commit, error) { - bytesContent, err := ioutil.ReadFile(filepath.Join(c.GitCommand.DotGitDir, "rebase-merge/git-rebase-todo")) +func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, error) { + bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")) if err != nil { - c.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error())) + 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, nil } @@ -328,7 +331,7 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*models.Commit, e // From: Lazygit Tester // Date: Wed, 5 Dec 2018 21:03:23 +1100 // Subject: second commit on master -func (c *CommitListBuilder) commitFromPatch(content string) (*models.Commit, error) { +func (self *CommitLoader) commitFromPatch(content string) *models.Commit { lines := strings.Split(content, "\n") sha := strings.Split(lines[0], " ")[1] name := strings.TrimPrefix(lines[3], "Subject: ") @@ -336,11 +339,11 @@ func (c *CommitListBuilder) commitFromPatch(content string) (*models.Commit, err Sha: sha, Name: name, Status: "rebasing", - }, nil + } } -func (c *CommitListBuilder) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) { - ancestor, err := c.getMergeBase(refName) +func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) { + ancestor, err := self.getMergeBase(refName) if err != nil { return nil, err } @@ -362,8 +365,8 @@ func (c *CommitListBuilder) setCommitMergedStatuses(refName string, commits []*m return commits, nil } -func (c *CommitListBuilder) getMergeBase(refName string) (string, error) { - currentBranch, _, err := c.GitCommand.CurrentBranchName() +func (self *CommitLoader) getMergeBase(refName string) (string, error) { + currentBranch, _, err := self.getCurrentBranchName() if err != nil { return "", err } @@ -374,7 +377,7 @@ func (c *CommitListBuilder) getMergeBase(refName string) (string, error) { } // swallowing error because it's not a big deal; probably because there are no commits yet - output, _ := c.OSCommand.RunCommandWithOutput("git merge-base %s %s", c.OSCommand.Quote(refName), c.OSCommand.Quote(baseBranch)) + output, _ := self.cmd.New(fmt.Sprintf("git merge-base %s %s", self.cmd.Quote(refName), self.cmd.Quote(baseBranch))).DontLog().RunWithOutput() return ignoringWarnings(output), nil } @@ -390,8 +393,13 @@ func ignoringWarnings(commandOutput string) string { // getFirstPushedCommit returns the first commit SHA which has been pushed to the ref's upstream. // all commits above this are deemed unpushed and marked as such. -func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) { - output, err := c.OSCommand.RunCommandWithOutput("git merge-base %s %s@{u}", c.OSCommand.Quote(refName), c.OSCommand.Quote(refName)) +func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) { + output, err := self.cmd. + New( + fmt.Sprintf("git merge-base %s %s@{u}", self.cmd.Quote(refName), self.cmd.Quote(refName)), + ). + DontLog(). + RunWithOutput() if err != nil { return "", err } @@ -400,18 +408,18 @@ func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) } // getLog gets the git log. -func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd { +func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj { limitFlag := "" if opts.Limit { - limitFlag = "-300" + limitFlag = " -300" } filterFlag := "" if opts.FilterPath != "" { - filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(opts.FilterPath)) + filterFlag = fmt.Sprintf(" --follow -- %s", self.cmd.Quote(opts.FilterPath)) } - config := c.GitCommand.Config.GetUserConfig().Git.Log + config := self.UserConfig.Git.Log orderFlag := "--" + config.Order allFlag := "" @@ -419,10 +427,10 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd { allFlag = " --all" } - return c.OSCommand.ExecutableFromString( + return self.cmd.New( fmt.Sprintf( - "git log %s %s %s --oneline %s %s --abbrev=%d %s", - c.OSCommand.Quote(opts.RefName), + "git log %s %s %s --oneline %s%s --abbrev=%d%s", + self.cmd.Quote(opts.RefName), orderFlag, allFlag, prettyFormat, @@ -430,7 +438,7 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd { 20, filterFlag, ), - ) + ).DontLog() } var prettyFormat = fmt.Sprintf( @@ -443,5 +451,5 @@ var prettyFormat = fmt.Sprintf( ) func canExtractCommit(line string) bool { - return strings.Split(line, " ")[0] != "gpg:" + return line != "" && strings.Split(line, " ")[0] != "gpg:" } diff --git a/pkg/commands/loaders/commits_test.go b/pkg/commands/loaders/commits_test.go new file mode 100644 index 000000000..23406abcc --- /dev/null +++ b/pkg/commands/loaders/commits_test.go @@ -0,0 +1,215 @@ +package loaders + +import ( + "path/filepath" + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func NewDummyCommitLoader() *CommitLoader { + cmn := utils.NewDummyCommon() + + return &CommitLoader{ + Common: cmn, + cmd: nil, + getCurrentBranchName: func() (string, string, error) { return "master", "master", nil }, + getRebaseMode: func() (enums.RebaseMode, error) { return enums.REBASE_MODE_NONE, nil }, + dotGitDir: ".git", + readFile: func(filename string) ([]byte, error) { + return []byte(""), nil + }, + walkFiles: func(root string, fn filepath.WalkFunc) error { + return nil + }, + } +} + +const commitsOutput = `0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode +b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield| (origin/better-tests)|e94e8fc5b6fab4cb755f|fix logging +e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield||d8084cd558925eb7c9c3|refactor +d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield||65f910ebd85283b5cce9|WIP +65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield||26c07b1ab33860a1a759|WIP +26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield||3d4470a6c072208722e5|WIP +3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield||053a66a7be3da43aacdc|WIP +053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield||985fe482e806b172aea4|refactoring the config struct` + +func TestGetCommits(t *testing.T) { + type scenario struct { + testName string + runner *oscommands.FakeCmdObjRunner + expectedCommits []*models.Commit + expectedError error + rebaseMode enums.RebaseMode + currentBranchName string + opts GetCommitsOptions + } + + scenarios := []scenario{ + { + testName: "should return no commits if there are none", + rebaseMode: enums.REBASE_MODE_NONE, + currentBranchName: "master", + opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false}, + runner: oscommands.NewFakeRunner(t). + Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). + Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H|%at|%aN|%d|%p|%s" --abbrev=20`, "", nil), + + expectedCommits: []*models.Commit{}, + expectedError: nil, + }, + { + testName: "should return commits if they are present", + rebaseMode: enums.REBASE_MODE_NONE, + currentBranchName: "master", + opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false}, + runner: oscommands.NewFakeRunner(t). + // here it's seeing which commits are yet to be pushed + Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). + // here it's actually getting all the commits in a formatted form, one per line + Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H|%at|%aN|%d|%p|%s" --abbrev=20`, commitsOutput, nil). + // here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged' + Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil), + + expectedCommits: []*models.Commit{ + { + Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc", + Name: "better typing for rebase mode", + Status: "unpushed", + Action: "", + Tags: []string{}, + ExtraInfo: "(HEAD -> better-tests)", + Author: "Jesse Duffield", + UnixTimestamp: 1640826609, + Parents: []string{ + "b21997d6b4cbdf84b149", + }, + }, + { + Sha: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", + Name: "fix logging", + Status: "pushed", + Action: "", + Tags: []string{}, + ExtraInfo: "(origin/better-tests)", + Author: "Jesse Duffield", + UnixTimestamp: 1640824515, + Parents: []string{ + "e94e8fc5b6fab4cb755f", + }, + }, + { + Sha: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c", + Name: "refactor", + Status: "pushed", + Action: "", + Tags: []string{}, + ExtraInfo: "", + Author: "Jesse Duffield", + UnixTimestamp: 1640823749, + Parents: []string{ + "d8084cd558925eb7c9c3", + }, + }, + { + Sha: "d8084cd558925eb7c9c38afeed5725c21653ab90", + Name: "WIP", + Status: "pushed", + Action: "", + Tags: []string{}, + ExtraInfo: "", + Author: "Jesse Duffield", + UnixTimestamp: 1640821426, + Parents: []string{ + "65f910ebd85283b5cce9", + }, + }, + { + Sha: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a", + Name: "WIP", + Status: "pushed", + Action: "", + Tags: []string{}, + ExtraInfo: "", + Author: "Jesse Duffield", + UnixTimestamp: 1640821275, + Parents: []string{ + "26c07b1ab33860a1a759", + }, + }, + { + Sha: "26c07b1ab33860a1a7591a0638f9925ccf497ffa", + Name: "WIP", + Status: "merged", + Action: "", + Tags: []string{}, + ExtraInfo: "", + Author: "Jesse Duffield", + UnixTimestamp: 1640750752, + Parents: []string{ + "3d4470a6c072208722e5", + }, + }, + { + Sha: "3d4470a6c072208722e5ae9a54bcb9634959a1c5", + Name: "WIP", + Status: "merged", + Action: "", + Tags: []string{}, + ExtraInfo: "", + Author: "Jesse Duffield", + UnixTimestamp: 1640748818, + Parents: []string{ + "053a66a7be3da43aacdc", + }, + }, + { + Sha: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2", + Name: "refactoring the config struct", + Status: "merged", + Action: "", + Tags: []string{}, + ExtraInfo: "", + Author: "Jesse Duffield", + UnixTimestamp: 1640739815, + Parents: []string{ + "985fe482e806b172aea4", + }, + }, + }, + expectedError: nil, + }, + } + + for _, scenario := range scenarios { + scenario := scenario + t.Run(scenario.testName, func(t *testing.T) { + builder := &CommitLoader{ + Common: utils.NewDummyCommon(), + cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner), + getCurrentBranchName: func() (string, string, error) { + return scenario.currentBranchName, scenario.currentBranchName, nil + }, + getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil }, + dotGitDir: ".git", + readFile: func(filename string) ([]byte, error) { + return []byte(""), nil + }, + walkFiles: func(root string, fn filepath.WalkFunc) error { + return nil + }, + } + + commits, err := builder.GetCommits(scenario.opts) + + assert.Equal(t, scenario.expectedCommits, commits) + assert.Equal(t, scenario.expectedError, err) + + scenario.runner.CheckForMissingCalls() + }) + } +} diff --git a/pkg/commands/loading_files.go b/pkg/commands/loaders/files.go similarity index 65% rename from pkg/commands/loading_files.go rename to pkg/commands/loaders/files.go index 098948ed4..f5becdb92 100644 --- a/pkg/commands/loading_files.go +++ b/pkg/commands/loaders/files.go @@ -1,36 +1,57 @@ -package commands +package loaders import ( "fmt" "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" ) -// GetStatusFiles git status files +type FileLoaderConfig interface { + GetShowUntrackedFiles() string +} + +type FileLoader struct { + *common.Common + cmd oscommands.ICmdObjBuilder + config FileLoaderConfig + getFileType func(string) string +} + +func NewFileLoader(cmn *common.Common, cmd oscommands.ICmdObjBuilder, config FileLoaderConfig) *FileLoader { + return &FileLoader{ + Common: cmn, + cmd: cmd, + getFileType: oscommands.FileType, + config: config, + } +} + type GetStatusFileOptions struct { NoRenames bool } -func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File { +func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File { // check if config wants us ignoring untracked files - untrackedFilesSetting := c.GitConfig.Get("status.showUntrackedFiles") + untrackedFilesSetting := self.config.GetShowUntrackedFiles() if untrackedFilesSetting == "" { untrackedFilesSetting = "all" } untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting) - statuses, err := c.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) + statuses, err := self.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) if err != nil { - c.Log.Error(err) + self.Log.Error(err) } files := []*models.File{} for _, status := range statuses { if strings.HasPrefix(status.StatusString, "warning") { - c.Log.Warningf("warning when calling git status: %s", status.StatusString) + self.Log.Warningf("warning when calling git status: %s", status.StatusString) continue } change := status.Change @@ -38,8 +59,8 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File { unstagedChange := change[1:2] untracked := utils.IncludesString([]string{"??", "A ", "AM"}, change) hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange) - hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change) hasInlineMergeConflicts := utils.IncludesString([]string{"UU", "AA"}, change) + hasMergeConflicts := hasInlineMergeConflicts || utils.IncludesString([]string{"DD", "AU", "UA", "UD", "DU"}, change) file := &models.File{ Name: status.Name, @@ -52,7 +73,7 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File { Added: unstagedChange == "A" || untracked, HasMergeConflicts: hasMergeConflicts, HasInlineMergeConflicts: hasInlineMergeConflicts, - Type: c.OSCommand.FileType(status.Name), + Type: self.getFileType(status.Name), ShortStatus: change, } files = append(files, file) @@ -74,13 +95,13 @@ type FileStatus struct { PreviousName string } -func (c *GitCommand) GitStatus(opts GitStatusOptions) ([]FileStatus, error) { +func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) { noRenamesFlag := "" if opts.NoRenames { - noRenamesFlag = "--no-renames" + noRenamesFlag = " --no-renames" } - statusLines, err := c.RunCommandWithOutput("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag) + statusLines, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutput() if err != nil { return []FileStatus{}, err } diff --git a/pkg/commands/loaders/files_test.go b/pkg/commands/loaders/files_test.go new file mode 100644 index 000000000..b860a1470 --- /dev/null +++ b/pkg/commands/loaders/files_test.go @@ -0,0 +1,210 @@ +package loaders + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestFileGetStatusFiles(t *testing.T) { + type scenario struct { + testName string + runner oscommands.ICmdObjRunner + expectedFiles []*models.File + } + + scenarios := []scenario{ + { + "No files found", + oscommands.NewFakeRunner(t). + Expect(`git status --untracked-files=yes --porcelain -z`, "", nil), + []*models.File{}, + }, + { + "Several files found", + oscommands.NewFakeRunner(t). + Expect( + `git status --untracked-files=yes --porcelain -z`, + "MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt", + nil, + ), + []*models.File{ + { + Name: "file1.txt", + HasStagedChanges: true, + HasUnstagedChanges: true, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "MM file1.txt", + Type: "file", + ShortStatus: "MM", + }, + { + Name: "file3.txt", + HasStagedChanges: true, + HasUnstagedChanges: false, + Tracked: false, + Added: true, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "A file3.txt", + Type: "file", + ShortStatus: "A ", + }, + { + Name: "file2.txt", + HasStagedChanges: true, + HasUnstagedChanges: true, + Tracked: false, + Added: true, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "AM file2.txt", + Type: "file", + ShortStatus: "AM", + }, + { + Name: "file4.txt", + HasStagedChanges: false, + HasUnstagedChanges: true, + Tracked: false, + Added: true, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "?? file4.txt", + Type: "file", + ShortStatus: "??", + }, + { + Name: "file5.txt", + HasStagedChanges: false, + HasUnstagedChanges: true, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: true, + HasInlineMergeConflicts: true, + DisplayString: "UU file5.txt", + Type: "file", + ShortStatus: "UU", + }, + }, + }, + { + "File with new line char", + oscommands.NewFakeRunner(t). + Expect(`git status --untracked-files=yes --porcelain -z`, "MM a\nb.txt", nil), + []*models.File{ + { + Name: "a\nb.txt", + HasStagedChanges: true, + HasUnstagedChanges: true, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "MM a\nb.txt", + Type: "file", + ShortStatus: "MM", + }, + }, + }, + { + "Renamed files", + oscommands.NewFakeRunner(t). + Expect( + `git status --untracked-files=yes --porcelain -z`, + "R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt", + nil, + ), + []*models.File{ + { + Name: "after1.txt", + PreviousName: "before1.txt", + HasStagedChanges: true, + HasUnstagedChanges: false, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "R before1.txt -> after1.txt", + Type: "file", + ShortStatus: "R ", + }, + { + Name: "after2.txt", + PreviousName: "before2.txt", + HasStagedChanges: true, + HasUnstagedChanges: true, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "RM before2.txt -> after2.txt", + Type: "file", + ShortStatus: "RM", + }, + }, + }, + { + "File with arrow in name", + oscommands.NewFakeRunner(t). + Expect( + `git status --untracked-files=yes --porcelain -z`, + `?? a -> b.txt`, + nil, + ), + []*models.File{ + { + Name: "a -> b.txt", + HasStagedChanges: false, + HasUnstagedChanges: true, + Tracked: false, + Added: true, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "?? a -> b.txt", + Type: "file", + ShortStatus: "??", + }, + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + cmd := oscommands.NewDummyCmdObjBuilder(s.runner) + + loader := &FileLoader{ + Common: utils.NewDummyCommon(), + 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/loaders/reflog_commits.go b/pkg/commands/loaders/reflog_commits.go new file mode 100644 index 000000000..dc1a4ac15 --- /dev/null +++ b/pkg/commands/loaders/reflog_commits.go @@ -0,0 +1,70 @@ +package loaders + +import ( + "fmt" + "strconv" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/common" +) + +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(lastReflogCommit *models.Commit, filterPath string) ([]*models.Commit, bool, error) { + commits := make([]*models.Commit, 0) + + filterPathArg := "" + if filterPath != "" { + filterPathArg = fmt.Sprintf(" --follow -- %s", self.cmd.Quote(filterPath)) + } + + cmdObj := self.cmd.New(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs"%s`, filterPathArg)).DontLog() + onlyObtainedNewReflogCommits := false + err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { + fields := strings.SplitN(line, " ", 3) + if len(fields) <= 2 { + return false, nil + } + + unixTimestamp, _ := strconv.Atoi(fields[1]) + + commit := &models.Commit{ + Sha: fields[0], + Name: fields[2], + UnixTimestamp: int64(unixTimestamp), + Status: "reflog", + } + + // note that the unix timestamp here is the timestamp of the COMMIT, not the reflog entry itself, + // so two consequetive reflog entries may have both the same SHA 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 && commit.Sha == lastReflogCommit.Sha && commit.UnixTimestamp == lastReflogCommit.UnixTimestamp && commit.Name == lastReflogCommit.Name { + onlyObtainedNewReflogCommits = true + // after this point we already have these reflogs loaded so we'll simply return the new ones + return true, nil + } + + commits = append(commits, commit) + return false, nil + }) + if err != nil { + return nil, false, err + } + + return commits, onlyObtainedNewReflogCommits, nil +} diff --git a/pkg/commands/loaders/reflog_commits_test.go b/pkg/commands/loaders/reflog_commits_test.go new file mode 100644 index 000000000..0e00ca3e5 --- /dev/null +++ b/pkg/commands/loaders/reflog_commits_test.go @@ -0,0 +1,159 @@ +package loaders + +import ( + "errors" + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/sanity-io/litter" + "github.com/stretchr/testify/assert" +) + +const reflogOutput = `c3c4b66b64c97ffeecde 1643150483 checkout: moving from A to B +c3c4b66b64c97ffeecde 1643150483 checkout: moving from B to A +c3c4b66b64c97ffeecde 1643150483 checkout: moving from A to B +c3c4b66b64c97ffeecde 1643150483 checkout: moving from master to A +f4ddf2f0d4be4ccc7efa 1643149435 checkout: moving from A to master +` + +func TestGetReflogCommits(t *testing.T) { + type scenario struct { + testName string + runner *oscommands.FakeCmdObjRunner + lastReflogCommit *models.Commit + filterPath string + expectedCommits []*models.Commit + expectedOnlyObtainedNew bool + expectedError error + } + + scenarios := []scenario{ + { + testName: "no reflog entries", + runner: oscommands.NewFakeRunner(t). + Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, "", nil), + + lastReflogCommit: nil, + expectedCommits: []*models.Commit{}, + expectedOnlyObtainedNew: false, + expectedError: nil, + }, + { + testName: "some reflog entries", + runner: oscommands.NewFakeRunner(t). + Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, reflogOutput, nil), + + lastReflogCommit: nil, + expectedCommits: []*models.Commit{ + { + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from A to B", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + { + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from B to A", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + { + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from A to B", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + { + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from master to A", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + { + Sha: "f4ddf2f0d4be4ccc7efa", + Name: "checkout: moving from A to master", + Status: "reflog", + UnixTimestamp: 1643149435, + }, + }, + expectedOnlyObtainedNew: false, + expectedError: nil, + }, + { + testName: "some reflog entries where last commit is given", + runner: oscommands.NewFakeRunner(t). + Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, reflogOutput, nil), + + lastReflogCommit: &models.Commit{ + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from B to A", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + expectedCommits: []*models.Commit{ + { + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from A to B", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + }, + expectedOnlyObtainedNew: true, + expectedError: nil, + }, + { + testName: "when passing filterPath", + runner: oscommands.NewFakeRunner(t). + Expect(`git log -g --abbrev=20 --format="%h %ct %gs" --follow -- "path"`, reflogOutput, nil), + + lastReflogCommit: &models.Commit{ + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from B to A", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + filterPath: "path", + expectedCommits: []*models.Commit{ + { + Sha: "c3c4b66b64c97ffeecde", + Name: "checkout: moving from A to B", + Status: "reflog", + UnixTimestamp: 1643150483, + }, + }, + expectedOnlyObtainedNew: true, + expectedError: nil, + }, + { + testName: "when command returns error", + runner: oscommands.NewFakeRunner(t). + Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, "", errors.New("haha")), + + lastReflogCommit: nil, + filterPath: "", + expectedCommits: nil, + expectedOnlyObtainedNew: false, + expectedError: errors.New("haha"), + }, + } + + for _, scenario := range scenarios { + scenario := scenario + t.Run(scenario.testName, func(t *testing.T) { + builder := &ReflogCommitLoader{ + Common: utils.NewDummyCommon(), + cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner), + } + + commits, onlyObtainednew, err := builder.GetReflogCommits(scenario.lastReflogCommit, scenario.filterPath) + assert.Equal(t, scenario.expectedOnlyObtainedNew, onlyObtainednew) + assert.Equal(t, scenario.expectedError, err) + t.Logf("actual commits: \n%s", litter.Sdump(commits)) + assert.Equal(t, scenario.expectedCommits, commits) + + scenario.runner.CheckForMissingCalls() + }) + } +} diff --git a/pkg/commands/loading_remotes.go b/pkg/commands/loaders/remotes.go similarity index 57% rename from pkg/commands/loading_remotes.go rename to pkg/commands/loaders/remotes.go index 49d4fabfe..bd1fe0b6a 100644 --- a/pkg/commands/loading_remotes.go +++ b/pkg/commands/loaders/remotes.go @@ -1,4 +1,4 @@ -package commands +package loaders import ( "fmt" @@ -6,18 +6,37 @@ import ( "sort" "strings" + 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" ) -func (c *GitCommand) GetRemotes() ([]*models.Remote, error) { - // get remote branches - unescaped := "git branch -r" - remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(unescaped) +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) { + remoteBranchesStr, err := self.cmd.New("git branch -r").DontLog().RunWithOutput() if err != nil { return nil, err } - goGitRemotes, err := c.Repo.Remotes() + goGitRemotes, err := self.getGoGitRemotes() if err != nil { return nil, err } @@ -27,7 +46,7 @@ func (c *GitCommand) GetRemotes() ([]*models.Remote, error) { for i, goGitRemote := range goGitRemotes { remoteName := goGitRemote.Config().Name - re := regexp.MustCompile(fmt.Sprintf(`%s\/([\S]+)`, remoteName)) + re := regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%s\/([\S]+)`, remoteName)) matches := re.FindAllStringSubmatch(remoteBranchesStr, -1) branches := make([]*models.RemoteBranch, len(matches)) for j, match := range matches { diff --git a/pkg/commands/loaders/stash.go b/pkg/commands/loaders/stash.go new file mode 100644 index 000000000..689bf30ce --- /dev/null +++ b/pkg/commands/loaders/stash.go @@ -0,0 +1,80 @@ +package loaders + +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" +) + +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() + } + + rawString, err := self.cmd.New("git stash list --name-only").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++ { + if !isAStash(lines[i]) { + continue + } + match := re.FindStringSubmatch(lines[i]) + idx, err := strconv.Atoi(match[1]) + if err != nil { + return self.getUnfilteredStashEntries() + } + currentStashEntry = self.stashEntryFromLine(lines[i], idx) + for i+1 < len(lines) && !isAStash(lines[i+1]) { + i++ + if lines[i] == filterPath { + stashEntries = append(stashEntries, currentStashEntry) + continue outer + } + } + } + return stashEntries +} + +func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry { + rawString, _ := self.cmd.New("git stash list --pretty='%gs'").DontLog().RunWithOutput() + stashEntries := []*models.StashEntry{} + for i, line := range utils.SplitLines(rawString) { + stashEntries = append(stashEntries, self.stashEntryFromLine(line, i)) + } + return stashEntries +} + +func (c *StashLoader) stashEntryFromLine(line string, index int) *models.StashEntry { + return &models.StashEntry{ + Name: line, + Index: index, + } +} diff --git a/pkg/commands/loaders/stash_test.go b/pkg/commands/loaders/stash_test.go new file mode 100644 index 000000000..e7999e861 --- /dev/null +++ b/pkg/commands/loaders/stash_test.go @@ -0,0 +1,60 @@ +package loaders + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestGetStashEntries(t *testing.T) { + type scenario struct { + testName string + filterPath string + runner oscommands.ICmdObjRunner + expectedStashEntries []*models.StashEntry + } + + scenarios := []scenario{ + { + "No stash entries found", + "", + oscommands.NewFakeRunner(t). + Expect(`git stash list --pretty='%gs'`, "", nil), + []*models.StashEntry{}, + }, + { + "Several stash entries found", + "", + oscommands.NewFakeRunner(t). + Expect( + `git stash list --pretty='%gs'`, + "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template", + nil, + ), + []*models.StashEntry{ + { + Index: 0, + Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", + }, + { + Index: 1, + Name: "WIP on master: bb86a3f update github template", + }, + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.testName, func(t *testing.T) { + cmd := oscommands.NewDummyCmdObjBuilder(s.runner) + + loader := NewStashLoader(utils.NewDummyCommon(), cmd) + + assert.EqualValues(t, s.expectedStashEntries, loader.GetStashEntries(s.filterPath)) + }) + } +} diff --git a/pkg/commands/loading_tags.go b/pkg/commands/loaders/tags.go similarity index 55% rename from pkg/commands/loading_tags.go rename to pkg/commands/loaders/tags.go index 71704f9c0..45b08a002 100644 --- a/pkg/commands/loading_tags.go +++ b/pkg/commands/loaders/tags.go @@ -1,16 +1,33 @@ -package commands +package loaders import ( "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" ) -func (c *GitCommand) GetTags() ([]*models.Tag, error) { +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 - remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(`git tag --list --sort=-creatordate`) + remoteBranchesStr, err := self.cmd.New(`git tag --list --sort=-creatordate`).DontLog().RunWithOutput() if err != nil { return nil, err } diff --git a/pkg/commands/loading_commit_files.go b/pkg/commands/loading_commit_files.go deleted file mode 100644 index bddddc761..000000000 --- a/pkg/commands/loading_commit_files.go +++ /dev/null @@ -1,42 +0,0 @@ -package commands - -import ( - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" -) - -// GetFilesInDiff get the specified commit files -func (c *GitCommand) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) { - reverseFlag := "" - if reverse { - reverseFlag = " -R " - } - - filenames, err := c.RunCommandWithOutput("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to) - if err != nil { - return nil, err - } - - return c.getCommitFilesFromFilenames(filenames), nil -} - -// filenames string is something like "file1\nfile2\nfile3" -func (c *GitCommand) getCommitFilesFromFilenames(filenames string) []*models.CommitFile { - commitFiles := make([]*models.CommitFile, 0) - - lines := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00") - n := len(lines) - for i := 0; i < n-1; i += 2 { - // typical result looks like 'A my_file' meaning my_file was added - changeStatus := lines[i] - name := lines[i+1] - - commitFiles = append(commitFiles, &models.CommitFile{ - Name: name, - ChangeStatus: changeStatus, - }) - } - - return commitFiles -} diff --git a/pkg/commands/loading_commits_test.go b/pkg/commands/loading_commits_test.go deleted file mode 100644 index bd35556d4..000000000 --- a/pkg/commands/loading_commits_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package commands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/stretchr/testify/assert" -) - -// NewDummyCommitListBuilder creates a new dummy CommitListBuilder for testing -func NewDummyCommitListBuilder() *CommitListBuilder { - osCommand := oscommands.NewDummyOSCommand() - - return &CommitListBuilder{ - Log: utils.NewDummyLog(), - GitCommand: NewDummyGitCommandWithOSCommand(osCommand), - OSCommand: osCommand, - Tr: i18n.NewTranslationSet(utils.NewDummyLog(), "auto"), - } -} - -// TestCommitListBuilderGetMergeBase is a function. -func TestCommitListBuilderGetMergeBase(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(string, error) - } - - scenarios := []scenario{ - { - "swallows an error if the call to merge-base returns an error", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return secureexec.Command("echo", "master") - case "merge-base": - assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) - return secureexec.Command("test") - } - return nil - }, - func(output string, err error) { - assert.NoError(t, err) - assert.EqualValues(t, "", output) - }, - }, - { - "returns the commit when master", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return secureexec.Command("echo", "master") - case "merge-base": - assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) - return secureexec.Command("echo", "blah") - } - return nil - }, - func(output string, err error) { - assert.NoError(t, err) - assert.Equal(t, "blah", output) - }, - }, - { - "checks against develop when a feature branch", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return secureexec.Command("echo", "feature/test") - case "merge-base": - assert.EqualValues(t, []string{"merge-base", "HEAD", "develop"}, args) - return secureexec.Command("echo", "blah") - } - return nil - }, - func(output string, err error) { - assert.NoError(t, err) - assert.Equal(t, "blah", output) - }, - }, - { - "bubbles up error if there is one", - func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command("test") - }, - func(output string, err error) { - assert.Error(t, err) - assert.Equal(t, "", output) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - c := NewDummyCommitListBuilder() - c.OSCommand.SetCommand(s.command) - s.test(c.getMergeBase("HEAD")) - }) - } -} diff --git a/pkg/commands/loading_files_test.go b/pkg/commands/loading_files_test.go deleted file mode 100644 index 456e4aca7..000000000 --- a/pkg/commands/loading_files_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package commands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandGetStatusFiles is a function. -func TestGitCommandGetStatusFiles(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func([]*models.File) - } - - scenarios := []scenario{ - { - "No files found", - func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command("echo") - }, - func(files []*models.File) { - assert.Len(t, files, 0) - }, - }, - { - "Several files found", - func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command( - "printf", - `MM file1.txt\0A file3.txt\0AM file2.txt\0?? file4.txt\0UU file5.txt`, - ) - }, - func(files []*models.File) { - assert.Len(t, files, 5) - - expected := []*models.File{ - { - Name: "file1.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "MM file1.txt", - Type: "other", - ShortStatus: "MM", - }, - { - Name: "file3.txt", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "A file3.txt", - Type: "other", - ShortStatus: "A ", - }, - { - Name: "file2.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "AM file2.txt", - Type: "other", - ShortStatus: "AM", - }, - { - Name: "file4.txt", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "?? file4.txt", - Type: "other", - ShortStatus: "??", - }, - { - Name: "file5.txt", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: true, - HasInlineMergeConflicts: true, - DisplayString: "UU file5.txt", - Type: "other", - ShortStatus: "UU", - }, - } - - assert.EqualValues(t, expected, files) - }, - }, - { - "File with new line char", - func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command( - "printf", - `MM a\nb.txt`, - ) - }, - func(files []*models.File) { - assert.Len(t, files, 1) - - expected := []*models.File{ - { - Name: "a\nb.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "MM a\nb.txt", - Type: "other", - ShortStatus: "MM", - }, - } - - assert.EqualValues(t, expected, files) - }, - }, - { - "Renamed files", - func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command( - "printf", - `R after1.txt\0before1.txt\0RM after2.txt\0before2.txt`, - ) - }, - func(files []*models.File) { - assert.Len(t, files, 2) - - expected := []*models.File{ - { - Name: "after1.txt", - PreviousName: "before1.txt", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "R before1.txt -> after1.txt", - Type: "other", - ShortStatus: "R ", - }, - { - Name: "after2.txt", - PreviousName: "before2.txt", - HasStagedChanges: true, - HasUnstagedChanges: true, - Tracked: true, - Added: false, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "RM before2.txt -> after2.txt", - Type: "other", - ShortStatus: "RM", - }, - } - - assert.EqualValues(t, expected, files) - }, - }, - { - "File with arrow in name", - func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command( - "printf", - `?? a -> b.txt`, - ) - }, - func(files []*models.File) { - assert.Len(t, files, 1) - - expected := []*models.File{ - { - Name: "a -> b.txt", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: false, - Added: true, - Deleted: false, - HasMergeConflicts: false, - HasInlineMergeConflicts: false, - DisplayString: "?? a -> b.txt", - Type: "other", - ShortStatus: "??", - }, - } - - assert.EqualValues(t, expected, files) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - - s.test(gitCmd.GetStatusFiles(GetStatusFileOptions{})) - }) - } -} diff --git a/pkg/commands/loading_reflog_commits.go b/pkg/commands/loading_reflog_commits.go deleted file mode 100644 index 619e5c225..000000000 --- a/pkg/commands/loading_reflog_commits.go +++ /dev/null @@ -1,53 +0,0 @@ -package commands - -import ( - "fmt" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -// 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 (c *GitCommand) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string) ([]*models.Commit, bool, error) { - commits := make([]*models.Commit, 0) - - filterPathArg := "" - if filterPath != "" { - filterPathArg = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(filterPath)) - } - - cmd := c.OSCommand.ExecutableFromString(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg)) - onlyObtainedNewReflogCommits := false - err := oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) { - fields := strings.SplitN(line, " ", 3) - if len(fields) <= 2 { - return false, nil - } - - unixTimestamp, _ := strconv.Atoi(fields[1]) - - commit := &models.Commit{ - Sha: fields[0], - Name: fields[2], - UnixTimestamp: int64(unixTimestamp), - Status: "reflog", - } - - if lastReflogCommit != nil && commit.Sha == lastReflogCommit.Sha && commit.UnixTimestamp == lastReflogCommit.UnixTimestamp { - onlyObtainedNewReflogCommits = true - // after this point we already have these reflogs loaded so we'll simply return the new ones - return true, nil - } - - commits = append(commits, commit) - return false, nil - }) - if err != nil { - return nil, false, err - } - - return commits, onlyObtainedNewReflogCommits, nil -} diff --git a/pkg/commands/loading_stash.go b/pkg/commands/loading_stash.go deleted file mode 100644 index e14b463dd..000000000 --- a/pkg/commands/loading_stash.go +++ /dev/null @@ -1,65 +0,0 @@ -package commands - -import ( - "regexp" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func (c *GitCommand) getUnfilteredStashEntries() []*models.StashEntry { - unescaped := "git stash list --pretty='%gs'" - rawString, _ := c.OSCommand.RunCommandWithOutput(unescaped) - stashEntries := []*models.StashEntry{} - for i, line := range utils.SplitLines(rawString) { - stashEntries = append(stashEntries, stashEntryFromLine(line, i)) - } - return stashEntries -} - -// GetStashEntries stash entries -func (c *GitCommand) GetStashEntries(filterPath string) []*models.StashEntry { - if filterPath == "" { - return c.getUnfilteredStashEntries() - } - - rawString, err := c.RunCommandWithOutput("git stash list --name-only") - if err != nil { - return c.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++ { - if !isAStash(lines[i]) { - continue - } - match := re.FindStringSubmatch(lines[i]) - idx, err := strconv.Atoi(match[1]) - if err != nil { - return c.getUnfilteredStashEntries() - } - currentStashEntry = stashEntryFromLine(lines[i], idx) - for i+1 < len(lines) && !isAStash(lines[i+1]) { - i++ - if lines[i] == filterPath { - stashEntries = append(stashEntries, currentStashEntry) - continue outer - } - } - } - return stashEntries -} - -func stashEntryFromLine(line string, index int) *models.StashEntry { - return &models.StashEntry{ - Name: line, - Index: index, - } -} diff --git a/pkg/commands/loading_stash_test.go b/pkg/commands/loading_stash_test.go deleted file mode 100644 index ea1f86ec1..000000000 --- a/pkg/commands/loading_stash_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package commands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandGetStashEntries is a function. -func TestGitCommandGetStashEntries(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func([]*models.StashEntry) - } - - scenarios := []scenario{ - { - "No stash entries found", - func(string, ...string) *exec.Cmd { - return secureexec.Command("echo") - }, - func(entries []*models.StashEntry) { - assert.Len(t, entries, 0) - }, - }, - { - "Several stash entries found", - func(string, ...string) *exec.Cmd { - return secureexec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template") - }, - func(entries []*models.StashEntry) { - expected := []*models.StashEntry{ - { - Index: 0, - Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", - }, - { - Index: 1, - Name: "WIP on master: bb86a3f update github template", - }, - } - - assert.Len(t, entries, 2) - assert.EqualValues(t, expected, entries) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - - s.test(gitCmd.GetStashEntries("")) - }) - } -} diff --git a/pkg/commands/models/branch.go b/pkg/commands/models/branch.go index df7e54903..7bd109d97 100644 --- a/pkg/commands/models/branch.go +++ b/pkg/commands/models/branch.go @@ -7,12 +7,16 @@ import "strings" 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 - Recency string - Pushables string - Pullables string - UpstreamName string - Head bool + DisplayName string + Recency string + Pushables string + Pullables string + Head 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 } func (b *Branch) RefName() string { @@ -27,22 +31,26 @@ func (b *Branch) Description() string { return b.RefName() } -// this method does not consider the case where the git config states that a branch is tracking the config. -// The Pullables value here is based on whether or not we saw an upstream when doing `git branch` func (b *Branch) IsTrackingRemote() bool { - return b.IsRealBranch() && b.Pullables != "?" + 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.Pushables != "?" && b.Pullables != "?" } func (b *Branch) MatchesUpstream() bool { - return b.IsRealBranch() && b.Pushables == "0" && b.Pullables == "0" + return b.RemoteBranchStoredLocally() && b.Pushables == "0" && b.Pullables == "0" } func (b *Branch) HasCommitsToPush() bool { - return b.IsRealBranch() && b.Pushables != "0" + return b.RemoteBranchStoredLocally() && b.Pushables != "0" } func (b *Branch) HasCommitsToPull() bool { - return b.IsRealBranch() && b.Pullables != "0" + return b.RemoteBranchStoredLocally() && b.Pullables != "0" } // for when we're in a detached head state diff --git a/pkg/commands/models/commit.go b/pkg/commands/models/commit.go index 1b8659e77..9f9184f2e 100644 --- a/pkg/commands/models/commit.go +++ b/pkg/commands/models/commit.go @@ -1,6 +1,10 @@ package models -import "fmt" +import ( + "fmt" + + "github.com/jesseduffield/lazygit/pkg/utils" +) // Commit : A git commit type Commit struct { @@ -18,10 +22,7 @@ type Commit struct { } func (c *Commit) ShortSha() string { - if len(c.Sha) < 8 { - return c.Sha - } - return c.Sha[:8] + return utils.ShortSha(c.Sha) } func (c *Commit) RefName() string { @@ -39,3 +40,9 @@ func (c *Commit) Description() string { 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 != "" +} diff --git a/pkg/commands/oscommands/cmd_obj.go b/pkg/commands/oscommands/cmd_obj.go index 592a4a61d..3e55359de 100644 --- a/pkg/commands/oscommands/cmd_obj.go +++ b/pkg/commands/oscommands/cmd_obj.go @@ -5,19 +5,90 @@ import ( ) // A command object is a general way to represent a command to be run on the -// command line. If you want to log the command you'll use .ToString() and -// if you want to run it you'll use .GetCmd() +// command line. type ICmdObj interface { GetCmd() *exec.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"' ToString() string + AddEnvVars(...string) ICmdObj + GetEnvVars() []string + + // runs the command and returns an error if any + Run() error + // runs the command and returns the output as a string, and an error if any + RunWithOutput() (string, error) + // 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. + RunAndProcessLines(onLine func(line string) (bool, error)) error + + // Be 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. + DontLog() ICmdObj + + // This returns false if DontLog() was called + ShouldLog() bool + + // when you call this, then call Run(), we'll stream the output to the cmdWriter (i.e. the command log panel) + StreamOutput() ICmdObj + // returns true if StreamOutput() was called + ShouldStreamOutput() bool + + // 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) + IgnoreEmptyError() ICmdObj + // returns true if IgnoreEmptyError() was called + ShouldIgnoreEmptyError() bool + + PromptOnCredentialRequest() ICmdObj + FailOnCredentialRequest() ICmdObj + + GetCredentialStrategy() CredentialStrategy } type CmdObj struct { cmdStr string cmd *exec.Cmd + + runner ICmdObjRunner + + // see DontLog() + dontLog bool + + // see StreamOutput() + streamOutput 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 } +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 +) + +var _ ICmdObj = &CmdObj{} + func (self *CmdObj) GetCmd() *exec.Cmd { return self.cmd } @@ -31,3 +102,64 @@ func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj { return self } + +func (self *CmdObj) GetEnvVars() []string { + return self.cmd.Env +} + +func (self *CmdObj) DontLog() ICmdObj { + self.dontLog = true + return self +} + +func (self *CmdObj) ShouldLog() bool { + return !self.dontLog +} + +func (self *CmdObj) StreamOutput() ICmdObj { + self.streamOutput = true + + return self +} + +func (self *CmdObj) ShouldStreamOutput() bool { + return self.streamOutput +} + +func (self *CmdObj) IgnoreEmptyError() ICmdObj { + self.ignoreEmptyError = true + + return self +} + +func (self *CmdObj) ShouldIgnoreEmptyError() bool { + return self.ignoreEmptyError +} + +func (self *CmdObj) Run() error { + return self.runner.Run(self) +} + +func (self *CmdObj) RunWithOutput() (string, error) { + return self.runner.RunWithOutput(self) +} + +func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error { + return self.runner.RunAndProcessLines(self, onLine) +} + +func (self *CmdObj) PromptOnCredentialRequest() ICmdObj { + self.credentialStrategy = PROMPT + + return self +} + +func (self *CmdObj) FailOnCredentialRequest() ICmdObj { + self.credentialStrategy = FAIL + + return self +} + +func (self *CmdObj) GetCredentialStrategy() CredentialStrategy { + return self.credentialStrategy +} diff --git a/pkg/commands/oscommands/cmd_obj_builder.go b/pkg/commands/oscommands/cmd_obj_builder.go new file mode 100644 index 000000000..a493ae651 --- /dev/null +++ b/pkg/commands/oscommands/cmd_obj_builder.go @@ -0,0 +1,101 @@ +package oscommands + +import ( + "fmt" + "os" + "strings" + + "github.com/jesseduffield/lazygit/pkg/secureexec" + "github.com/mgutz/str" +) + +type ICmdObjBuilder interface { + // New returns a new command object based on the string provided + New(cmdStr string) ICmdObj + // NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'` + NewShell(commandStr string) ICmdObj + // NewFromArgs takes a slice of strings like []string{"git", "commit"} and returns a new command object. This can be useful when you don't want to worry about whitespace and quoting and stuff. + NewFromArgs(args []string) ICmdObj + // 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(cmdStr string) ICmdObj { + args := str.ToArgv(cmdStr) + cmd := secureexec.Command(args[0], args[1:]...) + cmd.Env = os.Environ() + + return &CmdObj{ + cmdStr: cmdStr, + cmd: cmd, + runner: self.runner, + } +} + +func (self *CmdObjBuilder) NewFromArgs(args []string) ICmdObj { + cmd := secureexec.Command(args[0], args[1:]...) + cmd.Env = os.Environ() + + return &CmdObj{ + cmdStr: strings.Join(args, " "), + cmd: cmd, + runner: self.runner, + } +} + +func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj { + quotedCommand := "" + // Windows does not seem to like quotes around the command + if self.platform.OS == "windows" { + quotedCommand = strings.NewReplacer( + "^", "^^", + "&", "^&", + "|", "^|", + "<", "^<", + ">", "^>", + "%", "^%", + ).Replace(commandStr) + } else { + quotedCommand = self.Quote(commandStr) + } + + shellCommand := fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand) + return self.New(shellCommand) +} + +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 new file mode 100644 index 000000000..e1a38d80f --- /dev/null +++ b/pkg/commands/oscommands/cmd_obj_runner.go @@ -0,0 +1,272 @@ +package oscommands + +import ( + "bufio" + "bytes" + "io" + "regexp" + "strings" + + "github.com/go-errors/errors" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/sirupsen/logrus" +) + +type ICmdObjRunner interface { + Run(cmdObj ICmdObj) error + RunWithOutput(cmdObj ICmdObj) (string, error) + RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error +} + +type CredentialType int + +const ( + Password CredentialType = iota + Username + Passphrase +) + +type cmdObjRunner struct { + log *logrus.Entry + guiIO *guiIO +} + +var _ ICmdObjRunner = &cmdObjRunner{} + +func (self *cmdObjRunner) Run(cmdObj ICmdObj) error { + if cmdObj.GetCredentialStrategy() != NONE { + return self.runWithCredentialHandling(cmdObj) + } + + if cmdObj.ShouldStreamOutput() { + return self.runAndStream(cmdObj) + } + + _, err := self.RunWithOutput(cmdObj) + return err +} + +func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) { + 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 + } + + 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 + } + + self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand") + + if cmdObj.ShouldLog() { + self.logCmdObj(cmdObj) + } + + output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput()) + if err != nil { + self.log.WithField("command", cmdObj.ToString()).Error(output) + } + return output, err +} + +func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error { + 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) + } + + cmd := cmdObj.GetCmd() + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return err + } + + scanner := bufio.NewScanner(stdoutPipe) + scanner.Split(bufio.ScanLines) + if err := cmd.Start(); err != nil { + return err + } + + for scanner.Scan() { + line := scanner.Text() + stop, err := onLine(line) + if err != nil { + return err + } + if stop { + _ = cmd.Process.Kill() + break + } + } + + _ = cmd.Wait() + + return nil +} + +// Whenever we're asked for a password we just enter a newline, which will +// eventually cause the command to fail. +var failPromptFn = func(CredentialType) string { return "\n" } + +func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error { + var promptFn func(CredentialType) string + + switch cmdObj.GetCredentialStrategy() { + case PROMPT: + promptFn = self.guiIO.promptForCredentialFn + case FAIL: + promptFn = failPromptFn + case NONE: + // we should never land here + return errors.New("runWithCredentialHandling called but cmdObj does not have a a credential strategy") + } + + return self.runAndDetectCredentialRequest(cmdObj, promptFn) +} + +func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) { + 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 ICmdObj) error { + return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) { + go func() { + _, _ = io.Copy(cmdWriter, handler.stdoutPipe) + }() + }) +} + +// runAndDetectCredentialRequest detect a username / password / passphrase question in a command +// promptUserForCredential is a function that gets executed when this function detect you need to fillin 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 ICmdObj, + promptUserForCredential func(CredentialType) string, +) error { + // setting the output to english so we can parse it for a username/password request + cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8") + + 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) + }) + }) +} + +func (self *cmdObjRunner) runAndStreamAux( + cmdObj ICmdObj, + onRun func(*cmdHandler, io.Writer), +) error { + cmdWriter := self.guiIO.newCmdWriterFn() + + if cmdObj.ShouldLog() { + self.logCmdObj(cmdObj) + } + self.log.WithField("command", cmdObj.ToString()).Info("RunCommand") + cmd := cmdObj.GetCmd() + + var stderr bytes.Buffer + cmd.Stderr = io.MultiWriter(cmdWriter, &stderr) + + handler, err := self.getCmdHandler(cmd) + if err != nil { + return err + } + + defer func() { + if closeErr := handler.close(); closeErr != nil { + self.log.Error(closeErr) + } + }() + + onRun(handler, cmdWriter) + + err = cmd.Wait() + if err != nil { + errStr := stderr.String() + if cmdObj.ShouldIgnoreEmptyError() && errStr == "" { + return nil + } + return errors.New(stderr.String()) + } + + return nil +} + +func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, promptUserForCredential func(CredentialType) string) { + checkForCredentialRequest := self.getCheckForCredentialRequestFunc() + + scanner := bufio.NewScanner(reader) + scanner.Split(bufio.ScanBytes) + for scanner.Scan() { + newBytes := scanner.Bytes() + askFor, ok := checkForCredentialRequest(newBytes) + if ok { + toInput := promptUserForCredential(askFor) + // 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 + // 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) + } + + 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, + } + + for pattern, askFor := range prompts { + if match, _ := regexp.MatchString(pattern, ttyText.String()); match { + ttyText.Reset() + return askFor, true + } + } + + return 0, false + } +} diff --git a/pkg/commands/oscommands/cmd_obj_runner_default.go b/pkg/commands/oscommands/cmd_obj_runner_default.go new file mode 100644 index 000000000..567dcf970 --- /dev/null +++ b/pkg/commands/oscommands/cmd_obj_runner_default.go @@ -0,0 +1,25 @@ +//go:build !windows +// +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) getCmdHandler(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_win.go b/pkg/commands/oscommands/cmd_obj_runner_win.go new file mode 100644 index 000000000..9e3d1fd02 --- /dev/null +++ b/pkg/commands/oscommands/cmd_obj_runner_win.go @@ -0,0 +1,49 @@ +//go:build windows +// +build windows + +package oscommands + +import ( + "bytes" + "io" + "os/exec" + "sync" +) + +type Buffer struct { + b bytes.Buffer + m sync.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) +} + +// TODO: Remove this hack and replace it with a proper way to run commands live on windows. We still have an issue where if a password is requested, the request for a password is written straight to stdout because we can't control the stdout of a subprocess of a subprocess. Keep an eye on https://github.com/creack/pty/pull/109 +func (self *cmdObjRunner) getCmdHandler(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 + } + + // because we don't yet have windows support for a pty, we instead just + // pass our standard stream handlers and because there's no pty to close + // we pass a no-op function for that. + return &cmdHandler{ + stdoutPipe: stdoutReader, + stdinPipe: buf, + close: func() error { return nil }, + }, nil +} diff --git a/pkg/commands/oscommands/dummies.go b/pkg/commands/oscommands/dummies.go index bb6f741f1..158e9a9c1 100644 --- a/pkg/commands/oscommands/dummies.go +++ b/pkg/commands/oscommands/dummies.go @@ -1,11 +1,63 @@ package oscommands import ( - "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/utils" ) // NewDummyOSCommand creates a new dummy OSCommand for testing func NewDummyOSCommand() *OSCommand { - return NewOSCommand(utils.NewDummyLog(), config.NewDummyAppConfig()) + osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + + return osCmd +} + +type OSCommandDeps struct { + Common *common.Common + Platform *Platform + GetenvFn func(string) string + RemoveFileFn func(string) error + Cmd *CmdObjBuilder +} + +func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { + common := deps.Common + if common == nil { + common = utils.NewDummyCommon() + } + + platform := deps.Platform + if platform == nil { + platform = dummyPlatform + } + + return &OSCommand{ + Common: common, + Platform: platform, + getenvFn: deps.GetenvFn, + removeFileFn: deps.RemoveFileFn, + guiIO: NewNullGuiIO(utils.NewDummyLog()), + } +} + +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(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + osCommand.Cmd = NewDummyCmdObjBuilder(runner) + + return osCommand } diff --git a/pkg/commands/oscommands/exec_live.go b/pkg/commands/oscommands/exec_live.go deleted file mode 100644 index 6ca70a5ec..000000000 --- a/pkg/commands/oscommands/exec_live.go +++ /dev/null @@ -1,153 +0,0 @@ -package oscommands - -import ( - "bufio" - "bytes" - "io" - "os/exec" - "regexp" - "strings" - "unicode/utf8" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -// DetectUnamePass detect a username / password / passphrase question in a command -// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase -// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back -func (c *OSCommand) DetectUnamePass(cmdObj ICmdObj, writer io.Writer, promptUserForCredential func(string) string) error { - ttyText := "" - errMessage := c.RunCommandWithOutputLive(cmdObj, writer, func(word string) string { - ttyText = ttyText + " " + word - - prompts := map[string]string{ - `.+'s password:`: "password", - `Password\s*for\s*'.+':`: "password", - `Username\s*for\s*'.+':`: "username", - `Enter\s*passphrase\s*for\s*key\s*'.+':`: "passphrase", - } - - for pattern, askFor := range prompts { - if match, _ := regexp.MatchString(pattern, ttyText); match { - ttyText = "" - return promptUserForCredential(askFor) - } - } - - return "" - }) - return errMessage -} - -// Due to a lack of pty support on windows we have RunCommandWithOutputLiveWrapper being defined -// separate for windows and other OS's -func (c *OSCommand) RunCommandWithOutputLive(cmdObj ICmdObj, writer io.Writer, handleOutput func(string) string) error { - return RunCommandWithOutputLiveWrapper(c, cmdObj, writer, handleOutput) -} - -type cmdHandler struct { - stdoutPipe io.Reader - stdinPipe io.Writer - close func() error -} - -// RunCommandWithOutputLiveAux runs a command and return every word that gets written in stdout -// Output is a function that executes by every word that gets read by bufio -// As return of output you need to give a string that will be written to stdin -// NOTE: If the return data is empty it won't write anything to stdin -func RunCommandWithOutputLiveAux( - c *OSCommand, - cmdObj ICmdObj, - writer io.Writer, - // handleOutput takes a word from stdout and returns a string to be written to stdin. - // See DetectUnamePass above for how this is used to check for a username/password request - handleOutput func(string) string, - startCmd func(cmd *exec.Cmd) (*cmdHandler, error), -) error { - c.Log.WithField("command", cmdObj.ToString()).Info("RunCommand") - c.LogCommand(cmdObj.ToString(), true) - cmd := cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8").GetCmd() - - var stderr bytes.Buffer - cmd.Stderr = io.MultiWriter(writer, &stderr) - - handler, err := startCmd(cmd) - if err != nil { - return err - } - - defer func() { - if closeErr := handler.close(); closeErr != nil { - c.Log.Error(closeErr) - } - }() - - tr := io.TeeReader(handler.stdoutPipe, writer) - - go utils.Safe(func() { - scanner := bufio.NewScanner(tr) - scanner.Split(scanWordsWithNewLines) - for scanner.Scan() { - text := scanner.Text() - output := strings.Trim(text, " ") - toInput := handleOutput(output) - if toInput != "" { - _, _ = handler.stdinPipe.Write([]byte(toInput)) - } - } - }) - - err = cmd.Wait() - if err != nil { - return errors.New(stderr.String()) - } - - return nil -} - -// scanWordsWithNewLines is a copy of bufio.ScanWords but this also captures new lines -// For specific comments about this function take a look at: bufio.ScanWords -func scanWordsWithNewLines(data []byte, atEOF bool) (advance int, token []byte, err error) { - start := 0 - for width := 0; start < len(data); start += width { - var r rune - r, width = utf8.DecodeRune(data[start:]) - if !isSpace(r) { - break - } - } - for width, i := 0, start; i < len(data); i += width { - var r rune - r, width = utf8.DecodeRune(data[i:]) - if isSpace(r) { - return i + width, data[start:i], nil - } - } - if atEOF && len(data) > start { - return len(data), data[start:], nil - } - return start, nil, nil -} - -// isSpace is also copied from the bufio package and has been modified to also captures new lines -// For specific comments about this function take a look at: bufio.isSpace -func isSpace(r rune) bool { - if r <= '\u00FF' { - switch r { - case ' ', '\t', '\v', '\f': - return true - case '\u0085', '\u00A0': - return true - } - return false - } - if '\u2000' <= r && r <= '\u200a' { - return true - } - switch r { - case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000': - return true - } - return false -} diff --git a/pkg/commands/oscommands/exec_live_default.go b/pkg/commands/oscommands/exec_live_default.go deleted file mode 100644 index 827db9ed3..000000000 --- a/pkg/commands/oscommands/exec_live_default.go +++ /dev/null @@ -1,37 +0,0 @@ -//go:build !windows -// +build !windows - -package oscommands - -import ( - "io" - "os/exec" - - "github.com/creack/pty" -) - -func RunCommandWithOutputLiveWrapper( - c *OSCommand, - cmdObj ICmdObj, - writer io.Writer, - output func(string) string, -) error { - return RunCommandWithOutputLiveAux( - c, - cmdObj, - writer, - output, - func(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/exec_live_win.go b/pkg/commands/oscommands/exec_live_win.go deleted file mode 100644 index 5b61e478b..000000000 --- a/pkg/commands/oscommands/exec_live_win.go +++ /dev/null @@ -1,63 +0,0 @@ -//go:build windows -// +build windows - -package oscommands - -import ( - "bytes" - "io" - "os/exec" - "sync" -) - -type Buffer struct { - b bytes.Buffer - m sync.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) -} - -// RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there -// TODO: Remove this hack and replace it with a proper way to run commands live on windows. We still have an issue where if a password is requested, the request for a password is written straight to stdout because we can't control the stdout of a subprocess of a subprocess. Keep an eye on https://github.com/creack/pty/pull/109 -func RunCommandWithOutputLiveWrapper( - c *OSCommand, - cmdObj ICmdObj, - writer io.Writer, - output func(string) string, -) error { - return RunCommandWithOutputLiveAux( - c, - cmdObj, - writer, - output, - func(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 - } - - // because we don't yet have windows support for a pty, we instead just - // pass our standard stream handlers and because there's no pty to close - // we pass a no-op function for that. - return &cmdHandler{ - stdoutPipe: stdoutReader, - stdinPipe: buf, - close: func() error { return nil }, - }, nil - }, - ) -} diff --git a/pkg/commands/oscommands/fake_cmd_obj_runner.go b/pkg/commands/oscommands/fake_cmd_obj_runner.go new file mode 100644 index 000000000..b542bfee3 --- /dev/null +++ b/pkg/commands/oscommands/fake_cmd_obj_runner.go @@ -0,0 +1,117 @@ +package oscommands + +import ( + "bufio" + "fmt" + "regexp" + "strings" + "testing" + + "github.com/go-errors/errors" + "github.com/stretchr/testify/assert" +) + +// for use in testing + +type FakeCmdObjRunner struct { + t *testing.T + expectedCmds []func(ICmdObj) (string, error) + expectedCmdIndex int +} + +var _ ICmdObjRunner = &FakeCmdObjRunner{} + +func NewFakeRunner(t *testing.T) *FakeCmdObjRunner { + return &FakeCmdObjRunner{t: t} +} + +func (self *FakeCmdObjRunner) Run(cmdObj ICmdObj) error { + _, err := self.RunWithOutput(cmdObj) + return err +} + +func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) { + if self.expectedCmdIndex > len(self.expectedCmds)-1 { + self.t.Errorf("ran too many commands. Unexpected command: `%s`", cmdObj.ToString()) + return "", errors.New("ran too many commands") + } + + expectedCmd := self.expectedCmds[self.expectedCmdIndex] + output, err := expectedCmd(cmdObj) + + self.expectedCmdIndex++ + + return output, err +} + +func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, 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(fn func(cmdObj ICmdObj) (string, error)) *FakeCmdObjRunner { + self.expectedCmds = append(self.expectedCmds, fn) + + return self +} + +func (self *FakeCmdObjRunner) Expect(expectedCmdStr string, output string, err error) *FakeCmdObjRunner { + self.ExpectFunc(func(cmdObj ICmdObj) (string, error) { + cmdStr := cmdObj.ToString() + assert.Equal(self.t, expectedCmdStr, cmdStr, fmt.Sprintf("expected command %d to be %s, but was %s", self.expectedCmdIndex+1, expectedCmdStr, cmdStr)) + + return output, err + }) + + return self +} + +func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner { + self.ExpectFunc(func(cmdObj ICmdObj) (string, error) { + args := cmdObj.GetCmd().Args + assert.EqualValues(self.t, expectedArgs, args, fmt.Sprintf("command %d did not match expectation", self.expectedCmdIndex+1)) + + return output, err + }) + + return self +} + +func (self *FakeCmdObjRunner) ExpectGitArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner { + self.ExpectFunc(func(cmdObj ICmdObj) (string, error) { + // first arg is 'git' on unix and something like '"C:\\Program Files\\Git\\mingw64\\bin\\git.exe" on windows so we'll just ensure it ends in either 'git' or 'git.exe' + re := regexp.MustCompile(`git(\.exe)?$`) + args := cmdObj.GetCmd().Args + if !re.MatchString(args[0]) { + self.t.Errorf("expected first arg to end in .git or .git.exe but was %s", args[0]) + } + assert.EqualValues(self.t, expectedArgs, args[1:], fmt.Sprintf("command %d did not match expectation", self.expectedCmdIndex+1)) + + return output, err + }) + + return self +} + +func (self *FakeCmdObjRunner) CheckForMissingCalls() { + if self.expectedCmdIndex < len(self.expectedCmds) { + self.t.Errorf("expected command %d to be called, but was not", self.expectedCmdIndex+1) + } +} diff --git a/pkg/commands/oscommands/gui_io.go b/pkg/commands/oscommands/gui_io.go new file mode 100644 index 000000000..9540c13e1 --- /dev/null +++ b/pkg/commands/oscommands/gui_io.go @@ -0,0 +1,51 @@ +package oscommands + +import ( + "io" + "io/ioutil" + + "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) string +} + +func NewGuiIO(log *logrus.Entry, logCommandFn func(string, bool), newCmdWriterFn func() io.Writer, promptForCredentialFn func(CredentialType) 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 ioutil.Discard }, + promptForCredentialFn: failPromptFn, + } +} diff --git a/pkg/commands/oscommands/os.go b/pkg/commands/oscommands/os.go index cb41edff4..53f5bd6f6 100644 --- a/pkg/commands/oscommands/os.go +++ b/pkg/commands/oscommands/os.go @@ -1,7 +1,6 @@ package oscommands import ( - "bufio" "fmt" "io/ioutil" "os" @@ -13,13 +12,22 @@ import ( "github.com/go-errors/errors" "github.com/atotto/clipboard" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/secureexec" + "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/mgutz/str" - "github.com/sirupsen/logrus" ) +// 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 +} + // Platform stores the os state type Platform struct { OS string @@ -29,212 +37,30 @@ type Platform struct { OpenLinkCommand string } -// OSCommand holds all the os commands -type OSCommand struct { - Log *logrus.Entry - Platform *Platform - Config config.AppConfigurer - Command func(string, ...string) *exec.Cmd - BeforeExecuteCmd func(*exec.Cmd) - Getenv func(string) string - - // callback to run before running a command, i.e. for the purposes of logging - onRunCommand func(CmdLogEntry) - - // something like 'Staging File': allows us to group cmd logs under a single title - CmdLogSpan string - - removeFile func(string) error -} - -// TODO: make these fields private -type CmdLogEntry struct { - // e.g. 'git commit -m "haha"' - cmdStr string - // Span is something like 'Staging File'. Multiple commands can be grouped under the same - // span - span string - - // sometimes our command is direct like 'git commit', and sometimes it's a - // command to remove a file but through Go's standard library rather than the - // command line - commandLine bool -} - -func (e CmdLogEntry) GetCmdStr() string { - return e.cmdStr -} - -func (e CmdLogEntry) GetSpan() string { - return e.span -} - -func (e CmdLogEntry) GetCommandLine() bool { - return e.commandLine -} - -func NewCmdLogEntry(cmdStr string, span string, commandLine bool) CmdLogEntry { - return CmdLogEntry{cmdStr: cmdStr, span: span, commandLine: commandLine} -} - // NewOSCommand os command runner -func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand { - return &OSCommand{ - Log: log, - Platform: getPlatform(), - Config: config, - Command: secureexec.Command, - BeforeExecuteCmd: func(*exec.Cmd) {}, - Getenv: os.Getenv, - removeFile: os.RemoveAll, - } -} - -func (c *OSCommand) WithSpan(span string) *OSCommand { - // sometimes .WithSpan(span) will be called where span actually is empty, in - // which case we don't need to log anything so we can just return early here - // with the original struct - if span == "" { - return c +func NewOSCommand(common *common.Common, platform *Platform, guiIO *guiIO) *OSCommand { + c := &OSCommand{ + Common: common, + Platform: platform, + getenvFn: os.Getenv, + removeFileFn: os.RemoveAll, + guiIO: guiIO, } - newOSCommand := &OSCommand{} - *newOSCommand = *c - newOSCommand.CmdLogSpan = span - return newOSCommand -} + runner := &cmdObjRunner{log: common.Log, guiIO: guiIO} + c.Cmd = &CmdObjBuilder{runner: runner, platform: platform} -func (c *OSCommand) LogExecCmd(cmd *exec.Cmd) { - c.LogCommand(strings.Join(cmd.Args, " "), true) + return c } func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) { c.Log.WithField("command", cmdStr).Info("RunCommand") - if c.onRunCommand != nil && c.CmdLogSpan != "" { - c.onRunCommand(NewCmdLogEntry(cmdStr, c.CmdLogSpan, commandLine)) - } -} - -func (c *OSCommand) SetOnRunCommand(f func(CmdLogEntry)) { - c.onRunCommand = f -} - -// SetCommand sets the command function used by the struct. -// To be used for testing only -func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) { - c.Command = cmd -} - -// To be used for testing only -func (c *OSCommand) SetRemoveFile(f func(string) error) { - c.removeFile = f -} - -func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.Cmd)) { - c.BeforeExecuteCmd = cmd -} - -type RunCommandOptions struct { - EnvVars []string -} - -func (c *OSCommand) RunCommandWithOutputWithOptions(command string, options RunCommandOptions) (string, error) { - c.LogCommand(command, true) - cmd := c.ExecutableFromString(command) - - cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0") // prevents git from prompting us for input which would freeze the program - cmd.Env = append(cmd.Env, options.EnvVars...) - - return sanitisedCommandOutput(cmd.CombinedOutput()) -} - -func (c *OSCommand) RunCommandWithOptions(command string, options RunCommandOptions) error { - _, err := c.RunCommandWithOutputWithOptions(command, options) - return err -} - -// RunCommandWithOutput wrapper around commands returning their output and error -// NOTE: If you don't pass any formatArgs we'll just use the command directly, -// however there's a bizarre compiler error/warning when you pass in a formatString -// with a percent sign because it thinks it's supposed to be a formatString when -// in that case it's not. To get around that error you'll need to define the string -// in a variable and pass the variable into RunCommandWithOutput. -func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...interface{}) (string, error) { - command := formatString - if formatArgs != nil { - command = fmt.Sprintf(formatString, formatArgs...) - } - cmd := c.ExecutableFromString(command) - c.LogExecCmd(cmd) - output, err := sanitisedCommandOutput(cmd.CombinedOutput()) - if err != nil { - c.Log.WithField("command", command).Error(output) - } - return output, err -} - -// RunExecutableWithOutput runs an executable file and returns its output -func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) { - c.LogExecCmd(cmd) - c.BeforeExecuteCmd(cmd) - return sanitisedCommandOutput(cmd.CombinedOutput()) -} - -// RunExecutable runs an executable file and returns an error if there was one -func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error { - _, err := c.RunExecutableWithOutput(cmd) - return err -} - -// ExecutableFromString takes a string like `git status` and returns an executable command for it -func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd { - splitCmd := str.ToArgv(commandStr) - cmd := c.Command(splitCmd[0], splitCmd[1:]...) - cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") - return cmd -} - -// ShellCommandFromString takes a string like `git commit` and returns an executable shell command for it -func (c *OSCommand) ShellCommandFromString(commandStr string) *exec.Cmd { - quotedCommand := "" - // Windows does not seem to like quotes around the command - if c.Platform.OS == "windows" { - quotedCommand = strings.NewReplacer( - "^", "^^", - "&", "^&", - "|", "^|", - "<", "^<", - ">", "^>", - "%", "^%", - ).Replace(commandStr) - } else { - quotedCommand = c.Quote(commandStr) - } - - shellCommand := fmt.Sprintf("%s %s %s", c.Platform.Shell, c.Platform.ShellArg, quotedCommand) - return c.ExecutableFromString(shellCommand) -} - -// RunCommand runs a command and just returns the error -func (c *OSCommand) RunCommand(formatString string, formatArgs ...interface{}) error { - _, err := c.RunCommandWithOutput(formatString, formatArgs...) - return err -} - -// RunShellCommand runs shell commands i.e. 'sh -c '. Good for when you -// need access to the shell -func (c *OSCommand) RunShellCommand(command string) error { - cmd := c.ShellCommandFromString(command) - c.LogExecCmd(cmd) - - _, err := sanitisedCommandOutput(cmd.CombinedOutput()) - - return err + c.guiIO.logCommandFn(cmdStr, commandLine) } // FileType tells us if the file is a file, directory or other -func (c *OSCommand) FileType(path string) string { +func FileType(path string) string { fileInfo, err := os.Stat(path) if err != nil { return "other" @@ -245,78 +71,28 @@ func (c *OSCommand) FileType(path string) string { return "file" } -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 -} - -// OpenFile opens a file with the given func (c *OSCommand) OpenFile(filename string) error { - commandTemplate := c.Config.GetUserConfig().OS.OpenCommand + commandTemplate := c.UserConfig.OS.OpenCommand templateValues := map[string]string{ "filename": c.Quote(filename), } command := utils.ResolvePlaceholderString(commandTemplate, templateValues) - err := c.RunShellCommand(command) - return err + return c.Cmd.NewShell(command).Run() } -// OpenLink opens a file with the given func (c *OSCommand) OpenLink(link string) error { - c.LogCommand(fmt.Sprintf("Opening link '%s'", link), false) - commandTemplate := c.Config.GetUserConfig().OS.OpenLinkCommand + commandTemplate := c.UserConfig.OS.OpenLinkCommand templateValues := map[string]string{ "link": c.Quote(link), } command := utils.ResolvePlaceholderString(commandTemplate, templateValues) - err := c.RunShellCommand(command) - return err -} - -// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it -// TODO: see if this needs to exist, given that ExecutableFromString does the same things -func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd { - cmd := c.Command(cmdName, commandArgs...) - if cmd != nil { - cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") - } - c.LogExecCmd(cmd) - return cmd -} - -// PrepareShellSubProcess returns the pointer to a custom command -func (c *OSCommand) PrepareShellSubProcess(command string) *exec.Cmd { - return c.PrepareSubProcess(c.Platform.Shell, c.Platform.ShellArg, command) + return c.Cmd.NewShell(command).Run() } // Quote wraps a message in platform-specific quotation marks func (c *OSCommand) Quote(message string) string { - var quote string - if c.Platform.OS == "windows" { - quote = `\"` - message = strings.NewReplacer( - `"`, `"'"'"`, - `\"`, `\\"`, - ).Replace(message) - } else { - quote = `"` - message = strings.NewReplacer( - `\`, `\\`, - `"`, `\"`, - `$`, `\$`, - "`", "\\`", - ).Replace(message) - } - return quote + message + quote + return c.Cmd.Quote(message) } // AppendLineToFile adds a new line in file @@ -390,33 +166,6 @@ func (c *OSCommand) FileExists(path string) (bool, error) { return true, nil } -// RunPreparedCommand takes a pointer to an exec.Cmd and runs it -// this is useful if you need to give your command some environment variables -// before running it -func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error { - c.BeforeExecuteCmd(cmd) - c.LogExecCmd(cmd) - out, err := cmd.CombinedOutput() - outString := string(out) - c.Log.Info(outString) - if err != nil { - if len(outString) == 0 { - return err - } - return errors.New(outString) - } - return nil -} - -// GetLazygitPath returns the path of the currently executed file -func (c *OSCommand) 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) + `"` -} - // PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C func (c *OSCommand) PipeCommands(commandStrings ...string) error { cmds := make([]*exec.Cmd, len(commandStrings)) @@ -426,7 +175,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error { logCmdStr += " | " } logCmdStr += str - cmds[i] = c.ExecutableFromString(str) + cmds[i] = c.Cmd.New(str).GetCmd() } c.LogCommand(logCmdStr, true) @@ -489,35 +238,6 @@ func Kill(cmd *exec.Cmd) error { return cmd.Process.Kill() } -func RunLineOutputCmd(cmd *exec.Cmd, onLine func(line string) (bool, error)) error { - stdoutPipe, err := cmd.StdoutPipe() - if err != nil { - return err - } - - scanner := bufio.NewScanner(stdoutPipe) - scanner.Split(bufio.ScanLines) - if err := cmd.Start(); err != nil { - return err - } - - for scanner.Scan() { - line := scanner.Text() - stop, err := onLine(line) - if err != nil { - return err - } - if stop { - _ = cmd.Process.Kill() - break - } - } - - _ = cmd.Wait() - - return nil -} - func (c *OSCommand) CopyToClipboard(str string) error { escaped := strings.Replace(str, "\n", "\\n", -1) truncated := utils.TruncateWithEllipsis(escaped, 40) @@ -528,32 +248,34 @@ func (c *OSCommand) CopyToClipboard(str string) error { func (c *OSCommand) RemoveFile(path string) error { c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false) - return c.removeFile(path) + return c.removeFileFn(path) } -func (c *OSCommand) NewCmdObjFromStr(cmdStr string) ICmdObj { - args := str.ToArgv(cmdStr) - cmd := c.Command(args[0], args[1:]...) - cmd.Env = os.Environ() +func (c *OSCommand) Getenv(key string) string { + return c.getenvFn(key) +} - return &CmdObj{ - cmdStr: cmdStr, - cmd: cmd, +func GetTempDir() string { + return filepath.Join(os.TempDir(), "lazygit") +} + +// 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) + `"` } -func (c *OSCommand) NewCmdObjFromArgs(args []string) ICmdObj { - cmd := c.Command(args[0], args[1:]...) - - return &CmdObj{ - cmdStr: strings.Join(args, " "), - cmd: cmd, +func (c *OSCommand) UpdateWindowTitle() error { + if c.Platform.OS != "windows" { + return nil } -} - -func (c *OSCommand) NewCmdObj(cmd *exec.Cmd) ICmdObj { - return &CmdObj{ - cmdStr: strings.Join(cmd.Args, " "), - cmd: cmd, + path, getWdErr := os.Getwd() + if getWdErr != nil { + return getWdErr } + argString := fmt.Sprint("title ", filepath.Base(path), " - Lazygit") + return c.Cmd.NewShell(argString).Run() } diff --git a/pkg/commands/oscommands/os_default_platform.go b/pkg/commands/oscommands/os_default_platform.go index f5b1f4dbc..fd4967d95 100644 --- a/pkg/commands/oscommands/os_default_platform.go +++ b/pkg/commands/oscommands/os_default_platform.go @@ -7,7 +7,7 @@ import ( "runtime" ) -func getPlatform() *Platform { +func GetPlatform() *Platform { return &Platform{ OS: runtime.GOOS, Shell: "bash", diff --git a/pkg/commands/oscommands/os_default_test.go b/pkg/commands/oscommands/os_default_test.go deleted file mode 100644 index 5e15b1498..000000000 --- a/pkg/commands/oscommands/os_default_test.go +++ /dev/null @@ -1,138 +0,0 @@ -//go:build !windows -// +build !windows - -package oscommands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestOSCommandOpenFileDarwin is a function. -func TestOSCommandOpenFileDarwin(t *testing.T) { - type scenario struct { - filename string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "test", - func(name string, arg ...string) *exec.Cmd { - return secureexec.Command("exit", "1") - }, - func(err error) { - assert.Error(t, err) - }, - }, - { - "test", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "bash", name) - assert.Equal(t, []string{"-c", `open "test"`}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "filename with spaces", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "bash", name) - assert.Equal(t, []string{"-c", `open "filename with spaces"`}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - OSCmd := NewDummyOSCommand() - OSCmd.Platform.OS = "darwin" - OSCmd.Command = s.command - OSCmd.Config.GetUserConfig().OS.OpenCommand = "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 - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "test", - func(name string, arg ...string) *exec.Cmd { - return secureexec.Command("exit", "1") - }, - func(err error) { - assert.Error(t, err) - }, - }, - { - "test", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "bash", name) - assert.Equal(t, []string{"-c", `xdg-open "test" > /dev/null`}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "filename with spaces", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "bash", name) - assert.Equal(t, []string{"-c", `xdg-open "filename with spaces" > /dev/null`}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "let's_test_with_single_quote", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "bash", name) - assert.Equal(t, []string{"-c", `xdg-open "let's_test_with_single_quote" > /dev/null`}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "$USER.txt", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "bash", name) - assert.Equal(t, []string{"-c", `xdg-open "\$USER.txt" > /dev/null`}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - OSCmd := NewDummyOSCommand() - OSCmd.Command = s.command - OSCmd.Platform.OS = "linux" - OSCmd.Config.GetUserConfig().OS.OpenCommand = `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 index 4dcca3790..efda5a3a1 100644 --- a/pkg/commands/oscommands/os_test.go +++ b/pkg/commands/oscommands/os_test.go @@ -8,8 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestOSCommandRunCommandWithOutput is a function. -func TestOSCommandRunCommandWithOutput(t *testing.T) { +func TestOSCommandRunWithOutput(t *testing.T) { type scenario struct { command string test func(string, error) @@ -32,12 +31,12 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) { } for _, s := range scenarios { - s.test(NewDummyOSCommand().RunCommandWithOutput(s.command)) + c := NewDummyOSCommand() + s.test(c.Cmd.New(s.command).RunWithOutput()) } } -// TestOSCommandRunCommand is a function. -func TestOSCommandRunCommand(t *testing.T) { +func TestOSCommandRun(t *testing.T) { type scenario struct { command string test func(error) @@ -53,11 +52,11 @@ func TestOSCommandRunCommand(t *testing.T) { } for _, s := range scenarios { - s.test(NewDummyOSCommand().RunCommand(s.command)) + c := NewDummyOSCommand() + s.test(c.Cmd.New(s.command).Run()) } } -// TestOSCommandQuote is a function. func TestOSCommandQuote(t *testing.T) { osCommand := NewDummyOSCommand() @@ -109,7 +108,6 @@ func TestOSCommandQuoteWindows(t *testing.T) { assert.EqualValues(t, expected, actual) } -// TestOSCommandFileType is a function. func TestOSCommandFileType(t *testing.T) { type scenario struct { path string @@ -162,7 +160,7 @@ func TestOSCommandFileType(t *testing.T) { for _, s := range scenarios { s.setup() - s.test(NewDummyOSCommand().FileType(s.path)) + s.test(FileType(s.path)) _ = os.RemoveAll(s.path) } } @@ -192,6 +190,7 @@ func TestOSCommandCreateTempFile(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { s.test(NewDummyOSCommand().CreateTempFile(s.filename, s.content)) }) diff --git a/pkg/commands/oscommands/os_test_default.go b/pkg/commands/oscommands/os_test_default.go new file mode 100644 index 000000000..f4c1221ed --- /dev/null +++ b/pkg/commands/oscommands/os_test_default.go @@ -0,0 +1,114 @@ +//go:build !windows +// +build !windows + +package oscommands + +import ( + "testing" + + "github.com/go-errors/errors" + "github.com/stretchr/testify/assert" +) + +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.OpenCommand = "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.OpenCommand = `xdg-open {{filename}} > /dev/null` + + s.test(oSCmd.OpenFile(s.filename)) + } +} diff --git a/pkg/commands/oscommands/os_test_windows.go b/pkg/commands/oscommands/os_test_windows.go new file mode 100644 index 000000000..cf9c1e68a --- /dev/null +++ b/pkg/commands/oscommands/os_test_windows.go @@ -0,0 +1,78 @@ +//go:build windows +// +build windows + +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.OpenCommand = `start "" {{filename}}` + + s.test(oSCmd.OpenFile(s.filename)) + } +} diff --git a/pkg/commands/oscommands/os_windows.go b/pkg/commands/oscommands/os_windows.go index f3b3b4056..783f50518 100644 --- a/pkg/commands/oscommands/os_windows.go +++ b/pkg/commands/oscommands/os_windows.go @@ -1,6 +1,6 @@ package oscommands -func getPlatform() *Platform { +func GetPlatform() *Platform { return &Platform{ OS: "windows", Shell: "cmd", diff --git a/pkg/commands/oscommands/os_windows_test.go b/pkg/commands/oscommands/os_windows_test.go deleted file mode 100644 index 720f2705a..000000000 --- a/pkg/commands/oscommands/os_windows_test.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build windows -// +build windows - -package oscommands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestOSCommandOpenFileWindows tests the OpenFile command on Linux -func TestOSCommandOpenFileWindows(t *testing.T) { - type scenario struct { - filename string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "test", - func(name string, arg ...string) *exec.Cmd { - return secureexec.Command("exit", "1") - }, - func(err error) { - assert.Error(t, err) - }, - }, - { - "test", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "cmd", name) - assert.Equal(t, []string{"/c", "start", "", "test"}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "filename with spaces", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "cmd", name) - assert.Equal(t, []string{"/c", "start", "", "filename with spaces"}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "let's_test_with_single_quote", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "cmd", name) - assert.Equal(t, []string{"/c", "start", "", "let's_test_with_single_quote"}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "$USER.txt", - func(name string, arg ...string) *exec.Cmd { - assert.Equal(t, "cmd", name) - assert.Equal(t, []string{"/c", "start", "", "$USER.txt"}, arg) - return secureexec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - OSCmd := NewDummyOSCommand() - OSCmd.Command = s.command - OSCmd.Platform.OS = "windows" - OSCmd.Config.GetUserConfig().OS.OpenCommand = `start "" {{filename}}` - - s.test(OSCmd.OpenFile(s.filename)) - } -} diff --git a/pkg/commands/patch/patch_manager.go b/pkg/commands/patch/patch_manager.go index 6615d831f..c8e16a7fd 100644 --- a/pkg/commands/patch/patch_manager.go +++ b/pkg/commands/patch/patch_manager.go @@ -34,7 +34,7 @@ type PatchManager struct { // To is the commit sha if we're dealing with files of a commit, or a stash ref for a stash To string From string - Reverse bool + 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 @@ -43,18 +43,18 @@ type PatchManager struct { // fileInfoMap starts empty but you add files to it as you go along fileInfoMap map[string]*fileInfo Log *logrus.Entry - ApplyPatch applyPatchFunc + applyPatch applyPatchFunc - // LoadFileDiff loads the diff of a file, for a given to (typically a commit SHA) - LoadFileDiff loadFileDiffFunc + // loadFileDiff loads the diff of a file, for a given to (typically a commit SHA) + loadFileDiff loadFileDiffFunc } // NewPatchManager returns a new PatchManager func NewPatchManager(log *logrus.Entry, applyPatch applyPatchFunc, loadFileDiff loadFileDiffFunc) *PatchManager { return &PatchManager{ Log: log, - ApplyPatch: applyPatch, - LoadFileDiff: loadFileDiff, + applyPatch: applyPatch, + loadFileDiff: loadFileDiff, } } @@ -62,7 +62,7 @@ func NewPatchManager(log *logrus.Entry, applyPatch applyPatchFunc, loadFileDiff func (p *PatchManager) Start(from, to string, reverse bool, canRebase bool) { p.To = to p.From = from - p.Reverse = reverse + p.reverse = reverse p.CanRebase = canRebase p.fileInfoMap = map[string]*fileInfo{} } @@ -118,7 +118,7 @@ func (p *PatchManager) getFileInfo(filename string) (*fileInfo, error) { return info, nil } - diff, err := p.LoadFileDiff(p.From, p.To, p.Reverse, filename, true) + diff, err := p.loadFileDiff(p.From, p.To, p.reverse, filename, true) if err != nil { return nil, err } @@ -265,7 +265,7 @@ func (p *PatchManager) ApplyPatches(reverse bool) error { if patch == "" { continue } - if err = p.ApplyPatch(patch, applyFlags...); err != nil { + if err = p.applyPatch(patch, applyFlags...); err != nil { continue } break @@ -301,5 +301,5 @@ func (p *PatchManager) IsEmpty() bool { // if any of these things change we'll need to reset and start a new patch func (p *PatchManager) NewPatchRequired(from string, to string, reverse bool) bool { - return from != p.From || to != p.To || reverse != p.Reverse + return from != p.From || to != p.To || reverse != p.reverse } diff --git a/pkg/commands/patch/patch_modifier.go b/pkg/commands/patch/patch_modifier.go index 3ab13931b..2109ad1f0 100644 --- a/pkg/commands/patch/patch_modifier.go +++ b/pkg/commands/patch/patch_modifier.go @@ -22,7 +22,7 @@ func GetHeaderFromDiff(diff string) string { func GetHunksFromDiff(diff string) []*PatchHunk { hunks := []*PatchHunk{} firstLineIdx := -1 - var hunkLines []string + var hunkLines []string //nolint:prealloc pastDiffHeader := false for lineIdx, line := range strings.SplitAfter(diff, "\n") { diff --git a/pkg/commands/patch/patch_modifier_test.go b/pkg/commands/patch/patch_modifier_test.go index 8b866019b..ec79cbe32 100644 --- a/pkg/commands/patch/patch_modifier_test.go +++ b/pkg/commands/patch/patch_modifier_test.go @@ -511,6 +511,7 @@ func TestModifyPatchForRange(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { result := ModifiedPatchForRange(nil, s.filename, s.diffText, s.firstLineIndex, s.lastLineIndex, s.reverse, false) if !assert.Equal(t, s.expected, result) { @@ -538,6 +539,7 @@ func TestLineNumberOfLine(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { result := s.hunk.LineNumberOfLine(s.idx) if !assert.Equal(t, s.expected, result) { diff --git a/pkg/commands/patch/patch_parser.go b/pkg/commands/patch/patch_parser.go index 1fefe0748..c2be120c9 100644 --- a/pkg/commands/patch/patch_parser.go +++ b/pkg/commands/patch/patch_parser.go @@ -98,7 +98,7 @@ func (l *PatchLine) render(selected bool, included bool) string { return coloredString(style.FgCyan, match[1], selected, included) + coloredString(theme.DefaultTextColor, match[2], selected, false) } - textStyle := theme.DefaultTextColor + var textStyle style.TextStyle switch l.Kind { case PATCH_HEADER: textStyle = textStyle.SetBold() @@ -108,6 +108,8 @@ func (l *PatchLine) render(selected bool, included bool) string { textStyle = style.FgRed case COMMIT_SHA: textStyle = style.FgYellow + default: + textStyle = theme.DefaultTextColor } return coloredString(textStyle, content, selected, included) diff --git a/pkg/commands/patch_rebases.go b/pkg/commands/patch_rebases.go deleted file mode 100644 index a6901dc3f..000000000 --- a/pkg/commands/patch_rebases.go +++ /dev/null @@ -1,231 +0,0 @@ -package commands - -import ( - "fmt" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" -) - -// DeletePatchesFromCommit applies a patch in reverse for a commit -func (c *GitCommand) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int, p *patch.PatchManager) error { - if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil { - return err - } - - // apply each patch in reverse - if err := p.ApplyPatches(true); err != nil { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - return err - } - - // time to amend the selected commit - if err := c.AmendHead(); err != nil { - return err - } - - c.onSuccessfulContinue = func() error { - c.PatchManager.Reset() - return nil - } - - // continue - return c.GenericMergeOrRebaseAction("rebase", "continue") -} - -func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceCommitIdx int, destinationCommitIdx int, p *patch.PatchManager) error { - if sourceCommitIdx < destinationCommitIdx { - if err := c.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx); err != nil { - return err - } - - // apply each patch forward - if err := p.ApplyPatches(false); err != nil { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - return err - } - - // amend the destination commit - if err := c.AmendHead(); err != nil { - return err - } - - c.onSuccessfulContinue = func() error { - c.PatchManager.Reset() - return nil - } - - // continue - return c.GenericMergeOrRebaseAction("rebase", "continue") - } - - 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 c.UsingGpg() { - return errors.New(c.Tr.DisabledForGPG) - } - - baseIndex := sourceCommitIdx + 1 - todo := "" - for i, commit := range commits[0:baseIndex] { - a := "pick" - if i == sourceCommitIdx || i == destinationCommitIdx { - a = "edit" - } - todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo - } - - cmd, err := c.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true) - if err != nil { - return err - } - - if err := c.OSCommand.RunPreparedCommand(cmd); err != nil { - return err - } - - // apply each patch in reverse - if err := p.ApplyPatches(true); err != nil { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - return err - } - - // amend the source commit - if err := c.AmendHead(); err != nil { - return err - } - - if c.onSuccessfulContinue != nil { - return errors.New("You are midway through another rebase operation. Please abort to start again") - } - - c.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 := p.ApplyPatches(false); err != nil { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - return err - } - - // amend the destination commit - if err := c.AmendHead(); err != nil { - return err - } - - c.onSuccessfulContinue = func() error { - c.PatchManager.Reset() - return nil - } - - return c.GenericMergeOrRebaseAction("rebase", "continue") - } - - return c.GenericMergeOrRebaseAction("rebase", "continue") -} - -func (c *GitCommand) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, p *patch.PatchManager, stash bool) error { - if stash { - if err := c.StashSave(c.Tr.StashPrefix + commits[commitIdx].Sha); err != nil { - return err - } - } - - if err := c.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil { - return err - } - - if err := p.ApplyPatches(true); err != nil { - if c.WorkingTreeState() == REBASE_MODE_REBASING { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - } - return err - } - - // amend the commit - if err := c.AmendHead(); err != nil { - return err - } - - if c.onSuccessfulContinue != nil { - return errors.New("You are midway through another rebase operation. Please abort to start again") - } - - c.onSuccessfulContinue = func() error { - // add patches to index - if err := p.ApplyPatches(false); err != nil { - if c.WorkingTreeState() == REBASE_MODE_REBASING { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - } - return err - } - - if stash { - if err := c.StashDo(0, "apply"); err != nil { - return err - } - } - - c.PatchManager.Reset() - return nil - } - - return c.GenericMergeOrRebaseAction("rebase", "continue") -} - -func (c *GitCommand) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx int, p *patch.PatchManager) error { - if err := c.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil { - return err - } - - if err := p.ApplyPatches(true); err != nil { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - return err - } - - // amend the commit - if err := c.AmendHead(); err != nil { - return err - } - - // add patches to index - if err := p.ApplyPatches(false); err != nil { - if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil { - return err - } - return err - } - - head_message, _ := c.GetHeadCommitMessage() - new_message := fmt.Sprintf("Split from \"%s\"", head_message) - err := c.OSCommand.RunCommand(c.CommitCmdStr(new_message, "")) - if err != nil { - return err - } - - if c.onSuccessfulContinue != nil { - return errors.New("You are midway through another rebase operation. Please abort to start again") - } - - c.PatchManager.Reset() - return c.GenericMergeOrRebaseAction("rebase", "continue") -} diff --git a/pkg/commands/pull_request.go b/pkg/commands/pull_request.go deleted file mode 100644 index 0d2715c0b..000000000 --- a/pkg/commands/pull_request.go +++ /dev/null @@ -1,161 +0,0 @@ -package commands - -import ( - "fmt" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/config" -) - -// Service is a service that repository is on (Github, Bitbucket, ...) -type Service struct { - Name string - pullRequestURLIntoDefaultBranch func(owner string, repository string, from string) string - pullRequestURLIntoTargetBranch func(owner string, repository string, from string, to string) string -} - -// NewService builds a Service based on the host type -func NewService(typeName string, repositoryDomain string, siteDomain string) *Service { - var service *Service - - switch typeName { - case "github": - service = &Service{ - Name: repositoryDomain, - pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string { - return fmt.Sprintf("https://%s/%s/%s/compare/%s?expand=1", siteDomain, owner, repository, from) - }, - pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string { - return fmt.Sprintf("https://%s/%s/%s/compare/%s...%s?expand=1", siteDomain, owner, repository, to, from) - }, - } - case "bitbucket": - service = &Service{ - Name: repositoryDomain, - pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string { - return fmt.Sprintf("https://%s/%s/%s/pull-requests/new?source=%s&t=1", siteDomain, owner, repository, from) - }, - pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string { - return fmt.Sprintf("https://%s/%s/%s/pull-requests/new?source=%s&dest=%s&t=1", siteDomain, owner, repository, from, to) - }, - } - case "gitlab": - service = &Service{ - Name: repositoryDomain, - pullRequestURLIntoDefaultBranch: func(owner string, repository string, from string) string { - return fmt.Sprintf("https://%s/%s/%s/merge_requests/new?merge_request[source_branch]=%s", siteDomain, owner, repository, from) - }, - pullRequestURLIntoTargetBranch: func(owner string, repository string, from string, to string) string { - return fmt.Sprintf("https://%s/%s/%s/merge_requests/new?merge_request[source_branch]=%s&merge_request[target_branch]=%s", siteDomain, owner, repository, from, to) - }, - } - } - - return service -} - -func (s *Service) PullRequestURL(owner string, repository string, from string, to string) string { - if to == "" { - return s.pullRequestURLIntoDefaultBranch(owner, repository, from) - } else { - return s.pullRequestURLIntoTargetBranch(owner, repository, from, to) - } -} - -// PullRequest opens a link in browser to create new pull request -// with selected branch -type PullRequest struct { - GitServices []*Service - GitCommand *GitCommand -} - -// RepoInformation holds some basic information about the repo -type RepoInformation struct { - Owner string - Repository string -} - -func getServices(config config.AppConfigurer) []*Service { - services := []*Service{ - NewService("github", "github.com", "github.com"), - NewService("bitbucket", "bitbucket.org", "bitbucket.org"), - NewService("gitlab", "gitlab.com", "gitlab.com"), - } - - configServices := config.GetUserConfig().Services - - for repoDomain, typeAndDomain := range configServices { - splitData := strings.Split(typeAndDomain, ":") - if len(splitData) != 2 { - // TODO log this misconfiguration - continue - } - - service := NewService(splitData[0], repoDomain, splitData[1]) - if service == nil { - // TODO log this unsupported service - continue - } - - services = append(services, service) - } - - return services -} - -// NewPullRequest creates new instance of PullRequest -func NewPullRequest(gitCommand *GitCommand) *PullRequest { - return &PullRequest{ - GitServices: getServices(gitCommand.Config), - GitCommand: gitCommand, - } -} - -// Create opens link to new pull request in browser -func (pr *PullRequest) Create(from string, to string) (string, error) { - pullRequestURL, err := pr.getPullRequestURL(from, to) - if err != nil { - return "", err - } - - return pullRequestURL, pr.GitCommand.OSCommand.OpenLink(pullRequestURL) -} - -// CopyURL copies the pull request URL to the clipboard -func (pr *PullRequest) CopyURL(from string, to string) (string, error) { - pullRequestURL, err := pr.getPullRequestURL(from, to) - if err != nil { - return "", err - } - - return pullRequestURL, pr.GitCommand.OSCommand.CopyToClipboard(pullRequestURL) -} - -func (pr *PullRequest) getPullRequestURL(from string, to string) (string, error) { - branchExistsOnRemote := pr.GitCommand.CheckRemoteBranchExists(from) - - if !branchExistsOnRemote { - return "", errors.New(pr.GitCommand.Tr.NoBranchOnRemote) - } - - repoURL := pr.GitCommand.GetRemoteURL() - var gitService *Service - - for _, service := range pr.GitServices { - if strings.Contains(repoURL, service.Name) { - gitService = service - break - } - } - - if gitService == nil { - return "", errors.New(pr.GitCommand.Tr.UnsupportedGitService) - } - - repoInfo := GetRepoInfoFromURL(repoURL) - - pullRequestURL := gitService.PullRequestURL(repoInfo.Owner, repoInfo.Repository, from, to) - - return pullRequestURL, nil -} diff --git a/pkg/commands/pull_request_default_test.go b/pkg/commands/pull_request_default_test.go deleted file mode 100644 index e1ce61a5a..000000000 --- a/pkg/commands/pull_request_default_test.go +++ /dev/null @@ -1,256 +0,0 @@ -//go:build !windows -// +build !windows - -package commands - -import ( - "os/exec" - "strings" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestCreatePullRequest is a function. -func TestCreatePullRequest(t *testing.T) { - type scenario struct { - testName string - from string - to string - remoteUrl string - command func(string, ...string) *exec.Cmd - test func(url string, err error) - } - - scenarios := []scenario{ - { - testName: "Opens a link to new pull request on bitbucket", - from: "feature/profile-page", - remoteUrl: "git@bitbucket.org:johndoe/social_network.git", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@bitbucket.org:johndoe/social_network.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1", url) - }, - }, - { - testName: "Opens a link to new pull request on github", - from: "feature/sum-operation", - remoteUrl: "git@github.com:peter/calculator.git", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@github.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature/sum-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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@bitbucket.org:johndoe/social_network.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page/avatar&dest=feature/profile-page&t=1"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page/avatar&dest=feature/profile-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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/remote-events&dest=feature/events&t=1"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/remote-events&dest=feature/events&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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@github.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://github.com/peter/calculator/compare/feature/operations...feature/sum-operation?expand=1"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature/operations...feature/sum-operation?expand=1", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab", - from: "feature/ui", - remoteUrl: "git@gitlab.com:peter/calculator.git", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui", 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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/ui", 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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui", 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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "bash") - assert.Equal(t, args, []string{"-c", `open "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui"`}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui", url) - }, - }, - { - testName: "Throws an error if git service is unsupported", - from: "feature/divide-operation", - remoteUrl: "git@something.com:peter/calculator.git", - command: func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.Error(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCommand := NewDummyGitCommand() - gitCommand.OSCommand.Command = s.command - gitCommand.OSCommand.Platform.OS = "darwin" - gitCommand.OSCommand.Platform.Shell = "bash" - gitCommand.OSCommand.Platform.ShellArg = "-c" - gitCommand.OSCommand.Config.GetUserConfig().OS.OpenLinkCommand = "open {{link}}" - gitCommand.OSCommand.Config.GetUserConfig().Services = map[string]string{ - // valid configuration for a custom service URL - "git.work.com": "gitlab:code.work.com", - // invalid configurations for a custom service URL - "invalid.work.com": "noservice:invalid.work.com", - "noservice.work.com": "noservice.work.com", - } - gitCommand.GitConfig = git_config.NewFakeGitConfig(map[string]string{"remote.origin.url": s.remoteUrl}) - dummyPullRequest := NewPullRequest(gitCommand) - s.test(dummyPullRequest.Create(s.from, s.to)) - }) - } -} diff --git a/pkg/commands/pull_request_test.go b/pkg/commands/pull_request_test.go deleted file mode 100644 index f1283447b..000000000 --- a/pkg/commands/pull_request_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package commands - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestGetRepoInfoFromURL is a function. -func TestGetRepoInfoFromURL(t *testing.T) { - type scenario struct { - testName string - repoURL string - test func(RepoInformation) - } - - scenarios := []scenario{ - { - "Returns repository information for git remote url", - "git@github.com:petersmith/super_calculator", - func(repoInfo RepoInformation) { - assert.EqualValues(t, repoInfo.Owner, "petersmith") - assert.EqualValues(t, repoInfo.Repository, "super_calculator") - }, - }, - { - "Returns repository information for http remote url", - "https://my_username@bitbucket.org/johndoe/social_network.git", - func(repoInfo RepoInformation) { - assert.EqualValues(t, repoInfo.Owner, "johndoe") - assert.EqualValues(t, repoInfo.Repository, "social_network") - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - s.test(GetRepoInfoFromURL(s.repoURL)) - }) - } -} diff --git a/pkg/commands/pull_request_windows_test.go b/pkg/commands/pull_request_windows_test.go deleted file mode 100644 index 73e91f157..000000000 --- a/pkg/commands/pull_request_windows_test.go +++ /dev/null @@ -1,256 +0,0 @@ -//go:build windows -// +build windows - -package commands - -import ( - "os/exec" - "strings" - "testing" - - "github.com/jesseduffield/lazygit/pkg/commands/git_config" - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestCreatePullRequestOnWindows is a function. -func TestCreatePullRequestOnWindows(t *testing.T) { - type scenario struct { - testName string - from string - to string - remoteUrl string - command func(string, ...string) *exec.Cmd - test func(url string, err error) - } - - scenarios := []scenario{ - { - testName: "Opens a link to new pull request on bitbucket", - from: "feature/profile-page", - remoteUrl: "git@bitbucket.org:johndoe/social_network.git", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@bitbucket.org:johndoe/social_network.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page^&t=1"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events^&t=1"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1", url) - }, - }, - { - testName: "Opens a link to new pull request on github", - from: "feature/sum-operation", - remoteUrl: "git@github.com:peter/calculator.git", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@github.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature/sum-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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@bitbucket.org:johndoe/social_network.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page/avatar^&dest=feature/profile-page^&t=1"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page/avatar&dest=feature/profile-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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/remote-events^&dest=feature/events^&t=1"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/remote-events&dest=feature/events&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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@github.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://github.com/peter/calculator/compare/feature/operations...feature/sum-operation?expand=1"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://github.com/peter/calculator/compare/feature/operations...feature/sum-operation?expand=1", url) - }, - }, - { - testName: "Opens a link to new pull request on gitlab", - from: "feature/ui", - remoteUrl: "git@gitlab.com:peter/calculator.git", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui", 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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/ui", 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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui^&merge_request[target_branch]=epic/ui"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui", 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", - command: func(cmd string, args ...string) *exec.Cmd { - // Handle git remote url call - if strings.HasPrefix(cmd, "git") { - return secureexec.Command("echo", "git@gitlab.com:peter/calculator.git") - } - - assert.Equal(t, cmd, "cmd") - assert.Equal(t, args, []string{"/c", "start", "", "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui^&merge_request[target_branch]=epic/ui"}) - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.NoError(t, err) - assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature/commit-ui&merge_request[target_branch]=epic/ui", url) - }, - }, - { - testName: "Throws an error if git service is unsupported", - from: "feature/divide-operation", - remoteUrl: "git@something.com:peter/calculator.git", - command: func(cmd string, args ...string) *exec.Cmd { - return secureexec.Command("echo") - }, - test: func(url string, err error) { - assert.Error(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCommand := NewDummyGitCommand() - gitCommand.OSCommand.Command = s.command - gitCommand.OSCommand.Platform.OS = "windows" - gitCommand.OSCommand.Platform.Shell = "cmd" - gitCommand.OSCommand.Platform.ShellArg = "/c" - gitCommand.OSCommand.Config.GetUserConfig().OS.OpenLinkCommand = `start "" {{link}}` - gitCommand.OSCommand.Config.GetUserConfig().Services = map[string]string{ - // valid configuration for a custom service URL - "git.work.com": "gitlab:code.work.com", - // invalid configurations for a custom service URL - "invalid.work.com": "noservice:invalid.work.com", - "noservice.work.com": "noservice.work.com", - } - gitCommand.GitConfig = git_config.NewFakeGitConfig(map[string]string{"remote.origin.url": s.remoteUrl}) - dummyPullRequest := NewPullRequest(gitCommand) - s.test(dummyPullRequest.Create(s.from, s.to)) - }) - } -} diff --git a/pkg/commands/rebasing.go b/pkg/commands/rebasing.go deleted file mode 100644 index 63c568036..000000000 --- a/pkg/commands/rebasing.go +++ /dev/null @@ -1,289 +0,0 @@ -package commands - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/mgutz/str" -) - -func (c *GitCommand) RewordCommit(commits []*models.Commit, index int) (*exec.Cmd, error) { - todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, "reword") - if err != nil { - return nil, err - } - - return c.PrepareInteractiveRebaseCommand(sha, todo, false) -} - -func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error { - // we must ensure that we have at least two commits after the selected one - if len(commits) <= index+2 { - // assuming they aren't picking the bottom commit - return errors.New(c.Tr.NoRoom) - } - - todo := "" - orderedCommits := append(commits[0:index], commits[index+1], commits[index]) - for _, commit := range orderedCommits { - todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo - } - - cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true) - if err != nil { - return err - } - - return c.OSCommand.RunPreparedCommand(cmd) -} - -func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, action string) error { - todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, action) - if err != nil { - return err - } - - cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true) - if err != nil { - return err - } - - return c.OSCommand.RunPreparedCommand(cmd) -} - -// 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 a todo string to write to the todo file -func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) (*exec.Cmd, error) { - ex := c.OSCommand.GetLazygitPath() - - debug := "FALSE" - if c.OSCommand.Config.GetDebug() { - debug = "TRUE" - } - - cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha) - c.Log.WithField("command", cmdStr).Info("RunCommand") - splitCmd := str.ToArgv(cmdStr) - - cmd := c.OSCommand.Command(splitCmd[0], splitCmd[1:]...) - - gitSequenceEditor := ex - if todo == "" { - gitSequenceEditor = "true" - } else { - c.OSCommand.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false) - } - - cmd.Env = os.Environ() - cmd.Env = append( - cmd.Env, - "LAZYGIT_CLIENT_COMMAND=INTERACTIVE_REBASE", - "LAZYGIT_REBASE_TODO="+todo, - "DEBUG="+debug, - "LANG=en_US.UTF-8", // Force using EN as language - "LC_ALL=en_US.UTF-8", // Force using EN as language - "GIT_SEQUENCE_EDITOR="+gitSequenceEditor, - ) - - if overrideEditor { - cmd.Env = append(cmd.Env, "GIT_EDITOR="+ex) - } - - return cmd, nil -} - -func (c *GitCommand) GenerateGenericRebaseTodo(commits []*models.Commit, actionIndex int, action string) (string, string, error) { - baseIndex := actionIndex + 1 - - if len(commits) <= baseIndex { - return "", "", errors.New(c.Tr.CannotRebaseOntoFirstCommit) - } - - if action == "squash" || action == "fixup" { - baseIndex++ - - if len(commits) <= baseIndex { - return "", "", errors.New(c.Tr.CannotSquashOntoSecondCommit) - } - } - - todo := "" - for i, commit := range commits[0:baseIndex] { - var commitAction string - if i == actionIndex { - commitAction = action - } else if commit.IsMerge() { - // your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary! - // doing this means we don't need to worry about rebasing over merges which always causes problems. - // you typically shouldn't be doing rebases that pass over merge commits anyway. - commitAction = "drop" - } else { - commitAction = "pick" - } - todo = commitAction + " " + commit.Sha + " " + commit.Name + "\n" + todo - } - - return todo, commits[baseIndex].Sha, nil -} - -// AmendTo amends the given commit with whatever files are staged -func (c *GitCommand) AmendTo(sha string) error { - if err := c.CreateFixupCommit(sha); err != nil { - return err - } - - return c.SquashAllAboveFixupCommits(sha) -} - -// EditRebaseTodo sets the action at a given index in the git-rebase-todo file -func (c *GitCommand) EditRebaseTodo(index int, action string) error { - fileName := filepath.Join(c.DotGitDir, "rebase-merge/git-rebase-todo") - bytes, err := ioutil.ReadFile(fileName) - if err != nil { - return err - } - - content := strings.Split(string(bytes), "\n") - commitCount := c.getTodoCommitCount(content) - - // we have the most recent commit at the bottom whereas the todo file has - // it at the bottom, so we need to subtract our index from the commit count - contentIndex := commitCount - 1 - index - splitLine := strings.Split(content[contentIndex], " ") - content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ") - result := strings.Join(content, "\n") - - return ioutil.WriteFile(fileName, []byte(result), 0644) -} - -func (c *GitCommand) getTodoCommitCount(content []string) int { - // count lines that are not blank and are not comments - commitCount := 0 - for _, line := range content { - if line != "" && !strings.HasPrefix(line, "#") { - commitCount++ - } - } - return commitCount -} - -// MoveTodoDown moves a rebase todo item down by one position -func (c *GitCommand) MoveTodoDown(index int) error { - fileName := filepath.Join(c.DotGitDir, "rebase-merge/git-rebase-todo") - bytes, err := ioutil.ReadFile(fileName) - if err != nil { - return err - } - - content := strings.Split(string(bytes), "\n") - commitCount := c.getTodoCommitCount(content) - contentIndex := commitCount - 1 - index - - rearrangedContent := append(content[0:contentIndex-1], content[contentIndex], content[contentIndex-1]) - rearrangedContent = append(rearrangedContent, content[contentIndex+1:]...) - result := strings.Join(rearrangedContent, "\n") - - return ioutil.WriteFile(fileName, []byte(result), 0644) -} - -// SquashAllAboveFixupCommits squashes all fixup! commits above the given one -func (c *GitCommand) SquashAllAboveFixupCommits(sha string) error { - return c.runSkipEditorCommand( - fmt.Sprintf( - "git rebase --interactive --autostash --autosquash %s^", - sha, - ), - ) -} - -// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current -// commit and pick all others. After this you'll want to call `c.GenericMergeOrRebaseAction("rebase", "continue")` -func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*models.Commit, commitIndex int) error { - if len(commits)-1 < commitIndex { - 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 c.UsingGpg() { - return errors.New(c.Tr.DisabledForGPG) - } - - todo, sha, err := c.GenerateGenericRebaseTodo(commits, commitIndex, "edit") - if err != nil { - return err - } - - cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true) - if err != nil { - return err - } - - if err := c.OSCommand.RunPreparedCommand(cmd); err != nil { - return err - } - - return nil -} - -// RebaseBranch interactive rebases onto a branch -func (c *GitCommand) RebaseBranch(branchName string) error { - cmd, err := c.PrepareInteractiveRebaseCommand(branchName, "", false) - if err != nil { - return err - } - - return c.OSCommand.RunPreparedCommand(cmd) -} - -// 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 (c *GitCommand) GenericMergeOrRebaseAction(commandType string, command string) error { - err := c.runSkipEditorCommand( - fmt.Sprintf( - "git %s --%s", - commandType, - command, - ), - ) - if err != nil { - if !strings.Contains(err.Error(), "no rebase in progress") { - return err - } - c.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" && c.onSuccessfulContinue != nil { - f := c.onSuccessfulContinue - c.onSuccessfulContinue = nil - return f() - } - if command == "abort" { - c.onSuccessfulContinue = nil - } - return nil -} - -func (c *GitCommand) runSkipEditorCommand(command string) error { - cmd := c.OSCommand.ExecutableFromString(command) - lazyGitPath := c.OSCommand.GetLazygitPath() - cmd.Env = append( - cmd.Env, - "LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY", - "GIT_EDITOR="+lazyGitPath, - "EDITOR="+lazyGitPath, - "VISUAL="+lazyGitPath, - ) - return c.OSCommand.RunExecutable(cmd) -} diff --git a/pkg/commands/rebasing_test.go b/pkg/commands/rebasing_test.go deleted file mode 100644 index 7c4a954c9..000000000 --- a/pkg/commands/rebasing_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package commands - -import ( - "os/exec" - "regexp" - "testing" - - "github.com/jesseduffield/lazygit/pkg/test" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandRebaseBranch is a function. -func TestGitCommandRebaseBranch(t *testing.T) { - type scenario struct { - testName string - arg string - command func(string, ...string) *exec.Cmd - test func(error) - } - - scenarios := []scenario{ - { - "successful rebase", - "master", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: "git rebase --interactive --autostash --keep-empty master", - Replace: "echo", - }, - }), - func(err error) { - assert.NoError(t, err) - }, - }, - { - "unsuccessful rebase", - "master", - test.CreateMockCommand(t, []*test.CommandSwapper{ - { - Expect: "git rebase --interactive --autostash --keep-empty master", - Replace: "test", - }, - }), - func(err error) { - assert.Error(t, err) - }, - }, - } - - gitCmd := NewDummyGitCommand() - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd.OSCommand.Command = s.command - s.test(gitCmd.RebaseBranch(s.arg)) - }) - } -} - -// TestGitCommandSkipEditorCommand confirms that SkipEditorCommand injects -// environment variables that suppress an interactive editor -func TestGitCommandSkipEditorCommand(t *testing.T) { - cmd := NewDummyGitCommand() - - cmd.OSCommand.SetBeforeExecuteCmd(func(cmd *exec.Cmd) { - test.AssertContainsMatch( - t, - cmd.Env, - regexp.MustCompile("^VISUAL="), - "expected VISUAL to be set for a non-interactive external command", - ) - - test.AssertContainsMatch( - t, - cmd.Env, - regexp.MustCompile("^EDITOR="), - "expected EDITOR to be set for a non-interactive external command", - ) - - test.AssertContainsMatch( - t, - cmd.Env, - regexp.MustCompile("^GIT_EDITOR="), - "expected GIT_EDITOR to be set for a non-interactive external command", - ) - - test.AssertContainsMatch( - t, - cmd.Env, - regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"), - "expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command", - ) - }) - - _ = cmd.runSkipEditorCommand("true") -} diff --git a/pkg/commands/remotes.go b/pkg/commands/remotes.go deleted file mode 100644 index d25798d32..000000000 --- a/pkg/commands/remotes.go +++ /dev/null @@ -1,101 +0,0 @@ -package commands - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" -) - -func (c *GitCommand) AddRemote(name string, url string) error { - return c.RunCommand("git remote add %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(url)) -} - -func (c *GitCommand) RemoveRemote(name string) error { - return c.RunCommand("git remote remove %s", c.OSCommand.Quote(name)) -} - -func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error { - return c.RunCommand("git remote rename %s %s", c.OSCommand.Quote(oldRemoteName), c.OSCommand.Quote(newRemoteName)) -} - -func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error { - return c.RunCommand("git remote set-url %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(updatedUrl)) -} - -func (c *GitCommand) DeleteRemoteBranch(remoteName string, branchName string, promptUserForCredential func(string) string) error { - command := fmt.Sprintf("git push %s --delete %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(branchName)) - cmdObj := c.NewCmdObjFromStr(command) - return c.DetectUnamePass(cmdObj, promptUserForCredential) -} - -func (c *GitCommand) DetectUnamePass(cmdObj oscommands.ICmdObj, promptUserForCredential func(string) string) error { - return c.OSCommand.DetectUnamePass(cmdObj, c.GetCmdWriter(), promptUserForCredential) -} - -// CheckRemoteBranchExists Returns remote branch -func (c *GitCommand) CheckRemoteBranchExists(branchName string) bool { - _, err := c.OSCommand.RunCommandWithOutput( - "git show-ref --verify -- refs/remotes/origin/%s", - c.OSCommand.Quote(branchName), - ) - - return err == nil -} - -// GetRemoteURL returns current repo remote url -func (c *GitCommand) GetRemoteURL() string { - return c.GitConfig.Get("remote.origin.url") -} - -func GetRepoInfoFromURL(url string) RepoInformation { - isHTTP := strings.HasPrefix(url, "http") - - if isHTTP { - splits := strings.Split(url, "/") - owner := strings.Join(splits[3:len(splits)-1], "/") - repo := strings.TrimSuffix(splits[len(splits)-1], ".git") - - return RepoInformation{ - Owner: owner, - Repository: repo, - } - } - - tmpSplit := strings.Split(url, ":") - splits := strings.Split(tmpSplit[1], "/") - owner := strings.Join(splits[0:len(splits)-1], "/") - repo := strings.TrimSuffix(splits[len(splits)-1], ".git") - - return RepoInformation{ - Owner: owner, - Repository: repo, - } -} - -func (c *GitCommand) GetRemotesToOwnersMap(remotes []*models.Remote) (map[string]string, error) { - res := map[string]string{} - for _, remote := range remotes { - if len(remote.Urls) == 0 { - continue - } - - res[remote.Name] = GetRepoInfoFromURL(remote.Urls[0]).Owner - } - return res, nil -} - -func (c *GitCommand) GetRemotesToRepositoryMap(remotes []*models.Remote) (map[string]string, error) { - res := map[string]string{} - for _, remote := range remotes { - if len(remote.Urls) == 0 { - continue - } - - info := GetRepoInfoFromURL(remote.Urls[0]) - res[info.Owner] = info.Repository - } - - return res, nil -} diff --git a/pkg/commands/stash_entries.go b/pkg/commands/stash_entries.go deleted file mode 100644 index 2707903d7..000000000 --- a/pkg/commands/stash_entries.go +++ /dev/null @@ -1,58 +0,0 @@ -package commands - -import "fmt" - -// StashDo modify stash -func (c *GitCommand) StashDo(index int, method string) error { - return c.RunCommand("git stash %s stash@{%d}", method, index) -} - -// StashSave save stash -// TODO: before calling this, check if there is anything to save -func (c *GitCommand) StashSave(message string) error { - return c.RunCommand("git stash save %s", c.OSCommand.Quote(message)) -} - -// GetStashEntryDiff stash diff -func (c *GitCommand) ShowStashEntryCmdStr(index int) string { - return fmt.Sprintf("git stash show -p --stat --color=%s stash@{%d}", c.colorArg(), index) -} - -// StashSaveStagedChanges stashes only the currently staged changes. This takes a few steps -// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible -func (c *GitCommand) StashSaveStagedChanges(message string) error { - // wrap in 'writing', which uses a mutex - if err := c.RunCommand("git stash --keep-index"); err != nil { - return err - } - - if err := c.StashSave(message); err != nil { - return err - } - - if err := c.RunCommand("git stash apply stash@{1}"); err != nil { - return err - } - - if err := c.OSCommand.PipeCommands("git stash show -p", "git apply -R"); err != nil { - return err - } - - if err := c.RunCommand("git stash drop stash@{1}"); 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 := c.GetStatusFiles(GetStatusFileOptions{}) - for _, file := range files { - if file.ShortStatus == "AD" { - if err := c.UnStageFile(file.Names(), false); err != nil { - return err - } - } - } - - return nil -} diff --git a/pkg/commands/stash_entries_test.go b/pkg/commands/stash_entries_test.go deleted file mode 100644 index bb4778dc6..000000000 --- a/pkg/commands/stash_entries_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package commands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandStashDo is a function. -func TestGitCommandStashDo(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"stash", "drop", "stash@{1}"}, args) - - return secureexec.Command("echo") - } - - assert.NoError(t, gitCmd.StashDo(1, "drop")) -} - -// TestGitCommandStashSave is a function. -func TestGitCommandStashSave(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"stash", "save", "A stash message"}, args) - - return secureexec.Command("echo") - } - - assert.NoError(t, gitCmd.StashSave("A stash message")) -} diff --git a/pkg/commands/status.go b/pkg/commands/status.go deleted file mode 100644 index c11fe829f..000000000 --- a/pkg/commands/status.go +++ /dev/null @@ -1,55 +0,0 @@ -package commands - -import ( - "path/filepath" - - gogit "github.com/jesseduffield/go-git/v5" -) - -const ( - REBASE_MODE_NORMAL = "normal" - REBASE_MODE_INTERACTIVE = "interactive" - REBASE_MODE_REBASING = "rebasing" - REBASE_MODE_MERGING = "merging" -) - -// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase -// and "interactive" for interactive rebase -func (c *GitCommand) RebaseMode() (string, error) { - exists, err := c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "rebase-apply")) - if err != nil { - return "", err - } - if exists { - return REBASE_MODE_NORMAL, nil - } - exists, err = c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "rebase-merge")) - if exists { - return REBASE_MODE_INTERACTIVE, err - } else { - return "", err - } -} - -func (c *GitCommand) WorkingTreeState() string { - rebaseMode, _ := c.RebaseMode() - if rebaseMode != "" { - return REBASE_MODE_REBASING - } - merging, _ := c.IsInMergeState() - if merging { - return REBASE_MODE_MERGING - } - return REBASE_MODE_NORMAL -} - -// IsInMergeState states whether we are still mid-merge -func (c *GitCommand) IsInMergeState() (bool, error) { - return c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "MERGE_HEAD")) -} - -func (c *GitCommand) IsBareRepo() bool { - // note: could use `git rev-parse --is-bare-repository` if we wanna drop go-git - _, err := c.Repo.Worktree() - return err == gogit.ErrIsBareRepository -} diff --git a/pkg/commands/submodules.go b/pkg/commands/submodules.go deleted file mode 100644 index 45e830d10..000000000 --- a/pkg/commands/submodules.go +++ /dev/null @@ -1,165 +0,0 @@ -package commands - -import ( - "bufio" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/models" -) - -// .gitmodules looks like this: -// [submodule "mysubmodule"] -// path = blah/mysubmodule -// url = git@github.com:subbo.git - -func (c *GitCommand) GetSubmoduleConfigs() ([]*models.SubmoduleConfig, error) { - file, err := os.Open(".gitmodules") - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - - 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 - } else { - return "", false - } - } - - configs := []*models.SubmoduleConfig{} - for scanner.Scan() { - line := scanner.Text() - - if name, ok := firstMatch(line, `\[submodule "(.*)"\]`); ok { - configs = append(configs, &models.SubmoduleConfig{Name: name}) - continue - } - - if len(configs) > 0 { - lastConfig := configs[len(configs)-1] - - if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok { - lastConfig.Path = path - } else if url, ok := firstMatch(line, `\s*url\s*=\s*(.*)\s*`); ok { - lastConfig.Url = url - } - } - } - - return configs, nil -} - -func (c *GitCommand) SubmoduleStash(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) { - c.Log.Infof("submodule path %s does not exist, returning", submodule.Path) - return nil - } - - return c.RunCommand("git -C %s stash --include-untracked", c.OSCommand.Quote(submodule.Path)) -} - -func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error { - return c.RunCommand("git submodule update --init --force -- %s", c.OSCommand.Quote(submodule.Path)) -} - -func (c *GitCommand) SubmoduleUpdateAll() error { - // not doing an --init here because the user probably doesn't want that - return c.RunCommand("git submodule update --force") -} - -func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error { - // based on https://gist.github.com/myusuf3/7f645819ded92bda6677 - - if err := c.RunCommand("git submodule deinit --force -- %s", c.OSCommand.Quote(submodule.Path)); err != nil { - if strings.Contains(err.Error(), "did not match any file(s) known to git") { - if err := c.RunCommand("git config --file .gitmodules --remove-section submodule.%s", c.OSCommand.Quote(submodule.Name)); err != nil { - return err - } - - if err := c.RunCommand("git config --remove-section submodule.%s", c.OSCommand.Quote(submodule.Name)); err != nil { - return err - } - - // if there's an error here about it not existing then we'll just continue to do `git rm` - } else { - return err - } - } - - if err := c.RunCommand("git rm --force -r %s", submodule.Path); err != nil { - // if the directory isn't there then that's fine - c.Log.Error(err) - } - - return os.RemoveAll(filepath.Join(c.DotGitDir, "modules", submodule.Path)) -} - -func (c *GitCommand) SubmoduleAdd(name string, path string, url string) error { - return c.OSCommand.RunCommand( - "git submodule add --force --name %s -- %s %s ", - c.OSCommand.Quote(name), - c.OSCommand.Quote(url), - c.OSCommand.Quote(path), - ) -} - -func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) error { - // the set-url command is only for later git versions so we're doing it manually here - if err := c.RunCommand("git config --file .gitmodules submodule.%s.url %s", c.OSCommand.Quote(name), c.OSCommand.Quote(newUrl)); err != nil { - return err - } - - if err := c.RunCommand("git submodule sync -- %s", c.OSCommand.Quote(path)); err != nil { - return err - } - - return nil -} - -func (c *GitCommand) SubmoduleInit(path string) error { - return c.RunCommand("git submodule init -- %s", c.OSCommand.Quote(path)) -} - -func (c *GitCommand) SubmoduleUpdate(path string) error { - return c.RunCommand("git submodule update --init -- %s", c.OSCommand.Quote(path)) -} - -func (c *GitCommand) SubmoduleBulkInitCmdStr() string { - return "git submodule init" -} - -func (c *GitCommand) SubmoduleBulkUpdateCmdStr() string { - return "git submodule update" -} - -func (c *GitCommand) SubmoduleForceBulkUpdateCmdStr() string { - return "git submodule update --force" -} - -func (c *GitCommand) SubmoduleBulkDeinitCmdStr() string { - return "git submodule deinit --all --force" -} - -func (c *GitCommand) ResetSubmodules(submodules []*models.SubmoduleConfig) error { - for _, submodule := range submodules { - if err := c.SubmoduleStash(submodule); err != nil { - return err - } - } - - return c.SubmoduleUpdateAll() -} diff --git a/pkg/commands/sync.go b/pkg/commands/sync.go deleted file mode 100644 index 35f258f4b..000000000 --- a/pkg/commands/sync.go +++ /dev/null @@ -1,111 +0,0 @@ -package commands - -import ( - "fmt" - - "github.com/go-errors/errors" -) - -// Push pushes to a branch -type PushOpts struct { - Force bool - UpstreamRemote string - UpstreamBranch string - SetUpstream bool - PromptUserForCredential func(string) string -} - -func (c *GitCommand) Push(opts PushOpts) error { - cmdStr := "git push" - - if opts.Force { - cmdStr += " --force-with-lease" - } - - if opts.SetUpstream { - cmdStr += " --set-upstream" - } - - if opts.UpstreamRemote != "" { - cmdStr += " " + c.OSCommand.Quote(opts.UpstreamRemote) - } - - if opts.UpstreamBranch != "" { - if opts.UpstreamRemote == "" { - return errors.New(c.Tr.MustSpecifyOriginError) - } - cmdStr += " " + c.OSCommand.Quote(opts.UpstreamBranch) - } - - cmdObj := c.NewCmdObjFromStr(cmdStr) - return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential) -} - -type FetchOptions struct { - PromptUserForCredential func(string) string - RemoteName string - BranchName string -} - -// Fetch fetch git repo -func (c *GitCommand) Fetch(opts FetchOptions) error { - cmdStr := "git fetch" - - if opts.RemoteName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.RemoteName)) - } - if opts.BranchName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.BranchName)) - } - - cmdObj := c.NewCmdObjFromStr(cmdStr) - return c.DetectUnamePass(cmdObj, func(question string) string { - if opts.PromptUserForCredential != nil { - return opts.PromptUserForCredential(question) - } - return "\n" - }) -} - -type PullOptions struct { - PromptUserForCredential func(string) string - RemoteName string - BranchName string - FastForwardOnly bool -} - -func (c *GitCommand) Pull(opts PullOptions) error { - if opts.PromptUserForCredential == nil { - return errors.New("PromptUserForCredential is required") - } - - cmdStr := "git pull --no-edit" - - if opts.FastForwardOnly { - cmdStr += " --ff-only" - } - - if opts.RemoteName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.RemoteName)) - } - if opts.BranchName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.BranchName)) - } - - // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user - // has 'pull.rebase = interactive' configured. - cmdObj := c.NewCmdObjFromStr(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:") - return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential) -} - -func (c *GitCommand) FastForward(branchName string, remoteName string, remoteBranchName string, promptUserForCredential func(string) string) error { - cmdStr := fmt.Sprintf("git fetch %s %s:%s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName)) - cmdObj := c.NewCmdObjFromStr(cmdStr) - return c.DetectUnamePass(cmdObj, promptUserForCredential) -} - -func (c *GitCommand) FetchRemote(remoteName string, promptUserForCredential func(string) string) error { - cmdStr := fmt.Sprintf("git fetch %s", c.OSCommand.Quote(remoteName)) - cmdObj := c.NewCmdObjFromStr(cmdStr) - return c.DetectUnamePass(cmdObj, promptUserForCredential) -} diff --git a/pkg/commands/sync_test.go b/pkg/commands/sync_test.go deleted file mode 100644 index 924bd56ef..000000000 --- a/pkg/commands/sync_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package commands - -import ( - "os/exec" - "testing" - - "github.com/jesseduffield/lazygit/pkg/secureexec" - "github.com/stretchr/testify/assert" -) - -// TestGitCommandPush is a function. -func TestGitCommandPush(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - opts PushOpts - test func(error) - } - - prompt := func(passOrUname string) string { - return "\n" - } - - scenarios := []scenario{ - { - "Push with force disabled", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"push"}, args) - - return secureexec.Command("echo") - }, - PushOpts{Force: false, PromptUserForCredential: prompt}, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Push with force enabled", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"push", "--force-with-lease"}, args) - - return secureexec.Command("echo") - }, - PushOpts{Force: true, PromptUserForCredential: prompt}, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Push with an error occurring", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"push"}, args) - return secureexec.Command("test") - }, - PushOpts{Force: false, PromptUserForCredential: prompt}, - func(err error) { - assert.Error(t, err) - }, - }, - { - "Push with force disabled, upstream supplied", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"push", "origin", "master"}, args) - - return secureexec.Command("echo") - }, - PushOpts{ - Force: false, - UpstreamRemote: "origin", - UpstreamBranch: "master", - PromptUserForCredential: prompt, - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Push with force disabled, setting upstream", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"push", "--set-upstream", "origin", "master"}, args) - - return secureexec.Command("echo") - }, - PushOpts{ - Force: false, - UpstreamRemote: "origin", - UpstreamBranch: "master", - PromptUserForCredential: prompt, - SetUpstream: true, - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Push with force enabled, setting upstream", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"push", "--force-with-lease", "--set-upstream", "origin", "master"}, args) - - return secureexec.Command("echo") - }, - PushOpts{ - Force: true, - UpstreamRemote: "origin", - UpstreamBranch: "master", - PromptUserForCredential: prompt, - SetUpstream: true, - }, - func(err error) { - assert.NoError(t, err) - }, - }, - { - "Push with remote branch but no origin", - func(cmd string, args ...string) *exec.Cmd { - return nil - }, - PushOpts{ - Force: true, - UpstreamRemote: "", - UpstreamBranch: "master", - PromptUserForCredential: prompt, - SetUpstream: true, - }, - func(err error) { - assert.Error(t, err) - assert.EqualValues(t, "Must specify a remote if specifying a branch", err.Error()) - }, - }, - { - "Push with force disabled, upstream supplied", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"push", "origin", "master"}, args) - - return secureexec.Command("echo") - }, - PushOpts{ - Force: false, - UpstreamRemote: "origin", - UpstreamBranch: "master", - PromptUserForCredential: prompt, - }, - func(err error) { - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := NewDummyGitCommand() - gitCmd.OSCommand.Command = s.command - err := gitCmd.Push(s.opts) - s.test(err) - }) - } -} diff --git a/pkg/commands/tags.go b/pkg/commands/tags.go deleted file mode 100644 index 9aa327dd3..000000000 --- a/pkg/commands/tags.go +++ /dev/null @@ -1,19 +0,0 @@ -package commands - -import ( - "fmt" -) - -func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error { - return c.RunCommand("git tag -- %s %s", c.OSCommand.Quote(tagName), commitSha) -} - -func (c *GitCommand) DeleteTag(tagName string) error { - return c.RunCommand("git tag -d %s", c.OSCommand.Quote(tagName)) -} - -func (c *GitCommand) PushTag(remoteName string, tagName string, promptUserForCredential func(string) string) error { - cmdStr := fmt.Sprintf("git push %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(tagName)) - cmdObj := c.NewCmdObjFromStr(cmdStr) - return c.DetectUnamePass(cmdObj, promptUserForCredential) -} diff --git a/pkg/commands/types/enums/enums.go b/pkg/commands/types/enums/enums.go new file mode 100644 index 000000000..7e8a81798 --- /dev/null +++ b/pkg/commands/types/enums/enums.go @@ -0,0 +1,14 @@ +package enums + +type RebaseMode int + +const ( + // this means we're neither rebasing nor merging + REBASE_MODE_NONE RebaseMode = iota + // this means normal rebase as opposed to interactive rebase + REBASE_MODE_NORMAL + REBASE_MODE_INTERACTIVE + // REBASE_MODE_REBASING is a general state that captures both REBASE_MODE_NORMAL and REBASE_MODE_INTERACTIVE + REBASE_MODE_REBASING + REBASE_MODE_MERGING +) diff --git a/pkg/common/common.go b/pkg/common/common.go new file mode 100644 index 000000000..e8251a85d --- /dev/null +++ b/pkg/common/common.go @@ -0,0 +1,15 @@ +package common + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/sirupsen/logrus" +) + +// Commonly used things wrapped into one struct for convenience when passing it around +type Common struct { + Log *logrus.Entry + Tr *i18n.TranslationSet + UserConfig *config.UserConfig + Debug bool +} diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index b1b1acb0c..98620ad43 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -31,21 +31,21 @@ type AppConfig struct { // from AppConfig and still be used by lazygit. type AppConfigurer interface { GetDebug() bool + + // build info GetVersion() string GetCommit() string GetBuildDate() string GetName() string GetBuildSource() string + GetUserConfig() *UserConfig GetUserConfigPaths() []string GetUserConfigDir() string - GetTempDir() string + ReloadUserConfig() error + GetAppState() *AppState SaveAppState() error - SetIsNewRepo(bool) - GetIsNewRepo() bool - ReloadUserConfig() error - ShowCommandLogOnStartup() bool } // NewAppConfig makes a new app config @@ -167,37 +167,22 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error) return base, nil } -// GetIsNewRepo returns known repo boolean -func (c *AppConfig) GetIsNewRepo() bool { - return c.IsNewRepo -} - -// SetIsNewRepo set if the current repo is known -func (c *AppConfig) SetIsNewRepo(toSet bool) { - c.IsNewRepo = toSet -} - -// GetDebug returns debug flag func (c *AppConfig) GetDebug() bool { return c.Debug } -// GetVersion returns debug flag func (c *AppConfig) GetVersion() string { return c.Version } -// GetCommit returns debug flag func (c *AppConfig) GetCommit() string { return c.Commit } -// GetBuildDate returns debug flag func (c *AppConfig) GetBuildDate() string { return c.BuildDate } -// GetName returns debug flag func (c *AppConfig) GetName() string { return c.Name } @@ -226,10 +211,6 @@ func (c *AppConfig) GetUserConfigDir() string { return c.UserConfigDir } -func (c *AppConfig) GetTempDir() string { - return c.TempDir -} - func (c *AppConfig) ReloadUserConfig() error { userConfig, err := loadUserConfigWithDefaults(c.UserConfigPaths) if err != nil { @@ -277,17 +258,6 @@ func (c *AppConfig) SaveAppState() error { return err } -// 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 -func (c *AppConfig) ShowCommandLogOnStartup() bool { - if !c.UserConfig.Gui.ShowCommandLog { - return false - } - - return !c.AppState.HideCommandLog -} - // loadAppState loads recorded AppState from file func loadAppState() (*AppState, error) { filepath, err := configFilePath("state.yml") diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index e6c999794..5d03daba0 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -25,6 +25,7 @@ type RefresherConfig struct { type GuiConfig struct { AuthorColors map[string]string `yaml:"authorColors"` + BranchColors map[string]string `yaml:"branchColors"` ScrollHeight int `yaml:"scrollHeight"` ScrollPastBottom bool `yaml:"scrollPastBottom"` MouseEvents bool `yaml:"mouseEvents"` @@ -61,6 +62,7 @@ type CommitLengthConfig struct { type GitConfig struct { Paging PagingConfig `yaml:"paging"` + Commit CommitConfig `yaml:"commit"` Merging MergingConfig `yaml:"merging"` SkipHookPrefix string `yaml:"skipHookPrefix"` AutoFetch bool `yaml:"autoFetch"` @@ -73,6 +75,7 @@ type GitConfig struct { ParseEmoji bool `yaml:"parseEmoji"` Log LogConfig `yaml:"log"` EnableGhCommand bool `yaml:"enableGhCommand"` + DiffContextSize int `yaml:"diffContextSize"` } type PagingConfig struct { @@ -81,6 +84,10 @@ type PagingConfig struct { UseConfig bool `yaml:"useConfig"` } +type CommitConfig struct { + SignOff bool `yaml:"signOff"` +} + type MergingConfig struct { ManualCommit bool `yaml:"manualCommit"` Args string `yaml:"args"` @@ -177,6 +184,8 @@ type KeybindingUniversalConfig struct { AppendNewline string `yaml:"appendNewline"` ExtrasMenu string `yaml:"extrasMenu"` ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"` + IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"` + DecreaseContextInDiffView string `yaml:"decreaseContextInDiffView"` } type KeybindingStatusConfig struct { @@ -239,6 +248,8 @@ type KeybindingCommitsConfig struct { ResetCherryPick string `yaml:"resetCherryPick"` CopyCommitMessageToClipboard string `yaml:"copyCommitMessageToClipboard"` OpenLogMenu string `yaml:"openLogMenu"` + OpenInBrowser string `yaml:"openInBrowser"` + ViewBisectOptions string `yaml:"viewBisectOptions"` } type KeybindingStashConfig struct { @@ -285,6 +296,7 @@ type CustomCommand struct { Prompts []CustomCommandPrompt `yaml:"prompts"` LoadingText string `yaml:"loadingText"` Description string `yaml:"description"` + Stream bool `yaml:"stream"` } type CustomCommandPrompt struct { @@ -317,7 +329,7 @@ func GetDefaultConfig() *UserConfig { ScrollPastBottom: true, MouseEvents: true, SkipUnstageLineWarning: false, - SkipStashWarning: true, + SkipStashWarning: false, SidePanelWidth: 0.3333, ExpandFocusedSidePanel: false, MainPanelSplitMode: "flexible", @@ -345,6 +357,9 @@ func GetDefaultConfig() *UserConfig { ColorArg: "always", Pager: "", UseConfig: false}, + Commit: CommitConfig{ + SignOff: false, + }, Merging: MergingConfig{ ManualCommit: false, Args: "", @@ -360,7 +375,7 @@ func GetDefaultConfig() *UserConfig { DisableForcePushing: false, CommitPrefixes: map[string]CommitPrefixConfig(nil), ParseEmoji: false, - EnableGhCommand: false, + DiffContextSize: 3, }, Refresher: RefresherConfig{ RefreshInterval: 10, @@ -438,6 +453,8 @@ func GetDefaultConfig() *UserConfig { AppendNewline: "", ExtrasMenu: "@", ToggleWhitespaceInDiffView: "", + IncreaseContextInDiffView: "}", + DecreaseContextInDiffView: "{", }, Status: KeybindingStatusConfig{ CheckForUpdate: "u", @@ -496,6 +513,8 @@ func GetDefaultConfig() *UserConfig { ResetCherryPick: "", CopyCommitMessageToClipboard: "", OpenLogMenu: "", + OpenInBrowser: "o", + ViewBisectOptions: "b", }, Stash: KeybindingStashConfig{ PopStash: "g", diff --git a/pkg/gui/app_status_manager.go b/pkg/gui/app_status_manager.go index 1fcad4f30..e625fcad2 100644 --- a/pkg/gui/app_status_manager.go +++ b/pkg/gui/app_status_manager.go @@ -4,7 +4,6 @@ import ( "sync" "time" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -96,11 +95,13 @@ func (gui *Gui) renderAppStatus() { defer ticker.Stop() for range ticker.C { appStatus := gui.statusManager.getStatusString() + gui.OnUIThread(func() error { + return gui.renderString(gui.Views.AppStatus, appStatus) + }) + if appStatus == "" { - gui.renderString(gui.Views.AppStatus, "") return } - gui.renderString(gui.Views.AppStatus, appStatus) } }) } @@ -117,7 +118,7 @@ func (gui *Gui) WithWaitingStatus(message string, f func() error) error { gui.renderAppStatus() if err := f(); err != nil { - gui.g.Update(func(g *gocui.Gui) error { + gui.OnUIThread(func() error { return gui.surfaceError(err) }) } diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go index db1c3c172..fa2e7f29d 100644 --- a/pkg/gui/arrangement.go +++ b/pkg/gui/arrangement.go @@ -44,7 +44,7 @@ func (gui *Gui) getMidSectionWeights() (int, int) { currentWindow := gui.currentWindow() // we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4 - sidePanelWidthRatio := gui.Config.GetUserConfig().Gui.SidePanelWidth + sidePanelWidthRatio := gui.UserConfig.Gui.SidePanelWidth // we could make this better by creating ratios like 2:3 rather than always 1:something mainSectionWeight := int(1/sidePanelWidthRatio) - 1 sideSectionWeight := 1 @@ -115,7 +115,7 @@ func (gui *Gui) splitMainPanelSideBySide() bool { return false } - mainPanelSplitMode := gui.Config.GetUserConfig().Gui.MainPanelSplitMode + mainPanelSplitMode := gui.UserConfig.Gui.MainPanelSplitMode width, height := gui.g.Size() switch mainPanelSplitMode { @@ -143,7 +143,7 @@ func (gui *Gui) getExtrasWindowSize(screenHeight int) int { } else if screenHeight < 40 { baseSize = 1 } else { - baseSize = gui.Config.GetUserConfig().Gui.CommandLogSize + baseSize = gui.UserConfig.Gui.CommandLogSize } frameSize := 2 @@ -259,7 +259,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box { fullHeightBox("stash"), } } else if height >= 28 { - accordionMode := gui.Config.GetUserConfig().Gui.ExpandFocusedSidePanel + accordionMode := gui.UserConfig.Gui.ExpandFocusedSidePanel accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box { if accordionMode && defaultBox.Window == currentWindow { return &boxlayout.Box{ diff --git a/pkg/gui/basic_context.go b/pkg/gui/basic_context.go index 78cf85144..1db80ee4a 100644 --- a/pkg/gui/basic_context.go +++ b/pkg/gui/basic_context.go @@ -1,9 +1,11 @@ package gui type BasicContext struct { - OnFocus func() error - OnFocusLost func() error - OnRender func() error + OnFocus func(opts ...OnFocusOpts) error + OnFocusLost func() error + OnRender func() error + // this is for pushing some content to the main view + OnRenderToMain func(opts ...OnFocusOpts) error Kind ContextKind Key ContextKey ViewName string @@ -15,63 +17,83 @@ type BasicContext struct { hasParent bool } -func (c *BasicContext) GetOptionsMap() map[string]string { - if c.OnGetOptionsMap != nil { - return c.OnGetOptionsMap() +func (self *BasicContext) GetOptionsMap() map[string]string { + if self.OnGetOptionsMap != nil { + return self.OnGetOptionsMap() } return nil } -func (c *BasicContext) SetParentContext(context Context) { - c.ParentContext = context - c.hasParent = true +func (self *BasicContext) SetParentContext(context Context) { + self.ParentContext = context + self.hasParent = true } -func (c *BasicContext) GetParentContext() (Context, bool) { - return c.ParentContext, c.hasParent +func (self *BasicContext) GetParentContext() (Context, bool) { + return self.ParentContext, self.hasParent } -func (c *BasicContext) SetWindowName(windowName string) { - c.WindowName = windowName +func (self *BasicContext) SetWindowName(windowName string) { + self.WindowName = windowName } -func (c *BasicContext) GetWindowName() string { - windowName := c.WindowName +func (self *BasicContext) GetWindowName() string { + windowName := self.WindowName if windowName != "" { return windowName } // TODO: actually set this for everything so we don't default to the view name - return c.ViewName + return self.ViewName } -func (c *BasicContext) HandleRender() error { - if c.OnRender != nil { - return c.OnRender() +func (self *BasicContext) HandleRender() error { + if self.OnRender != nil { + return self.OnRender() } return nil } -func (c *BasicContext) GetViewName() string { - return c.ViewName +func (self *BasicContext) GetViewName() string { + return self.ViewName } -func (c *BasicContext) HandleFocus() error { - return c.OnFocus() +func (self *BasicContext) HandleFocus(opts ...OnFocusOpts) error { + if self.OnFocus != nil { + if err := self.OnFocus(opts...); err != nil { + return err + } + } + + if self.OnRenderToMain != nil { + if err := self.OnRenderToMain(opts...); err != nil { + return err + } + } + + return nil } -func (c *BasicContext) HandleFocusLost() error { - if c.OnFocusLost != nil { - return c.OnFocusLost() +func (self *BasicContext) HandleFocusLost() error { + if self.OnFocusLost != nil { + return self.OnFocusLost() } return nil } -func (c *BasicContext) GetKind() ContextKind { - return c.Kind +func (self *BasicContext) HandleRenderToMain() error { + if self.OnRenderToMain != nil { + return self.OnRenderToMain() + } + + return nil } -func (c *BasicContext) GetKey() ContextKey { - return c.Key +func (self *BasicContext) GetKind() ContextKind { + return self.Kind +} + +func (self *BasicContext) GetKey() ContextKey { + return self.Key } diff --git a/pkg/gui/bisect.go b/pkg/gui/bisect.go new file mode 100644 index 000000000..5c46460ac --- /dev/null +++ b/pkg/gui/bisect.go @@ -0,0 +1,219 @@ +package gui + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/models" +) + +func (gui *Gui) handleOpenBisectMenu() error { + if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { + return err + } + + // no shame in getting this directly rather than using the cached value + // given how cheap it is to obtain + info := gui.Git.Bisect.GetInfo() + commit := gui.getSelectedLocalCommit() + if info.Started() { + return gui.openMidBisectMenu(info, commit) + } else { + return gui.openStartBisectMenu(info, commit) + } +} + +func (gui *Gui) 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.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha + // 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 && !gui.Git.Bisect.ReachableFromStart(info) + + menuItems := []*menuItem{ + { + displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()), + onPress: func() error { + gui.logAction(gui.Tr.Actions.BisectMark) + if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil { + return gui.surfaceError(err) + } + + return gui.afterMark(selectCurrentAfter, waitToReselect) + }, + }, + { + displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()), + onPress: func() error { + gui.logAction(gui.Tr.Actions.BisectMark) + if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil { + return gui.surfaceError(err) + } + + return gui.afterMark(selectCurrentAfter, waitToReselect) + }, + }, + { + displayString: fmt.Sprintf(gui.Tr.Bisect.Skip, commit.ShortSha()), + onPress: func() error { + gui.logAction(gui.Tr.Actions.BisectSkip) + if err := gui.Git.Bisect.Skip(commit.Sha); err != nil { + return gui.surfaceError(err) + } + + return gui.afterMark(selectCurrentAfter, waitToReselect) + }, + }, + { + displayString: gui.Tr.Bisect.ResetOption, + onPress: func() error { + return gui.resetBisect() + }, + }, + } + + return gui.createMenu( + gui.Tr.Bisect.BisectMenuTitle, + menuItems, + createMenuOptions{showCancel: true}, + ) +} + +func (gui *Gui) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error { + return gui.createMenu( + gui.Tr.Bisect.BisectMenuTitle, + []*menuItem{ + { + displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()), + onPress: func() error { + gui.logAction(gui.Tr.Actions.StartBisect) + if err := gui.Git.Bisect.Start(); err != nil { + return gui.surfaceError(err) + } + + if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil { + return gui.surfaceError(err) + } + + return gui.postBisectCommandRefresh() + }, + }, + { + displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()), + onPress: func() error { + gui.logAction(gui.Tr.Actions.StartBisect) + if err := gui.Git.Bisect.Start(); err != nil { + return gui.surfaceError(err) + } + + if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil { + return gui.surfaceError(err) + } + + return gui.postBisectCommandRefresh() + }, + }, + }, + createMenuOptions{showCancel: true}, + ) +} + +func (gui *Gui) resetBisect() error { + return gui.ask(askOpts{ + title: gui.Tr.Bisect.ResetTitle, + prompt: gui.Tr.Bisect.ResetPrompt, + handleConfirm: func() error { + gui.logAction(gui.Tr.Actions.ResetBisect) + if err := gui.Git.Bisect.Reset(); err != nil { + return gui.surfaceError(err) + } + + return gui.postBisectCommandRefresh() + }, + }) +} + +func (gui *Gui) showBisectCompleteMessage(candidateShas []string) error { + prompt := gui.Tr.Bisect.CompletePrompt + if len(candidateShas) > 1 { + prompt = gui.Tr.Bisect.CompletePromptIndeterminate + } + + formattedCommits, err := gui.Git.Commit.GetCommitsOneline(candidateShas) + if err != nil { + return gui.surfaceError(err) + } + + return gui.ask(askOpts{ + title: gui.Tr.Bisect.CompleteTitle, + prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)), + handleConfirm: func() error { + gui.logAction(gui.Tr.Actions.ResetBisect) + if err := gui.Git.Bisect.Reset(); err != nil { + return gui.surfaceError(err) + } + + return gui.postBisectCommandRefresh() + }, + }) +} + +func (gui *Gui) afterMark(selectCurrent bool, waitToReselect bool) error { + done, candidateShas, err := gui.Git.Bisect.IsDone() + if err != nil { + return gui.surfaceError(err) + } + + if err := gui.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil { + return gui.surfaceError(err) + } + + if done { + return gui.showBisectCompleteMessage(candidateShas) + } + + return nil +} + +func (gui *Gui) postBisectCommandRefresh() error { + return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{}}) +} + +func (gui *Gui) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error { + selectFn := func() { + if selectCurrent { + gui.selectCurrentBisectCommit() + } + } + + if waitToReselect { + return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{}, then: selectFn}) + } else { + selectFn() + + return gui.postBisectCommandRefresh() + } +} + +func (gui *Gui) selectCurrentBisectCommit() { + info := gui.Git.Bisect.GetInfo() + if info.GetCurrentSha() != "" { + // find index of commit with that sha, move cursor to that. + for i, commit := range gui.State.Commits { + if commit.Sha == info.GetCurrentSha() { + gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(i) + _ = gui.State.Contexts.BranchCommits.HandleFocus() + break + } + } + } +} diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 74f1ed485..c2c44f273 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -1,12 +1,12 @@ package gui import ( + "errors" "fmt" "strings" - "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/utils" ) @@ -25,17 +25,15 @@ func (gui *Gui) getSelectedBranch() *models.Branch { return gui.State.Branches[selectedLine] } -func (gui *Gui) handleBranchSelect() error { +func (gui *Gui) branchesRenderToMain() error { var task updateTask branch := gui.getSelectedBranch() if branch == nil { task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo) } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.GetBranchGraphCmdStr(branch.Name), - ) + cmdObj := gui.Git.Branch.GetGraphCmdObj(branch.Name) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ @@ -56,17 +54,18 @@ func (gui *Gui) refreshBranches() { // which allows us to order them correctly. So if we're filtering we'll just // manually load all the reflog commits here var err error - reflogCommits, _, err = gui.GitCommand.GetReflogCommits(nil, "") + reflogCommits, _, err = gui.Git.Loaders.ReflogCommits.GetReflogCommits(nil, "") if err != nil { gui.Log.Error(err) } } - builder, err := commands.NewBranchListBuilder(gui.Log, gui.GitCommand, reflogCommits) + branches, err := gui.Git.Loaders.Branches.Load(reflogCommits) if err != nil { _ = gui.surfaceError(err) } - gui.State.Branches = builder.Build() + + gui.State.Branches = branches if err := gui.postRefreshUpdate(gui.State.Contexts.Branches); err != nil { gui.Log.Error(err) @@ -126,7 +125,8 @@ func (gui *Gui) handleBranchPress() error { return gui.createErrorPanel(gui.Tr.AlreadyCheckedOutBranch) } branch := gui.getSelectedBranch() - return gui.handleCheckoutRef(branch.Name, handleCheckoutRefOptions{span: gui.Tr.Spans.CheckoutBranch}) + gui.logAction(gui.Tr.Actions.CheckoutBranch) + return gui.handleCheckoutRef(branch.Name, handleCheckoutRefOptions{}) } func (gui *Gui) handleCreateOrShowPullRequestPress() error { @@ -153,14 +153,24 @@ func (gui *Gui) handleCreateOrOpenPullRequestMenu() error { } func (gui *Gui) handleCopyPullRequestURLPress() error { - pullRequest := commands.NewPullRequest(gui.GitCommand) + hostingServiceMgr := gui.getHostingServiceMgr() branch := gui.getSelectedBranch() - url, err := pullRequest.CopyURL(branch.Name, "") + + branchExistsOnRemote := gui.Git.Remote.CheckRemoteBranchExists(branch.Name) + + if !branchExistsOnRemote { + return gui.surfaceError(errors.New(gui.Tr.NoBranchOnRemote)) + } + + url, err := hostingServiceMgr.GetPullRequestURL(branch.Name, "") if err != nil { return gui.surfaceError(err) } - gui.OnRunCommand(oscommands.NewCmdLogEntry(fmt.Sprintf("Copying to clipboard: '%s'", url), "Copy URL", false)) + gui.logAction(gui.Tr.Actions.CopyPullRequestURL) + if err := gui.OSCommand.CopyToClipboard(url); err != nil { + return gui.surfaceError(err) + } gui.raiseToast(gui.Tr.PullRequestURLCopiedToClipboard) @@ -171,8 +181,9 @@ func (gui *Gui) handleGitFetch() error { if err := gui.createLoaderPanel(gui.Tr.FetchWait); err != nil { return err } + go utils.Safe(func() { - err := gui.fetch(true, "Fetch") + err := gui.fetch() gui.handleCredentialsPopup(err) _ = gui.refreshSidePanels(refreshOptions{mode: ASYNC}) }) @@ -188,7 +199,8 @@ func (gui *Gui) handleForceCheckout() error { title: title, prompt: message, handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.ForceCheckoutBranch).Checkout(branch.Name, commands.CheckoutOptions{Force: true}); err != nil { + gui.logAction(gui.Tr.Actions.ForceCheckoutBranch) + if err := gui.Git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil { _ = gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{mode: ASYNC}) @@ -200,7 +212,6 @@ type handleCheckoutRefOptions struct { WaitingStatus string EnvVars []string onRefNotFound func(ref string) error - span string } func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) error { @@ -209,7 +220,7 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) waitingStatus = gui.Tr.CheckingOutStatus } - cmdOptions := commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars} + cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars} onSuccess := func() { gui.State.Panels.Branches.SelectedLineIdx = 0 @@ -218,10 +229,8 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) gui.State.Panels.Commits.LimitCommits = true } - gitCommand := gui.GitCommand.WithSpan(options.span) - return gui.WithWaitingStatus(waitingStatus, func() error { - if err := gitCommand.Checkout(ref, cmdOptions); err != nil { + if err := gui.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") { @@ -235,15 +244,15 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) title: gui.Tr.AutoStashTitle, prompt: gui.Tr.AutoStashPrompt, handleConfirm: func() error { - if err := gitCommand.StashSave(gui.Tr.StashPrefix + ref); err != nil { + if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + ref); err != nil { return gui.surfaceError(err) } - if err := gitCommand.Checkout(ref, cmdOptions); err != nil { + if err := gui.Git.Branch.Checkout(ref, cmdOptions); err != nil { return gui.surfaceError(err) } onSuccess() - if err := gitCommand.StashDo(0, "pop"); err != nil { + if err := gui.Git.Stash.Pop(0); err != nil { if err := gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI}); err != nil { return err } @@ -269,10 +278,9 @@ func (gui *Gui) handleCheckoutByName() error { title: gui.Tr.BranchName + ":", findSuggestionsFunc: gui.getRefsSuggestionsFunc(), handleConfirm: func(response string) error { + gui.logAction("Checkout branch") return gui.handleCheckoutRef(response, handleCheckoutRefOptions{ - span: "Checkout branch", onRefNotFound: func(ref string) error { - return gui.ask(askOpts{ title: gui.Tr.BranchNotFoundTitle, prompt: fmt.Sprintf("%s %s%s", gui.Tr.BranchNotFoundPrompt, ref, "?"), @@ -300,7 +308,7 @@ func (gui *Gui) createNewBranchWithName(newBranchName string) error { return nil } - if err := gui.GitCommand.NewBranch(newBranchName, branch.Name); err != nil { + if err := gui.Git.Branch.New(newBranchName, branch.Name); err != nil { return gui.surfaceError(err) } @@ -343,7 +351,8 @@ func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) err title: title, prompt: message, handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DeleteBranch).DeleteBranch(selectedBranch.Name, force); err != nil { + gui.logAction(gui.Tr.Actions.DeleteBranch) + if err := gui.Git.Branch.Delete(selectedBranch.Name, force); err != nil { errMessage := err.Error() if !force && strings.Contains(errMessage, "git branch -D ") { return gui.deleteNamedBranch(selectedBranch, true) @@ -360,7 +369,7 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error { return err } - if gui.GitCommand.IsHeadDetached() { + if gui.Git.Branch.IsHeadDetached() { return gui.createErrorPanel("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 := gui.getCheckedOutBranch().Name @@ -379,7 +388,8 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error { title: gui.Tr.MergingTitle, prompt: prompt, handleConfirm: func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.Merge).Merge(branchName, commands.MergeOpts{}) + gui.logAction(gui.Tr.Actions.Merge) + err := gui.Git.Branch.Merge(branchName, git_commands.MergeOpts{}) return gui.handleGenericMergeCommandResult(err) }, }) @@ -420,7 +430,8 @@ func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error { title: gui.Tr.RebasingTitle, prompt: prompt, handleConfirm: func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.RebaseBranch).RebaseBranch(selectedBranchName) + gui.logAction(gui.Tr.Actions.RebaseBranch) + err := gui.Git.Rebase.RebaseBranch(selectedBranchName) return gui.handleGenericMergeCommandResult(err) }, }) @@ -435,25 +446,19 @@ func (gui *Gui) handleFastForward() error { if !branch.IsTrackingRemote() { return gui.createErrorPanel(gui.Tr.FwdNoUpstream) } + if !branch.RemoteBranchStoredLocally() { + return gui.createErrorPanel(gui.Tr.FwdNoLocalUpstream) + } if branch.HasCommitsToPush() { return gui.createErrorPanel(gui.Tr.FwdCommitsToPush) } - upstream, err := gui.GitCommand.GetUpstreamForBranch(branch.Name) - if err != nil { - return gui.surfaceError(err) - } - - span := gui.Tr.Spans.FastForwardBranch - - split := strings.Split(upstream, "/") - remoteName := split[0] - remoteBranchName := strings.Join(split[1:], "/") + action := gui.Tr.Actions.FastForwardBranch message := utils.ResolvePlaceholderString( gui.Tr.Fetching, map[string]string{ - "from": fmt.Sprintf("%s/%s", remoteName, remoteBranchName), + "from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch), "to": branch.Name, }, ) @@ -461,9 +466,10 @@ func (gui *Gui) handleFastForward() error { _ = gui.createLoaderPanel(message) if gui.State.Panels.Branches.SelectedLineIdx == 0 { - _ = gui.pullWithLock(PullFilesOptions{span: span, FastForwardOnly: true}) + _ = gui.pullWithLock(PullFilesOptions{action: action, FastForwardOnly: true}) } else { - err := gui.GitCommand.WithSpan(span).FastForward(branch.Name, remoteName, remoteBranchName, gui.promptUserForCredential) + gui.logAction(action) + err := gui.Git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch) gui.handleCredentialsPopup(err) _ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{BRANCHES}}) } @@ -491,7 +497,8 @@ func (gui *Gui) handleRenameBranch() error { title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":", initialContent: branch.Name, handleConfirm: func(newBranchName string) error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.RenameBranch).RenameBranch(branch.Name, newBranchName); err != nil { + gui.logAction(gui.Tr.Actions.RenameBranch) + if err := gui.Git.Branch.Rename(branch.Name, newBranchName); err != nil { return gui.surfaceError(err) } @@ -559,7 +566,8 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error { title: message, initialContent: prefilledName, handleConfirm: func(response string) error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.CreateBranch).NewBranch(sanitizedBranchName(response), item.ID()); err != nil { + gui.logAction(gui.Tr.Actions.CreateBranch) + if err := gui.Git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil { return err } diff --git a/pkg/gui/cherry_picking.go b/pkg/gui/cherry_picking.go index c57854370..b4b9439cd 100644 --- a/pkg/gui/cherry_picking.go +++ b/pkg/gui/cherry_picking.go @@ -148,7 +148,8 @@ func (gui *Gui) HandlePasteCommits() error { prompt: gui.Tr.SureCherryPick, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.CherryPickingStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.CherryPick).CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits) + gui.logAction(gui.Tr.Actions.CherryPick) + err := gui.Git.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits) return gui.handleGenericMergeCommandResult(err) }) }, diff --git a/pkg/gui/command_log_panel.go b/pkg/gui/command_log_panel.go index 261ceda90..aa46a4d18 100644 --- a/pkg/gui/command_log_panel.go +++ b/pkg/gui/command_log_panel.go @@ -6,46 +6,58 @@ import ( "strings" "time" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/constants" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/theme" ) -func (gui *Gui) GetOnRunCommand() func(entry oscommands.CmdLogEntry) { - // closing over this so that nobody else can modify it - currentSpan := "" - - return func(entry oscommands.CmdLogEntry) { - if gui.Views.Extras == nil { - return - } - - gui.Views.Extras.Autoscroll = true - - if entry.GetSpan() != currentSpan { - fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(entry.GetSpan())) - currentSpan = entry.GetSpan() - } - - textStyle := theme.DefaultTextColor - if !entry.GetCommandLine() { - textStyle = style.FgMagenta - } - gui.CmdLog = append(gui.CmdLog, entry.GetCmdStr()) - indentedCmdStr := " " + strings.Replace(entry.GetCmdStr(), "\n", "\n ", -1) - fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr)) +// 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 + + 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.CmdLog = append(gui.CmdLog, cmdStr) + indentedCmdStr := " " + strings.Replace(cmdStr, "\n", "\n ", -1) + fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr)) } func (gui *Gui) printCommandLogHeader() { introStr := fmt.Sprintf( gui.Tr.CommandLogHeader, - gui.getKeyDisplay(gui.Config.GetUserConfig().Keybinding.Universal.ExtrasMenu), + gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.ExtrasMenu), ) fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr)) - if gui.Config.GetUserConfig().Gui.ShowRandomTip { + if gui.UserConfig.Gui.ShowRandomTip { fmt.Fprintf( gui.Views.Extras, "%s: %s", @@ -56,7 +68,7 @@ func (gui *Gui) printCommandLogHeader() { } func (gui *Gui) getRandomTip() string { - config := gui.Config.GetUserConfig().Keybinding + config := gui.UserConfig.Keybinding formattedKey := func(key string) string { return gui.getKeyDisplay(key) diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index a01551e8d..61f3b72b8 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -8,11 +8,11 @@ import ( func (gui *Gui) getSelectedCommitFileNode() *filetree.CommitFileNode { selectedLine := gui.State.Panels.CommitFiles.SelectedLineIdx - if selectedLine == -1 || selectedLine > gui.State.CommitFileManager.GetItemsLength()-1 { + if selectedLine == -1 || selectedLine > gui.State.CommitFileTreeViewModel.GetItemsLength()-1 { return nil } - return gui.State.CommitFileManager.GetItemAtIndex(selectedLine) + return gui.State.CommitFileTreeViewModel.GetItemAtIndex(selectedLine) } func (gui *Gui) getSelectedCommitFile() *models.CommitFile { @@ -31,21 +31,22 @@ func (gui *Gui) getSelectedCommitFilePath() string { return node.GetPath() } -func (gui *Gui) handleCommitFileSelect() error { +func (gui *Gui) onCommitFileFocus() error { gui.escapeLineByLinePanel() + return nil +} +func (gui *Gui) commitFilesRenderToMain() error { node := gui.getSelectedCommitFileNode() if node == nil { return nil } - to := gui.State.CommitFileManager.GetParent() + to := gui.State.CommitFileTreeViewModel.GetParent() from, reverse := gui.getFromAndReverseArgsForDiff(to) - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowFileDiffCmdStr(from, to, reverse, node.GetPath(), false), - ) - task := NewRunPtyTask(cmd) + cmdObj := gui.Git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false) + task := NewRunPtyTask(cmdObj.GetCmd()) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ @@ -62,7 +63,8 @@ func (gui *Gui) handleCheckoutCommitFile() error { return nil } - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.CheckoutFile).CheckoutFile(gui.State.CommitFileManager.GetParent(), node.GetPath()); err != nil { + gui.logAction(gui.Tr.Actions.CheckoutFile) + if err := gui.Git.WorkingTree.CheckoutFile(gui.State.CommitFileTreeViewModel.GetParent(), node.GetPath()); err != nil { return gui.surfaceError(err) } @@ -81,7 +83,8 @@ func (gui *Gui) handleDiscardOldFileChange() error { prompt: gui.Tr.DiscardFileChangesPrompt, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DiscardOldFileChange).DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil { + gui.logAction(gui.Tr.Actions.DiscardOldFileChange) + if err := gui.Git.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil { if err := gui.handleGenericMergeCommandResult(err); err != nil { return err } @@ -104,11 +107,12 @@ func (gui *Gui) refreshCommitFilesView() error { to := gui.State.Panels.CommitFiles.refName from, reverse := gui.getFromAndReverseArgsForDiff(to) - files, err := gui.GitCommand.GetFilesInDiff(from, to, reverse) + files, err := gui.Git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse) if err != nil { return gui.surfaceError(err) } - gui.State.CommitFileManager.SetFiles(files, to) + gui.State.CommitFileTreeViewModel.SetParent(to) + gui.State.CommitFileTreeViewModel.SetFiles(files) return gui.postRefreshUpdate(gui.State.Contexts.CommitFiles) } @@ -142,7 +146,7 @@ func (gui *Gui) handleToggleFileForPatch() error { } toggleTheFile := func() error { - if !gui.GitCommand.PatchManager.Active() { + if !gui.Git.Patch.PatchManager.Active() { if err := gui.startPatchManager(); err != nil { return err } @@ -151,14 +155,14 @@ func (gui *Gui) handleToggleFileForPatch() error { // if there is any file that hasn't been fully added we'll fully add everything, // otherwise we'll remove everything adding := node.AnyFile(func(file *models.CommitFile) bool { - return gui.GitCommand.PatchManager.GetFileStatus(file.Name, gui.State.CommitFileManager.GetParent()) != patch.WHOLE + return gui.Git.Patch.PatchManager.GetFileStatus(file.Name, gui.State.CommitFileTreeViewModel.GetParent()) != patch.WHOLE }) err := node.ForEachFile(func(file *models.CommitFile) error { if adding { - return gui.GitCommand.PatchManager.AddFileWhole(file.Name) + return gui.Git.Patch.PatchManager.AddFileWhole(file.Name) } else { - return gui.GitCommand.PatchManager.RemoveFile(file.Name) + return gui.Git.Patch.PatchManager.RemoveFile(file.Name) } }) @@ -166,19 +170,19 @@ func (gui *Gui) handleToggleFileForPatch() error { return gui.surfaceError(err) } - if gui.GitCommand.PatchManager.IsEmpty() { - gui.GitCommand.PatchManager.Reset() + if gui.Git.Patch.PatchManager.IsEmpty() { + gui.Git.Patch.PatchManager.Reset() } return gui.postRefreshUpdate(gui.State.Contexts.CommitFiles) } - if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != gui.State.CommitFileManager.GetParent() { + if gui.Git.Patch.PatchManager.Active() && gui.Git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() { return gui.ask(askOpts{ title: gui.Tr.DiscardPatch, prompt: gui.Tr.DiscardPatchConfirm, handleConfirm: func() error { - gui.GitCommand.PatchManager.Reset() + gui.Git.Patch.PatchManager.Reset() return toggleTheFile() }, }) @@ -193,15 +197,15 @@ func (gui *Gui) startPatchManager() error { to := gui.State.Panels.CommitFiles.refName from, reverse := gui.getFromAndReverseArgsForDiff(to) - gui.GitCommand.PatchManager.Start(from, to, reverse, canRebase) + gui.Git.Patch.PatchManager.Start(from, to, reverse, canRebase) return nil } func (gui *Gui) handleEnterCommitFile() error { - return gui.enterCommitFile(-1) + return gui.enterCommitFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1}) } -func (gui *Gui) enterCommitFile(selectedLineIdx int) error { +func (gui *Gui) enterCommitFile(opts OnFocusOpts) error { node := gui.getSelectedCommitFileNode() if node == nil { return nil @@ -211,35 +215,28 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error { return gui.handleToggleCommitFileDirCollapsed() } - enterTheFile := func(selectedLineIdx int) error { - if !gui.GitCommand.PatchManager.Active() { + enterTheFile := func() error { + if !gui.Git.Patch.PatchManager.Active() { if err := gui.startPatchManager(); err != nil { return err } } - if err := gui.pushContext(gui.State.Contexts.PatchBuilding); err != nil { - return err - } - return gui.handleRefreshPatchBuildingPanel(selectedLineIdx) + return gui.pushContext(gui.State.Contexts.PatchBuilding, opts) } - if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != gui.State.CommitFileManager.GetParent() { + if gui.Git.Patch.PatchManager.Active() && gui.Git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() { return gui.ask(askOpts{ - title: gui.Tr.DiscardPatch, - prompt: gui.Tr.DiscardPatchConfirm, - handlersManageFocus: true, + title: gui.Tr.DiscardPatch, + prompt: gui.Tr.DiscardPatchConfirm, handleConfirm: func() error { - gui.GitCommand.PatchManager.Reset() - return enterTheFile(selectedLineIdx) - }, - handleClose: func() error { - return gui.pushContext(gui.State.Contexts.CommitFiles) + gui.Git.Patch.PatchManager.Reset() + return enterTheFile() }, }) } - return enterTheFile(selectedLineIdx) + return enterTheFile() } func (gui *Gui) handleToggleCommitFileDirCollapsed() error { @@ -248,7 +245,7 @@ func (gui *Gui) handleToggleCommitFileDirCollapsed() error { return nil } - gui.State.CommitFileManager.ToggleCollapsed(node.GetPath()) + gui.State.CommitFileTreeViewModel.ToggleCollapsed(node.GetPath()) if err := gui.postRefreshUpdate(gui.State.Contexts.CommitFiles); err != nil { gui.Log.Error(err) @@ -279,12 +276,12 @@ func (gui *Gui) switchToCommitFilesContext(refName string, canRebase bool, conte func (gui *Gui) handleToggleCommitFileTreeView() error { path := gui.getSelectedCommitFilePath() - gui.State.CommitFileManager.ToggleShowTree() + gui.State.CommitFileTreeViewModel.ToggleShowTree() // find that same node in the new format and move the cursor to it if path != "" { - gui.State.CommitFileManager.ExpandToPath(path) - index, found := gui.State.CommitFileManager.GetIndexForPath(path) + gui.State.CommitFileTreeViewModel.ExpandToPath(path) + index, found := gui.State.CommitFileTreeViewModel.GetIndexForPath(path) if found { gui.State.Contexts.CommitFiles.GetPanelState().SetSelectedLineIdx(index) } diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index edc86eff7..5f5a8741f 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -5,26 +5,23 @@ import ( "strings" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) handleCommitConfirm() error { message := strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent()) + gui.State.failedCommitMessage = message if message == "" { return gui.createErrorPanel(gui.Tr.CommitWithoutMessageErr) } - flags := "" - skipHookPrefix := gui.Config.GetUserConfig().Git.SkipHookPrefix - if skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix) { - flags = "--no-verify" - } - cmdStr := gui.GitCommand.CommitCmdStr(message, flags) - gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdStr, gui.Tr.Spans.Commit, true)) + cmdObj := gui.Git.Commit.CommitCmdObj(message) + gui.logAction(gui.Tr.Actions.Commit) + _ = gui.returnFromContext() - return gui.withGpgHandling(cmdStr, gui.Tr.CommittingStatus, func() error { + return gui.withGpgHandling(cmdObj, gui.Tr.CommittingStatus, func() error { gui.Views.CommitMessage.ClearTextArea() + gui.State.failedCommitMessage = "" return nil }) } @@ -37,14 +34,13 @@ func (gui *Gui) handleCommitMessageFocused() error { message := utils.ResolvePlaceholderString( gui.Tr.CommitMessageConfirm, map[string]string{ - "keyBindClose": gui.getKeyDisplay(gui.Config.GetUserConfig().Keybinding.Universal.Return), - "keyBindConfirm": gui.getKeyDisplay(gui.Config.GetUserConfig().Keybinding.Universal.Confirm), - "keyBindNewLine": gui.getKeyDisplay(gui.Config.GetUserConfig().Keybinding.Universal.AppendNewline), + "keyBindClose": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.Return), + "keyBindConfirm": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.Confirm), + "keyBindNewLine": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.AppendNewline), }, ) - gui.renderString(gui.Views.Options, message) - return nil + return gui.renderString(gui.Views.Options, message) } func (gui *Gui) getBufferLength(view *gocui.View) string { @@ -53,7 +49,7 @@ func (gui *Gui) getBufferLength(view *gocui.View) string { // RenderCommitLength is a function. func (gui *Gui) RenderCommitLength() { - if !gui.Config.GetUserConfig().Gui.CommitLength.Show { + if !gui.UserConfig.Gui.CommitLength.Show { return } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index f2149ec78..40848f97e 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -4,9 +4,8 @@ import ( "fmt" "sync" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -24,7 +23,7 @@ func (gui *Gui) getSelectedLocalCommit() *models.Commit { return gui.State.Commits[selectedLine] } -func (gui *Gui) handleCommitSelect() error { +func (gui *Gui) onCommitFocus() error { state := gui.State.Panels.Commits if state.SelectedLineIdx > COMMIT_THRESHOLD && state.LimitCommits { state.LimitCommits = false @@ -37,15 +36,17 @@ func (gui *Gui) handleCommitSelect() error { gui.escapeLineByLinePanel() + return nil +} + +func (gui *Gui) branchCommitsRenderToMain() error { var task updateTask commit := gui.getSelectedLocalCommit() if commit == nil { task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch) } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), - ) - task = NewRunPtyTask(cmd) + cmdObj := gui.Git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath()) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ @@ -90,7 +91,7 @@ func (gui *Gui) refreshReflogCommitsConsideringStartup() { // whenever we change commits, we should update branches because the upstream/downstream // counts can change. Whenever we change branches we should probably also change commits // e.g. in the case of switching branches. -func (gui *Gui) refreshCommits() error { +func (gui *Gui) refreshCommits() { wg := sync.WaitGroup{} wg.Add(2) @@ -121,22 +122,18 @@ func (gui *Gui) refreshCommits() error { }) wg.Wait() - - return nil } func (gui *Gui) refreshCommitsWithLimit() error { gui.Mutexes.BranchCommitsMutex.Lock() defer gui.Mutexes.BranchCommitsMutex.Unlock() - builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) - - commits, err := builder.GetCommits( - commands.GetCommitsOptions{ + commits, err := gui.Git.Loaders.Commits.GetCommits( + loaders.GetCommitsOptions{ Limit: gui.State.Panels.Commits.LimitCommits, FilterPath: gui.State.Modes.Filtering.GetPath(), IncludeRebaseCommits: true, - RefName: "HEAD", + RefName: gui.refForLog(), All: gui.State.ShowWholeGitGraph, }, ) @@ -148,13 +145,27 @@ func (gui *Gui) refreshCommitsWithLimit() error { return gui.postRefreshUpdate(gui.State.Contexts.BranchCommits) } +func (gui *Gui) refForLog() string { + bisectInfo := gui.Git.Bisect.GetInfo() + gui.State.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() && !gui.Git.Bisect.ReachableFromStart(bisectInfo) { + return bisectInfo.GetNewSha() + } + + return bisectInfo.GetStartSha() +} + func (gui *Gui) refreshRebaseCommits() error { gui.Mutexes.BranchCommitsMutex.Lock() defer gui.Mutexes.BranchCommitsMutex.Unlock() - builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) - - updatedCommits, err := builder.MergeRebasingCommits(gui.State.Commits) + updatedCommits, err := gui.Git.Loaders.Commits.MergeRebasingCommits(gui.State.Commits) if err != nil { return err } @@ -187,7 +198,8 @@ func (gui *Gui) handleCommitSquashDown() error { prompt: gui.Tr.SureSquashThisCommit, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.SquashingStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.SquashCommitDown).InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "squash") + gui.logAction(gui.Tr.Actions.SquashCommitDown) + err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "squash") return gui.handleGenericMergeCommandResult(err) }) }, @@ -216,14 +228,15 @@ func (gui *Gui) handleCommitFixup() error { prompt: gui.Tr.SureFixupThisCommit, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.FixingStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.FixupCommit).InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "fixup") + gui.logAction(gui.Tr.Actions.FixupCommit) + err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "fixup") return gui.handleGenericMergeCommandResult(err) }) }, }) } -func (gui *Gui) handleRenameCommit() error { +func (gui *Gui) handleRewordCommit() error { if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { return err } @@ -236,25 +249,23 @@ func (gui *Gui) handleRenameCommit() error { return nil } - if gui.State.Panels.Commits.SelectedLineIdx != 0 { - return gui.createErrorPanel(gui.Tr.OnlyRenameTopCommit) - } - commit := gui.getSelectedLocalCommit() if commit == nil { return nil } - message, err := gui.GitCommand.GetCommitMessage(commit.Sha) + message, err := gui.Git.Commit.GetCommitMessage(commit.Sha) if err != nil { return gui.surfaceError(err) } + // TODO: use the commit message panel here return gui.prompt(promptOpts{ - title: gui.Tr.LcRenameCommit, + title: gui.Tr.LcRewordCommit, initialContent: message, handleConfirm: func(response string) error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.RewordCommit).RenameCommit(response); err != nil { + gui.logAction(gui.Tr.Actions.RewordCommit) + if err := gui.Git.Rebase.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, response); err != nil { return gui.surfaceError(err) } @@ -263,7 +274,7 @@ func (gui *Gui) handleRenameCommit() error { }) } -func (gui *Gui) handleRenameCommitEditor() error { +func (gui *Gui) handleRewordCommitEditor() error { if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { return err } @@ -276,7 +287,8 @@ func (gui *Gui) handleRenameCommitEditor() error { return nil } - subProcess, err := gui.GitCommand.WithSpan(gui.Tr.Spans.RewordCommit).RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx) + gui.logAction(gui.Tr.Actions.RewordCommit) + subProcess, err := gui.Git.Rebase.RewordCommitInEditor(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx) if err != nil { return gui.surfaceError(err) } @@ -304,13 +316,13 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) { return true, gui.createErrorPanel(gui.Tr.LcRewordNotSupported) } - gui.OnRunCommand(oscommands.NewCmdLogEntry( + gui.logAction("Update rebase TODO") + gui.logCommand( fmt.Sprintf("Updating rebase action of commit %s to '%s'", selectedCommit.ShortSha(), action), - "Update rebase TODO", false, - )) + ) - if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil { + if err := gui.Git.Rebase.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil { return false, gui.surfaceError(err) } @@ -335,7 +347,8 @@ func (gui *Gui) handleCommitDelete() error { prompt: gui.Tr.DeleteCommitPrompt, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.DeletingStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.DropCommit).InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "drop") + gui.logAction(gui.Tr.Actions.DropCommit) + err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "drop") return gui.handleGenericMergeCommandResult(err) }) }, @@ -347,8 +360,6 @@ func (gui *Gui) handleCommitMoveDown() error { return err } - span := gui.Tr.Spans.MoveCommitDown - index := gui.State.Panels.Commits.SelectedLineIdx selectedCommit := gui.State.Commits[index] if selectedCommit.Status == "rebasing" { @@ -358,13 +369,10 @@ func (gui *Gui) handleCommitMoveDown() error { // logging directly here because MoveTodoDown doesn't have enough information // to provide a useful log - gui.OnRunCommand(oscommands.NewCmdLogEntry( - fmt.Sprintf("Moving commit %s down", selectedCommit.ShortSha()), - span, - false, - )) + gui.logAction(gui.Tr.Actions.MoveCommitDown) + gui.logCommand(fmt.Sprintf("Moving commit %s down", selectedCommit.ShortSha()), false) - if err := gui.GitCommand.MoveTodoDown(index); err != nil { + if err := gui.Git.Rebase.MoveTodoDown(index); err != nil { return gui.surfaceError(err) } gui.State.Panels.Commits.SelectedLineIdx++ @@ -372,7 +380,8 @@ func (gui *Gui) handleCommitMoveDown() error { } return gui.WithWaitingStatus(gui.Tr.MovingStatus, func() error { - err := gui.GitCommand.WithSpan(span).MoveCommitDown(gui.State.Commits, index) + gui.logAction(gui.Tr.Actions.MoveCommitDown) + err := gui.Git.Rebase.MoveCommitDown(gui.State.Commits, index) if err == nil { gui.State.Panels.Commits.SelectedLineIdx++ } @@ -390,19 +399,17 @@ func (gui *Gui) handleCommitMoveUp() error { return nil } - span := gui.Tr.Spans.MoveCommitUp - selectedCommit := gui.State.Commits[index] if selectedCommit.Status == "rebasing" { // logging directly here because MoveTodoDown doesn't have enough information // to provide a useful log - gui.OnRunCommand(oscommands.NewCmdLogEntry( + gui.logAction(gui.Tr.Actions.MoveCommitUp) + gui.logCommand( fmt.Sprintf("Moving commit %s up", selectedCommit.ShortSha()), - span, false, - )) + ) - if err := gui.GitCommand.MoveTodoDown(index - 1); err != nil { + if err := gui.Git.Rebase.MoveTodoDown(index - 1); err != nil { return gui.surfaceError(err) } gui.State.Panels.Commits.SelectedLineIdx-- @@ -410,7 +417,8 @@ func (gui *Gui) handleCommitMoveUp() error { } return gui.WithWaitingStatus(gui.Tr.MovingStatus, func() error { - err := gui.GitCommand.WithSpan(span).MoveCommitDown(gui.State.Commits, index-1) + gui.logAction(gui.Tr.Actions.MoveCommitUp) + err := gui.Git.Rebase.MoveCommitDown(gui.State.Commits, index-1) if err == nil { gui.State.Panels.Commits.SelectedLineIdx-- } @@ -432,7 +440,8 @@ func (gui *Gui) handleCommitEdit() error { } return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error { - err = gui.GitCommand.WithSpan(gui.Tr.Spans.EditCommit).InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "edit") + gui.logAction(gui.Tr.Actions.EditCommit) + err = gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "edit") return gui.handleGenericMergeCommandResult(err) }) } @@ -447,7 +456,8 @@ func (gui *Gui) handleCommitAmendTo() error { prompt: gui.Tr.AmendCommitPrompt, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.AmendingStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.AmendCommit).AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLineIdx].Sha) + gui.logAction(gui.Tr.Actions.AmendCommit) + err := gui.Git.Rebase.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLineIdx].Sha) return gui.handleGenericMergeCommandResult(err) }) }, @@ -476,16 +486,25 @@ func (gui *Gui) handleCommitRevert() error { if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { return err } - commit := gui.getSelectedLocalCommit() - if commit.IsMerge() { return gui.createRevertMergeCommitMenu(commit) } else { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.RevertCommit).Revert(commit.Sha); err != nil { - return gui.surfaceError(err) - } - return gui.afterRevertCommit() + return gui.ask(askOpts{ + title: gui.Tr.Actions.RevertCommit, + prompt: utils.ResolvePlaceholderString( + gui.Tr.ConfirmRevertCommit, + map[string]string{ + "selectedCommit": commit.ShortSha(), + }), + handleConfirm: func() error { + gui.logAction(gui.Tr.Actions.RevertCommit) + if err := gui.Git.Commit.Revert(commit.Sha); err != nil { + return gui.surfaceError(err) + } + return gui.afterRevertCommit() + }, + }) } } @@ -493,7 +512,7 @@ func (gui *Gui) createRevertMergeCommitMenu(commit *models.Commit) error { menuItems := make([]*menuItem, len(commit.Parents)) for i, parentSha := range commit.Parents { i := i - message, err := gui.GitCommand.GetCommitMessageFirstLine(parentSha) + message, err := gui.Git.Commit.GetCommitMessageFirstLine(parentSha) if err != nil { return gui.surfaceError(err) } @@ -502,7 +521,8 @@ func (gui *Gui) createRevertMergeCommitMenu(commit *models.Commit) error { displayString: fmt.Sprintf("%s: %s", utils.SafeTruncate(parentSha, 8), message), onPress: func() error { parentNumber := i + 1 - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.RevertCommit).RevertMerge(commit.Sha, parentNumber); err != nil { + gui.logAction(gui.Tr.Actions.RevertCommit) + if err := gui.Git.Commit.RevertMerge(commit.Sha, parentNumber); err != nil { return gui.surfaceError(err) } return gui.afterRevertCommit() @@ -548,7 +568,8 @@ func (gui *Gui) handleCreateFixupCommit() error { title: gui.Tr.CreateFixupCommit, prompt: prompt, handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.CreateFixupCommit).CreateFixupCommit(commit.Sha); err != nil { + gui.logAction(gui.Tr.Actions.CreateFixupCommit) + if err := gui.Git.Commit.CreateFixupCommit(commit.Sha); err != nil { return gui.surfaceError(err) } @@ -579,7 +600,8 @@ func (gui *Gui) handleSquashAllAboveFixupCommits() error { prompt: prompt, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.SquashingStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.SquashAllAboveFixupCommits).SquashAllAboveFixupCommits(commit.Sha) + gui.logAction(gui.Tr.Actions.SquashAllAboveFixupCommits) + err := gui.Git.Rebase.SquashAllAboveFixupCommits(commit.Sha) return gui.handleGenericMergeCommandResult(err) }) }, @@ -587,25 +609,65 @@ func (gui *Gui) handleSquashAllAboveFixupCommits() error { } func (gui *Gui) handleTagCommit() error { - // TODO: bring up menu asking if you want to make a lightweight or annotated tag - // if annotated, switch to a subprocess to create the message - commit := gui.getSelectedLocalCommit() if commit == nil { return nil } - return gui.handleCreateLightweightTag(commit.Sha) + return gui.createTagMenu(commit.Sha) +} + +func (gui *Gui) createTagMenu(commitSha string) error { + items := []*menuItem{ + { + displayString: gui.Tr.LcLightweightTag, + onPress: func() error { + return gui.handleCreateLightweightTag(commitSha) + }, + }, + { + displayString: gui.Tr.LcAnnotatedTag, + onPress: func() error { + return gui.handleCreateAnnotatedTag(commitSha) + }, + }, + } + + return gui.createMenu(gui.Tr.TagMenuTitle, items, createMenuOptions{showCancel: true}) +} + +func (gui *Gui) afterTagCreate() error { + gui.State.Panels.Tags.SelectedLineIdx = 0 // Set to the top + return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}}) +} + +func (gui *Gui) handleCreateAnnotatedTag(commitSha string) error { + return gui.prompt(promptOpts{ + title: gui.Tr.TagNameTitle, + handleConfirm: func(tagName string) error { + return gui.prompt(promptOpts{ + title: gui.Tr.TagMessageTitle, + handleConfirm: func(msg string) error { + gui.logAction(gui.Tr.Actions.CreateAnnotatedTag) + if err := gui.Git.Tag.CreateAnnotated(tagName, commitSha, msg); err != nil { + return gui.surfaceError(err) + } + return gui.afterTagCreate() + }, + }) + }, + }) } func (gui *Gui) handleCreateLightweightTag(commitSha string) error { return gui.prompt(promptOpts{ title: gui.Tr.TagNameTitle, - handleConfirm: func(response string) error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.CreateLightweightTag).CreateLightweightTag(response, commitSha); err != nil { + handleConfirm: func(tagName string) error { + gui.logAction(gui.Tr.Actions.CreateLightweightTag) + if err := gui.Git.Tag.CreateLightweight(tagName, commitSha); err != nil { return gui.surfaceError(err) } - return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}}) + return gui.afterTagCreate() }, }) } @@ -620,7 +682,8 @@ func (gui *Gui) handleCheckoutCommit() error { title: gui.Tr.LcCheckoutCommit, prompt: gui.Tr.SureCheckoutThisCommit, handleConfirm: func() error { - return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{span: gui.Tr.Spans.CheckoutCommit}) + gui.logAction(gui.Tr.Actions.CheckoutCommit) + return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{}) }, }) } @@ -634,7 +697,7 @@ func (gui *Gui) handleCreateCommitResetMenu() error { return gui.createResetMenu(commit.Sha) } -func (gui *Gui) handleOpenSearchForCommitsPanel(_viewName string) error { +func (gui *Gui) handleOpenSearchForCommitsPanel(string) error { // we usually lazyload these commits but now that we're searching we need to load them now if gui.State.Panels.Commits.LimitCommits { gui.State.Panels.Commits.LimitCommits = false @@ -670,12 +733,13 @@ func (gui *Gui) handleCopySelectedCommitMessageToClipboard() error { return nil } - message, err := gui.GitCommand.GetCommitMessage(commit.Sha) + message, err := gui.Git.Commit.GetCommitMessage(commit.Sha) if err != nil { return gui.surfaceError(err) } - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.CopyCommitMessageToClipboard).CopyToClipboard(message); err != nil { + gui.logAction(gui.Tr.Actions.CopyCommitMessageToClipboard) + if err := gui.OSCommand.CopyToClipboard(message); err != nil { return gui.surfaceError(err) } @@ -705,7 +769,7 @@ func (gui *Gui) handleOpenLogMenu() error { opensMenu: true, onPress: func() error { onSelect := func(value string) { - gui.Config.GetUserConfig().Git.Log.ShowGraph = value + gui.UserConfig.Git.Log.ShowGraph = value gui.render() } return gui.createMenu(gui.Tr.LogMenuTitle, []*menuItem{ @@ -738,7 +802,7 @@ func (gui *Gui) handleOpenLogMenu() error { opensMenu: true, onPress: func() error { onSelect := func(value string) error { - gui.Config.GetUserConfig().Git.Log.Order = value + gui.UserConfig.Git.Log.Order = value return gui.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error { return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{COMMITS}}) }) @@ -767,3 +831,24 @@ func (gui *Gui) handleOpenLogMenu() error { }, }, createMenuOptions{showCancel: true}) } + +func (gui *Gui) handleOpenCommitInBrowser() error { + commit := gui.getSelectedLocalCommit() + if commit == nil { + return nil + } + + hostingServiceMgr := gui.getHostingServiceMgr() + + url, err := hostingServiceMgr.GetCommitURL(commit.Sha) + if err != nil { + return gui.surfaceError(err) + } + + gui.logAction(gui.Tr.Actions.OpenCommitInBrowser) + if err := gui.OSCommand.OpenLink(url); err != nil { + return gui.surfaceError(err) + } + + return nil +} diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index e1b78bc3a..b092b1d10 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -1,5 +1,3 @@ -// lots of this has been directly ported from one of the example files, will brush up later - package gui import ( @@ -7,7 +5,6 @@ import ( "strings" "github.com/jesseduffield/gocui" - "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" @@ -39,65 +36,50 @@ type askOpts struct { type promptOpts struct { title string initialContent string - handleConfirm func(string) error findSuggestionsFunc func(string) []*types.Suggestion + handleConfirm func(string) error } func (gui *Gui) ask(opts askOpts) error { - return gui.createPopupPanel(createPopupPanelOpts{ - title: opts.title, - prompt: opts.prompt, - handleConfirm: opts.handleConfirm, - handleClose: opts.handleClose, - handlersManageFocus: opts.handlersManageFocus, - }) + return gui.PopupHandler.Ask(opts) } func (gui *Gui) prompt(opts promptOpts) error { - return gui.createPopupPanel(createPopupPanelOpts{ - title: opts.title, - prompt: opts.initialContent, - editable: true, - handleConfirmPrompt: opts.handleConfirm, - findSuggestionsFunc: opts.findSuggestionsFunc, - }) + return gui.PopupHandler.Prompt(opts) } func (gui *Gui) createLoaderPanel(prompt string) error { - return gui.createPopupPanel(createPopupPanelOpts{ - prompt: prompt, - hasLoader: true, - }) + return gui.PopupHandler.Loader(prompt) } func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function func() error) func() error { return func() error { - if function != nil { - if err := function(); err != nil { - return err - } - } - if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil { return err } + if function != nil { + if err := function(); err != nil { + return gui.surfaceError(err) + } + } + return nil } } func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, function func(string) error, getResponse func() string) func() error { return func() error { + if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil { + return err + } + if function != nil { if err := function(getResponse()); err != nil { return gui.surfaceError(err) } } - if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil { - return err - } - return nil } } @@ -191,48 +173,46 @@ func (gui *Gui) prepareConfirmationPanel( suggestionsView.FgColor = theme.GocuiDefaultTextColor gui.setSuggestions(findSuggestionsFunc("")) suggestionsView.Visible = true - suggestionsView.Title = fmt.Sprintf(gui.Tr.SuggestionsTitle, gui.Config.GetUserConfig().Keybinding.Universal.TogglePanel) + suggestionsView.Title = fmt.Sprintf(gui.Tr.SuggestionsTitle, gui.UserConfig.Keybinding.Universal.TogglePanel) } - gui.g.Update(func(g *gocui.Gui) error { - return gui.pushContext(gui.State.Contexts.Confirmation) - }) return nil } func (gui *Gui) createPopupPanel(opts createPopupPanelOpts) error { - gui.g.Update(func(g *gocui.Gui) error { - // remove any previous keybindings - gui.clearConfirmationViewKeyBindings() + // remove any previous keybindings + gui.clearConfirmationViewKeyBindings() - err := gui.prepareConfirmationPanel( - opts.title, - opts.prompt, - opts.hasLoader, - opts.findSuggestionsFunc, - opts.editable, - ) - if err != nil { + err := gui.prepareConfirmationPanel( + opts.title, + opts.prompt, + opts.hasLoader, + opts.findSuggestionsFunc, + opts.editable, + ) + if err != nil { + return err + } + confirmationView := gui.Views.Confirmation + confirmationView.Editable = opts.editable + confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor) + + if opts.editable { + textArea := confirmationView.TextArea + textArea.Clear() + textArea.TypeString(opts.prompt) + confirmationView.RenderTextArea() + } else { + if err := gui.renderString(confirmationView, opts.prompt); err != nil { return err } - confirmationView := gui.Views.Confirmation - confirmationView.Editable = opts.editable - confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor) + } - if opts.editable { - textArea := confirmationView.TextArea - textArea.Clear() - textArea.TypeString(opts.prompt) - confirmationView.RenderTextArea() - } else { - if err := gui.renderStringSync(confirmationView, opts.prompt); err != nil { - return err - } - } + if err := gui.setKeyBindings(opts); err != nil { + return err + } - return gui.setKeyBindings(opts) - }) - return nil + return gui.pushContext(gui.State.Contexts.Confirmation) } func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error { @@ -244,7 +224,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error { }, ) - gui.renderString(gui.Views.Options, actions) + _ = gui.renderString(gui.Views.Options, actions) var onConfirm func() error if opts.handleConfirmPrompt != nil { onConfirm = gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() }) @@ -258,7 +238,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error { handler func() error } - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding onSuggestionConfirm := gui.wrappedPromptConfirmationFunction( opts.handlersManageFocus, opts.handleConfirmPrompt, @@ -323,7 +303,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error { } func (gui *Gui) clearConfirmationViewKeyBindings() { - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone) _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone) _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone) @@ -339,15 +319,7 @@ func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View) } func (gui *Gui) createErrorPanel(message string) error { - coloredMessage := style.FgRed.Sprint(strings.TrimSpace(message)) - if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil { - return err - } - - return gui.ask(askOpts{ - title: gui.Tr.Error, - prompt: coloredMessage, - }) + return gui.PopupHandler.Error(message) } func (gui *Gui) surfaceError(err error) error { @@ -355,5 +327,9 @@ func (gui *Gui) surfaceError(err error) error { return nil } + if err == gocui.ErrQuit { + return err + } + return gui.createErrorPanel(err.Error()) } diff --git a/pkg/gui/context.go b/pkg/gui/context.go index 66e28fc35..7aa9a1046 100644 --- a/pkg/gui/context.go +++ b/pkg/gui/context.go @@ -1,6 +1,7 @@ package gui import ( + "errors" "fmt" "github.com/jesseduffield/gocui" @@ -16,10 +17,16 @@ const ( EXTRAS_CONTEXT ) +type OnFocusOpts struct { + ClickedViewName string + ClickedViewLineIdx int +} + type Context interface { - HandleFocus() error + HandleFocus(opts ...OnFocusOpts) error HandleFocusLost() error HandleRender() error + HandleRenderToMain() error GetKind() ContextKind GetViewName() string GetWindowName() string @@ -64,32 +71,25 @@ func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey { // use replaceContext when you don't want to return to the original context upon // hitting escape: you want to go that context's parent instead. func (gui *Gui) replaceContext(c Context) error { - gui.g.Update(func(*gocui.Gui) error { - gui.State.ContextManager.Lock() - defer gui.State.ContextManager.Unlock() + gui.State.ContextManager.Lock() + defer gui.State.ContextManager.Unlock() - if len(gui.State.ContextManager.ContextStack) == 0 { - gui.State.ContextManager.ContextStack = []Context{c} - } else { - // replace the last item with the given item - gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c) - } + if len(gui.State.ContextManager.ContextStack) == 0 { + gui.State.ContextManager.ContextStack = []Context{c} + } else { + // replace the last item with the given item + gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c) + } - return gui.activateContext(c) - }) - - return nil + return gui.activateContext(c) } -func (gui *Gui) pushContext(c Context) error { - gui.g.Update(func(*gocui.Gui) error { - return gui.pushContextDirect(c) - }) +func (gui *Gui) pushContext(c Context, opts ...OnFocusOpts) error { + // using triple dot but you should only ever pass one of these opt structs + if len(opts) > 1 { + return errors.New("cannot pass multiple opts to pushContext") + } - return nil -} - -func (gui *Gui) pushContextDirect(c Context) error { gui.State.ContextManager.Lock() // push onto stack @@ -114,7 +114,7 @@ func (gui *Gui) pushContextDirect(c Context) error { gui.State.ContextManager.Unlock() - return gui.activateContext(c) + return gui.activateContext(c, opts...) } // asynchronous code idea: functions return an error via a channel, when done @@ -127,14 +127,6 @@ func (gui *Gui) pushContextWithView(viewName string) error { } func (gui *Gui) returnFromContext() error { - gui.g.Update(func(*gocui.Gui) error { - return gui.returnFromContextSync() - }) - - return nil -} - -func (gui *Gui) returnFromContextSync() error { gui.State.ContextManager.Lock() if len(gui.State.ContextManager.ContextStack) == 1 { @@ -169,7 +161,7 @@ func (gui *Gui) deactivateContext(c Context) error { } // if we are the kind of context that is sent to back upon deactivation, we should do that - if view != nil && c.GetKind() == TEMPORARY_POPUP || c.GetKind() == PERSISTENT_POPUP || c.GetKey() == COMMIT_FILES_CONTEXT_KEY { + if view != nil && (c.GetKind() == TEMPORARY_POPUP || c.GetKind() == PERSISTENT_POPUP || c.GetKey() == COMMIT_FILES_CONTEXT_KEY) { view.Visible = false } @@ -206,7 +198,7 @@ func (gui *Gui) postRefreshUpdate(c Context) error { return nil } -func (gui *Gui) activateContext(c Context) error { +func (gui *Gui) activateContext(c Context, opts ...OnFocusOpts) error { viewName := c.GetViewName() v, err := gui.g.View(viewName) if err != nil { @@ -249,7 +241,7 @@ func (gui *Gui) activateContext(c Context) error { } gui.renderOptionsMap(optionsMap) - if err := c.HandleFocus(); err != nil { + if err := c.HandleFocus(opts...); err != nil { return err } diff --git a/pkg/gui/context_config.go b/pkg/gui/context_config.go index a3b965a69..f567f5a5c 100644 --- a/pkg/gui/context_config.go +++ b/pkg/gui/context_config.go @@ -110,10 +110,10 @@ func (gui *Gui) allContexts() []Context { func (gui *Gui) contextTree() ContextTree { return ContextTree{ Status: &BasicContext{ - OnFocus: gui.handleStatusSelect, - Kind: SIDE_CONTEXT, - ViewName: "status", - Key: STATUS_CONTEXT_KEY, + OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain), + Kind: SIDE_CONTEXT, + ViewName: "status", + Key: STATUS_CONTEXT_KEY, }, Files: gui.filesListContext(), Submodules: gui.submodulesListContext(), @@ -128,7 +128,7 @@ func (gui *Gui) contextTree() ContextTree { Tags: gui.tagsListContext(), Stash: gui.stashListContext(), Normal: &BasicContext{ - OnFocus: func() error { + OnFocus: func(opts ...OnFocusOpts) error { return nil // TODO: should we do something here? We should allow for scrolling the panel }, Kind: MAIN_CONTEXT, @@ -136,59 +136,67 @@ func (gui *Gui) contextTree() ContextTree { Key: MAIN_NORMAL_CONTEXT_KEY, }, Staging: &BasicContext{ - OnFocus: func() error { - return nil - // TODO: centralise the code here - // return gui.refreshStagingPanel(false, -1) + OnFocus: func(opts ...OnFocusOpts) error { + forceSecondaryFocused := false + selectedLineIdx := -1 + if len(opts) > 0 && opts[0].ClickedViewName != "" { + if opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary" { + selectedLineIdx = opts[0].ClickedViewLineIdx + } + if opts[0].ClickedViewName == "secondary" { + forceSecondaryFocused = true + } + } + return gui.onStagingFocus(forceSecondaryFocused, selectedLineIdx) }, Kind: MAIN_CONTEXT, ViewName: "main", Key: MAIN_STAGING_CONTEXT_KEY, }, PatchBuilding: &BasicContext{ - OnFocus: func() error { - return nil - // TODO: centralise the code here - // return gui.refreshPatchBuildingPanel(-1) + OnFocus: func(opts ...OnFocusOpts) error { + selectedLineIdx := -1 + if len(opts) > 0 && (opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary") { + selectedLineIdx = opts[0].ClickedViewLineIdx + } + + return gui.onPatchBuildingFocus(selectedLineIdx) }, Kind: MAIN_CONTEXT, ViewName: "main", Key: MAIN_PATCH_BUILDING_CONTEXT_KEY, }, Merging: &BasicContext{ - OnFocus: gui.refreshMergePanelWithLock, + OnFocus: OnFocusWrapper(func() error { return gui.renderConflictsWithLock(true) }), Kind: MAIN_CONTEXT, ViewName: "main", Key: MAIN_MERGING_CONTEXT_KEY, OnGetOptionsMap: gui.getMergingOptions, }, Credentials: &BasicContext{ - OnFocus: gui.handleCredentialsViewFocused, + OnFocus: OnFocusWrapper(gui.handleCredentialsViewFocused), Kind: PERSISTENT_POPUP, ViewName: "credentials", Key: CREDENTIALS_CONTEXT_KEY, }, Confirmation: &BasicContext{ - OnFocus: func() error { return nil }, Kind: TEMPORARY_POPUP, ViewName: "confirmation", Key: CONFIRMATION_CONTEXT_KEY, }, Suggestions: gui.suggestionsListContext(), CommitMessage: &BasicContext{ - OnFocus: gui.handleCommitMessageFocused, + OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused), Kind: PERSISTENT_POPUP, ViewName: "commitMessage", Key: COMMIT_MESSAGE_CONTEXT_KEY, }, Search: &BasicContext{ - OnFocus: func() error { return nil }, Kind: PERSISTENT_POPUP, ViewName: "search", Key: SEARCH_CONTEXT_KEY, }, CommandLog: &BasicContext{ - OnFocus: func() error { return nil }, Kind: EXTRAS_CONTEXT, ViewName: "extras", Key: COMMAND_LOG_CONTEXT_KEY, @@ -201,6 +209,14 @@ func (gui *Gui) contextTree() ContextTree { } } +// using this wrapper for when an onFocus function doesn't care about any potential +// props that could be passed +func OnFocusWrapper(f func() error) func(opts ...OnFocusOpts) error { + return func(opts ...OnFocusOpts) error { + return f() + } +} + func (tree ContextTree) initialViewContextMap() map[string]Context { return map[string]Context{ "status": tree.Status, diff --git a/pkg/gui/context_test.go b/pkg/gui/context_test.go new file mode 100644 index 000000000..7f03f7484 --- /dev/null +++ b/pkg/gui/context_test.go @@ -0,0 +1,40 @@ +package gui + +import ( + "testing" + + "github.com/jesseduffield/gocui" + "github.com/stretchr/testify/assert" +) + +func TestCanDeactivatePopupContextsWithoutViews(t *testing.T) { + contexts := []func(gui *Gui) Context{ + func(gui *Gui) Context { return gui.State.Contexts.Credentials }, + func(gui *Gui) Context { return gui.State.Contexts.Confirmation }, + func(gui *Gui) Context { return gui.State.Contexts.CommitMessage }, + func(gui *Gui) Context { return gui.State.Contexts.Search }, + } + + for _, c := range contexts { + gui := NewDummyGui() + context := c(gui) + gui.g = &gocui.Gui{} + + _ = gui.deactivateContext(context) + + // This really only checks a prerequisit, not the effect of deactivateContext + view, _ := gui.g.View(context.GetViewName()) + assert.Nil(t, view, string(context.GetKey())) + } +} + +func TestCanDeactivateCommitFilesContextsWithoutViews(t *testing.T) { + gui := NewDummyGui() + gui.g = &gocui.Gui{} + + _ = gui.deactivateContext(gui.State.Contexts.CommitFiles) + + // This really only checks a prerequisite, not the effect of deactivateContext + view, _ := gui.g.View(gui.State.Contexts.CommitFiles.GetViewName()) + assert.Nil(t, view) +} diff --git a/pkg/gui/credentials_panel.go b/pkg/gui/credentials_panel.go index 320f535f5..984591a62 100644 --- a/pkg/gui/credentials_panel.go +++ b/pkg/gui/credentials_panel.go @@ -3,25 +3,25 @@ package gui import ( "strings" - "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/utils" ) type credentials chan string // promptUserForCredential wait for a username, password or passphrase input from the credentials popup -func (gui *Gui) promptUserForCredential(passOrUname string) string { +func (gui *Gui) promptUserForCredential(passOrUname oscommands.CredentialType) string { gui.credentials = make(chan string) - gui.g.Update(func(g *gocui.Gui) error { + gui.OnUIThread(func() error { credentialsView := gui.Views.Credentials switch passOrUname { - case "username": + case oscommands.Username: credentialsView.Title = gui.Tr.CredentialsUsername credentialsView.Mask = 0 - case "password": + case oscommands.Password: credentialsView.Title = gui.Tr.CredentialsPassword credentialsView.Mask = '*' - default: + case oscommands.Passphrase: credentialsView.Title = gui.Tr.CredentialsPassphrase credentialsView.Mask = '*' } @@ -57,7 +57,7 @@ func (gui *Gui) handleCloseCredentialsView() error { } func (gui *Gui) handleCredentialsViewFocused() error { - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding message := utils.ResolvePlaceholderString( gui.Tr.CloseConfirm, @@ -67,8 +67,7 @@ func (gui *Gui) handleCredentialsViewFocused() error { }, ) - gui.renderString(gui.Views.Options, message) - return nil + return gui.renderString(gui.Views.Options, message) } // handleCredentialsPopup handles the views after executing a command that might ask for credentials diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index 8f98d6597..02293dd65 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -144,7 +144,7 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, label } candidates := []commandMenuEntry{} - for _, str := range strings.Split(string(commandOutput), "\n") { + for _, str := range strings.Split(commandOutput, "\n") { if str == "" { continue } @@ -203,7 +203,7 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR } // Run and save output - message, err := gui.GitCommand.RunCommandWithOutput(cmdStr) + message, err := gui.Git.Custom.RunWithOutput(cmdStr) if err != nil { return gui.surfaceError(err) } @@ -216,6 +216,7 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR menuItems := make([]*menuItem, len(candidates)) for i := range candidates { + i := i menuItems[i] = &menuItem{ displayStrings: []string{candidates[i].label}, onPress: func() error { @@ -244,7 +245,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand } if customCommand.Subprocess { - return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.PrepareShellSubProcess(cmdStr)) + return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.Cmd.NewShell(cmdStr)) } loadingText := customCommand.LoadingText @@ -252,7 +253,13 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand loadingText = gui.Tr.LcRunningCustomCommandStatus } return gui.WithWaitingStatus(loadingText, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.CustomCommand).RunShellCommand(cmdStr); err != nil { + gui.logAction(gui.Tr.Actions.CustomCommand) + cmdObj := gui.OSCommand.Cmd.NewShell(cmdStr) + if customCommand.Stream { + cmdObj.StreamOutput() + } + err := cmdObj.Run() + if err != nil { return gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{}) @@ -295,7 +302,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand func (gui *Gui) GetCustomCommandKeybindings() []*Binding { bindings := []*Binding{} - customCommands := gui.Config.GetUserConfig().CustomCommands + customCommands := gui.UserConfig.CustomCommands for _, customCommand := range customCommands { var viewName string diff --git a/pkg/gui/custom_commands_test.go b/pkg/gui/custom_commands_test.go index d51617c09..d31bcf291 100644 --- a/pkg/gui/custom_commands_test.go +++ b/pkg/gui/custom_commands_test.go @@ -56,6 +56,7 @@ func TestGuiGenerateMenuCandidates(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { s.test(NewDummyGui().GenerateMenuCandidates(s.cmdOut, s.filter, s.valueFormat, s.labelFormat)) }) diff --git a/pkg/gui/diff_context_size.go b/pkg/gui/diff_context_size.go new file mode 100644 index 000000000..3b6f6b0a9 --- /dev/null +++ b/pkg/gui/diff_context_size.go @@ -0,0 +1,75 @@ +package gui + +import ( + "errors" +) + +var CONTEXT_KEYS_SHOWING_DIFFS = []ContextKey{ + FILES_CONTEXT_KEY, + COMMIT_FILES_CONTEXT_KEY, + STASH_CONTEXT_KEY, + BRANCH_COMMITS_CONTEXT_KEY, + SUB_COMMITS_CONTEXT_KEY, + MAIN_STAGING_CONTEXT_KEY, + MAIN_PATCH_BUILDING_CONTEXT_KEY, +} + +func isShowingDiff(gui *Gui) bool { + key := gui.currentStaticContext().GetKey() + + for _, contextKey := range CONTEXT_KEYS_SHOWING_DIFFS { + if key == contextKey { + return true + } + } + return false +} + +func (gui *Gui) IncreaseContextInDiffView() error { + if isShowingDiff(gui) { + if err := gui.CheckCanChangeContext(); err != nil { + return gui.surfaceError(err) + } + + gui.UserConfig.Git.DiffContextSize = gui.UserConfig.Git.DiffContextSize + 1 + return gui.handleDiffContextSizeChange() + } + + return nil +} + +func (gui *Gui) DecreaseContextInDiffView() error { + old_size := gui.UserConfig.Git.DiffContextSize + + if isShowingDiff(gui) && old_size > 1 { + if err := gui.CheckCanChangeContext(); err != nil { + return gui.surfaceError(err) + } + + gui.UserConfig.Git.DiffContextSize = old_size - 1 + return gui.handleDiffContextSizeChange() + } + + return nil +} + +func (gui *Gui) handleDiffContextSizeChange() error { + currentContext := gui.currentStaticContext() + switch currentContext.GetKey() { + // we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards. + case MAIN_PATCH_BUILDING_CONTEXT_KEY: + return gui.handleRefreshPatchBuildingPanel(-1) + case MAIN_STAGING_CONTEXT_KEY: + return gui.handleRefreshStagingPanel(false, -1) + default: + return currentContext.HandleRenderToMain() + } +} + +func (gui *Gui) CheckCanChangeContext() error { + if gui.Git.Patch.PatchManager.Active() { + return errors.New(gui.Tr.CantChangeContextSizeError) + } + + return nil +} diff --git a/pkg/gui/diff_context_size_test.go b/pkg/gui/diff_context_size_test.go new file mode 100644 index 000000000..b459e40d0 --- /dev/null +++ b/pkg/gui/diff_context_size_test.go @@ -0,0 +1,190 @@ +package gui + +import ( + "testing" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/patch" + "github.com/stretchr/testify/assert" +) + +const diffForTest = `diff --git a/pkg/gui/diff_context_size.go b/pkg/gui/diff_context_size.go +index 0da0a982..742b7dcf 100644 +--- a/pkg/gui/diff_context_size.go ++++ b/pkg/gui/diff_context_size.go +@@ -9,12 +9,12 @@ func getRefreshFunction(gui *Gui) func()error { + } + } else if key == MAIN_STAGING_CONTEXT_KEY { + return func() error { +- selectedLine := gui.Views.Secondary.SelectedLineIdx() ++ selectedLine := gui.State.Panels.LineByLine.GetSelectedLineIdx() + return gui.handleRefreshStagingPanel(false, selectedLine) + } + } else if key == MAIN_PATCH_BUILDING_CONTEXT_KEY { +` + +func setupGuiForTest(gui *Gui) { + gui.g = &gocui.Gui{} + gui.Views.Main, _ = gui.prepareView("main") + gui.Views.Secondary, _ = gui.prepareView("secondary") + gui.Views.Options, _ = gui.prepareView("options") + gui.Git.Patch.PatchManager = &patch.PatchManager{} + _, _ = gui.refreshLineByLinePanel(diffForTest, "", false, 11) +} + +func TestIncreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) { + contexts := []func(gui *Gui) Context{ + func(gui *Gui) Context { return gui.State.Contexts.Files }, + func(gui *Gui) Context { return gui.State.Contexts.BranchCommits }, + func(gui *Gui) Context { return gui.State.Contexts.CommitFiles }, + func(gui *Gui) Context { return gui.State.Contexts.Stash }, + func(gui *Gui) Context { return gui.State.Contexts.Staging }, + func(gui *Gui) Context { return gui.State.Contexts.PatchBuilding }, + func(gui *Gui) Context { return gui.State.Contexts.SubCommits }, + } + + for _, c := range contexts { + gui := NewDummyGui() + context := c(gui) + setupGuiForTest(gui) + gui.UserConfig.Git.DiffContextSize = 1 + _ = gui.pushContext(context) + + _ = gui.IncreaseContextInDiffView() + + assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize, string(context.GetKey())) + } +} + +func TestDoesntIncreaseContextInDiffViewInContextWithoutDiff(t *testing.T) { + contexts := []func(gui *Gui) Context{ + func(gui *Gui) Context { return gui.State.Contexts.Status }, + func(gui *Gui) Context { return gui.State.Contexts.Submodules }, + func(gui *Gui) Context { return gui.State.Contexts.Remotes }, + func(gui *Gui) Context { return gui.State.Contexts.Normal }, + func(gui *Gui) Context { return gui.State.Contexts.ReflogCommits }, + func(gui *Gui) Context { return gui.State.Contexts.RemoteBranches }, + func(gui *Gui) Context { return gui.State.Contexts.Tags }, + // not testing this because it will kick straight back to the files context + // upon pushing the context + // func(gui *Gui) Context { return gui.State.Contexts.Merging }, + func(gui *Gui) Context { return gui.State.Contexts.CommandLog }, + } + + for _, c := range contexts { + gui := NewDummyGui() + context := c(gui) + setupGuiForTest(gui) + gui.UserConfig.Git.DiffContextSize = 1 + _ = gui.pushContext(context) + + _ = gui.IncreaseContextInDiffView() + + assert.Equal(t, 1, gui.UserConfig.Git.DiffContextSize, string(context.GetKey())) + } +} + +func TestDecreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) { + contexts := []func(gui *Gui) Context{ + func(gui *Gui) Context { return gui.State.Contexts.Files }, + func(gui *Gui) Context { return gui.State.Contexts.BranchCommits }, + func(gui *Gui) Context { return gui.State.Contexts.CommitFiles }, + func(gui *Gui) Context { return gui.State.Contexts.Stash }, + func(gui *Gui) Context { return gui.State.Contexts.Staging }, + func(gui *Gui) Context { return gui.State.Contexts.PatchBuilding }, + func(gui *Gui) Context { return gui.State.Contexts.SubCommits }, + } + + for _, c := range contexts { + gui := NewDummyGui() + context := c(gui) + setupGuiForTest(gui) + gui.UserConfig.Git.DiffContextSize = 2 + _ = gui.pushContext(context) + + _ = gui.DecreaseContextInDiffView() + + assert.Equal(t, 1, gui.UserConfig.Git.DiffContextSize, string(context.GetKey())) + } +} + +func TestDoesntDecreaseContextInDiffViewInContextWithoutDiff(t *testing.T) { + contexts := []func(gui *Gui) Context{ + func(gui *Gui) Context { return gui.State.Contexts.Status }, + func(gui *Gui) Context { return gui.State.Contexts.Submodules }, + func(gui *Gui) Context { return gui.State.Contexts.Remotes }, + func(gui *Gui) Context { return gui.State.Contexts.Normal }, + func(gui *Gui) Context { return gui.State.Contexts.ReflogCommits }, + func(gui *Gui) Context { return gui.State.Contexts.RemoteBranches }, + func(gui *Gui) Context { return gui.State.Contexts.Tags }, + // not testing this because it will kick straight back to the files context + // upon pushing the context + // func(gui *Gui) Context { return gui.State.Contexts.Merging }, + func(gui *Gui) Context { return gui.State.Contexts.CommandLog }, + } + + for _, c := range contexts { + gui := NewDummyGui() + context := c(gui) + setupGuiForTest(gui) + gui.UserConfig.Git.DiffContextSize = 2 + _ = gui.pushContext(context) + + _ = gui.DecreaseContextInDiffView() + + assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize, string(context.GetKey())) + } +} + +func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) { + gui := NewDummyGui() + setupGuiForTest(gui) + gui.UserConfig.Git.DiffContextSize = 2 + _ = gui.pushContext(gui.State.Contexts.CommitFiles) + gui.Git.Patch.PatchManager.Start("from", "to", false, false) + + errorCount := 0 + gui.PopupHandler = &TestPopupHandler{ + onError: func(message string) error { + assert.Equal(t, gui.Tr.CantChangeContextSizeError, message) + errorCount += 1 + return nil + }, + } + + _ = gui.IncreaseContextInDiffView() + + assert.Equal(t, 1, errorCount) + assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize) +} + +func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) { + gui := NewDummyGui() + setupGuiForTest(gui) + gui.UserConfig.Git.DiffContextSize = 2 + _ = gui.pushContext(gui.State.Contexts.CommitFiles) + gui.Git.Patch.PatchManager.Start("from", "to", false, false) + + errorCount := 0 + gui.PopupHandler = &TestPopupHandler{ + onError: func(message string) error { + assert.Equal(t, gui.Tr.CantChangeContextSizeError, message) + errorCount += 1 + return nil + }, + } + + _ = gui.DecreaseContextInDiffView() + + assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize) +} + +func TestDecreasesContextInDiffViewNoFurtherThanOne(t *testing.T) { + gui := NewDummyGui() + setupGuiForTest(gui) + gui.UserConfig.Git.DiffContextSize = 1 + + _ = gui.DecreaseContextInDiffView() + + assert.Equal(t, 1, gui.UserConfig.Git.DiffContextSize) +} diff --git a/pkg/gui/diffing.go b/pkg/gui/diffing.go index e1f3afa88..2721a1880 100644 --- a/pkg/gui/diffing.go +++ b/pkg/gui/diffing.go @@ -13,10 +13,10 @@ func (gui *Gui) exitDiffMode() error { } func (gui *Gui) renderDiff() error { - cmd := gui.OSCommand.ExecutableFromString( + cmdObj := gui.OSCommand.Cmd.New( fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", gui.diffStr()), ) - task := NewRunPtyTask(cmd) + task := NewRunPtyTask(cmdObj.GetCmd()) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ @@ -44,8 +44,8 @@ func (gui *Gui) currentDiffTerminals() []string { branch := gui.getSelectedBranch() if branch != nil { names := []string{branch.ID()} - if branch.UpstreamName != "" { - names = append(names, branch.UpstreamName) + if branch.IsTrackingRemote() { + names = append(names, branch.ID()+"@{u}") } return names } diff --git a/pkg/gui/discard_changes_menu_panel.go b/pkg/gui/discard_changes_menu_panel.go index bf20b5b8c..673f057e8 100644 --- a/pkg/gui/discard_changes_menu_panel.go +++ b/pkg/gui/discard_changes_menu_panel.go @@ -12,7 +12,8 @@ func (gui *Gui) handleCreateDiscardMenu() error { { displayString: gui.Tr.LcDiscardAllChanges, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DiscardAllChangesInDirectory).DiscardAllDirChanges(node); err != nil { + gui.logAction(gui.Tr.Actions.DiscardAllChangesInDirectory) + if err := gui.Git.WorkingTree.DiscardAllDirChanges(node); err != nil { return gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}}) @@ -24,7 +25,8 @@ func (gui *Gui) handleCreateDiscardMenu() error { menuItems = append(menuItems, &menuItem{ displayString: gui.Tr.LcDiscardUnstagedChanges, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DiscardUnstagedChangesInDirectory).DiscardUnstagedDirChanges(node); err != nil { + gui.logAction(gui.Tr.Actions.DiscardUnstagedChangesInDirectory) + if err := gui.Git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil { return gui.surfaceError(err) } @@ -52,7 +54,8 @@ func (gui *Gui) handleCreateDiscardMenu() error { { displayString: gui.Tr.LcDiscardAllChanges, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DiscardAllChangesInFile).DiscardAllFileChanges(file); err != nil { + gui.logAction(gui.Tr.Actions.DiscardAllChangesInFile) + if err := gui.Git.WorkingTree.DiscardAllFileChanges(file); err != nil { return gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}}) @@ -64,7 +67,8 @@ func (gui *Gui) handleCreateDiscardMenu() error { menuItems = append(menuItems, &menuItem{ displayString: gui.Tr.LcDiscardUnstagedChanges, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DiscardAllUnstagedChangesInFile).DiscardUnstagedFileChanges(file); err != nil { + gui.logAction(gui.Tr.Actions.DiscardAllUnstagedChangesInFile) + if err := gui.Git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil { return gui.surfaceError(err) } diff --git a/pkg/gui/dummies.go b/pkg/gui/dummies.go index 0e37bbbc8..587460ccd 100644 --- a/pkg/gui/dummies.go +++ b/pkg/gui/dummies.go @@ -1,10 +1,9 @@ package gui import ( - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/updates" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -12,12 +11,12 @@ import ( // NewDummyGui creates a new dummy GUI for testing func NewDummyUpdater() *updates.Updater { newAppConfig := config.NewDummyAppConfig() - DummyUpdater, _ := updates.NewUpdater(utils.NewDummyLog(), newAppConfig, oscommands.NewDummyOSCommand(), i18n.NewTranslationSet(utils.NewDummyLog(), newAppConfig.GetUserConfig().Gui.Language)) - return DummyUpdater + dummyUpdater, _ := updates.NewUpdater(utils.NewDummyCommon(), newAppConfig, oscommands.NewDummyOSCommand()) + return dummyUpdater } func NewDummyGui() *Gui { newAppConfig := config.NewDummyAppConfig() - DummyGui, _ := NewGui(utils.NewDummyLog(), commands.NewDummyGitCommand(), oscommands.NewDummyOSCommand(), i18n.NewTranslationSet(utils.NewDummyLog(), newAppConfig.GetUserConfig().Gui.Language), newAppConfig, NewDummyUpdater(), "", false) - return DummyGui + dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, git_config.NewFakeGitConfig(nil), NewDummyUpdater(), "", false) + return dummyGui } diff --git a/pkg/gui/editors.go b/pkg/gui/editors.go index 430dcfdfe..054a36094 100644 --- a/pkg/gui/editors.go +++ b/pkg/gui/editors.go @@ -7,7 +7,7 @@ import ( ) func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool { - newlineKey, ok := gui.getKey(gui.Config.GetUserConfig().Keybinding.Universal.AppendNewline).(gocui.Key) + newlineKey, ok := gui.getKey(gui.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key) if !ok { newlineKey = gocui.KeyAltEnter } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 6b5d0dd5d..fa8cfa79c 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -5,12 +5,13 @@ import ( "regexp" "strings" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui/filetree" + "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -22,7 +23,7 @@ func (gui *Gui) getSelectedFileNode() *filetree.FileNode { return nil } - return gui.State.FileManager.GetItemAtIndex(selectedLine) + return gui.State.FileTreeViewModel.GetItemAtIndex(selectedLine) } func (gui *Gui) getSelectedFile() *models.File { @@ -42,9 +43,7 @@ func (gui *Gui) getSelectedPath() string { return node.GetPath() } -func (gui *Gui) selectFile(alreadySelected bool) error { - gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) - +func (gui *Gui) filesRenderToMain() error { node := gui.getSelectedFileNode() if node == nil { @@ -56,30 +55,32 @@ func (gui *Gui) selectFile(alreadySelected bool) error { }) } - if !alreadySelected { - gui.takeOverMergeConflictScrolling() - } - if node.File != nil && node.File.HasInlineMergeConflicts { - return gui.refreshMergePanelWithLock() + ok, err := gui.setConflictsAndRenderWithLock(node.GetPath(), false) + if err != nil { + return err + } + if ok { + return nil + } } - cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) - cmd := gui.OSCommand.ExecutableFromString(cmdStr) + gui.resetMergeStateWithLock() + + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ title: gui.Tr.UnstagedChanges, - task: NewRunPtyTask(cmd), + task: NewRunPtyTask(cmdObj.GetCmd()), }} if node.GetHasUnstagedChanges() { if node.GetHasStagedChanges() { - cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(node, false, true, gui.State.IgnoreWhitespaceInDiffView) - cmd := gui.OSCommand.ExecutableFromString(cmdStr) + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView) refreshOpts.secondary = &viewUpdateOpts{ title: gui.Tr.StagedChanges, - task: NewRunPtyTask(cmd), + task: NewRunPtyTask(cmdObj.GetCmd()), } } } else { @@ -97,16 +98,21 @@ func (gui *Gui) refreshFilesAndSubmodules() error { gui.Mutexes.RefreshingFilesMutex.Unlock() }() - selectedPath := gui.getSelectedPath() + prevSelectedPath := gui.getSelectedPath() if err := gui.refreshStateSubmoduleConfigs(); err != nil { return err } + + if err := gui.refreshMergeState(); err != nil { + return err + } + if err := gui.refreshStateFiles(); err != nil { return err } - gui.g.Update(func(g *gocui.Gui) error { + gui.OnUIThread(func() error { if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil { gui.Log.Error(err) } @@ -118,12 +124,15 @@ func (gui *Gui) refreshFilesAndSubmodules() error { } } - if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (g.CurrentView() == gui.Views.Main && ContextKey(g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) { - newSelectedPath := gui.getSelectedPath() - alreadySelected := selectedPath != "" && newSelectedPath == selectedPath - if err := gui.selectFile(alreadySelected); err != nil { - return err + if gui.currentContext().GetKey() == FILES_CONTEXT_KEY { + currentSelectedPath := gui.getSelectedPath() + alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath + if !alreadySelected { + gui.takeOverMergeConflictScrolling() } + + gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) + return gui.filesRenderToMain() } return nil @@ -135,7 +144,7 @@ func (gui *Gui) refreshFilesAndSubmodules() error { // specific functions func (gui *Gui) stagedFiles() []*models.File { - files := gui.State.FileManager.GetAllFiles() + files := gui.State.FileTreeViewModel.GetAllFiles() result := make([]*models.File, 0) for _, file := range files { if file.HasStagedChanges { @@ -146,7 +155,7 @@ func (gui *Gui) stagedFiles() []*models.File { } func (gui *Gui) trackedFiles() []*models.File { - files := gui.State.FileManager.GetAllFiles() + files := gui.State.FileTreeViewModel.GetAllFiles() result := make([]*models.File, 0, len(files)) for _, file := range files { if file.Tracked { @@ -156,20 +165,11 @@ func (gui *Gui) trackedFiles() []*models.File { return result } -func (gui *Gui) stageSelectedFile() error { - file := gui.getSelectedFile() - if file == nil { - return nil - } - - return gui.GitCommand.StageFile(file.Name) -} - func (gui *Gui) handleEnterFile() error { - return gui.enterFile(false, -1) + return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1}) } -func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error { +func (gui *Gui) enterFile(opts OnFocusOpts) error { node := gui.getSelectedFileNode() if node == nil { return nil @@ -188,14 +188,13 @@ func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error } if file.HasInlineMergeConflicts { - return gui.handleSwitchToMerge() + return gui.switchToMerge() } if file.HasMergeConflicts { return gui.createErrorPanel(gui.Tr.FileStagingRequirements) } - _ = gui.pushContext(gui.State.Contexts.Staging) - return gui.handleRefreshStagingPanel(forceSecondaryFocused, selectedLineIdx) // TODO: check if this is broken, try moving into context code + return gui.pushContext(gui.State.Contexts.Staging, opts) } func (gui *Gui) handleFilePress() error { @@ -208,15 +207,17 @@ func (gui *Gui) handleFilePress() error { file := node.File if file.HasInlineMergeConflicts { - return gui.handleSwitchToMerge() + return gui.switchToMerge() } if file.HasUnstagedChanges { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.StageFile).StageFile(file.Name); err != nil { + gui.logAction(gui.Tr.Actions.StageFile) + if err := gui.Git.WorkingTree.StageFile(file.Name); err != nil { return gui.surfaceError(err) } } else { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.UnstageFile).UnStageFile(file.Names(), file.Tracked); err != nil { + gui.logAction(gui.Tr.Actions.UnstageFile) + if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil { return gui.surfaceError(err) } } @@ -228,12 +229,14 @@ func (gui *Gui) handleFilePress() error { } if node.GetHasUnstagedChanges() { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.StageFile).StageFile(node.Path); err != nil { + gui.logAction(gui.Tr.Actions.StageFile) + if err := gui.Git.WorkingTree.StageFile(node.Path); err != nil { return gui.surfaceError(err) } } else { // pretty sure it doesn't matter that we're always passing true here - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.UnstageFile).UnStageFile([]string{node.Path}, true); err != nil { + gui.logAction(gui.Tr.Actions.UnstageFile) + if err := gui.Git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil { return gui.surfaceError(err) } } @@ -243,11 +246,11 @@ func (gui *Gui) handleFilePress() error { return err } - return gui.selectFile(true) + return gui.State.Contexts.Files.HandleFocus() } func (gui *Gui) allFilesStaged() bool { - for _, file := range gui.State.FileManager.GetAllFiles() { + for _, file := range gui.State.FileTreeViewModel.GetAllFiles() { if file.HasUnstagedChanges { return false } @@ -255,16 +258,19 @@ func (gui *Gui) allFilesStaged() bool { return true } -func (gui *Gui) focusAndSelectFile() error { - return gui.selectFile(false) +func (gui *Gui) onFocusFile() error { + gui.takeOverMergeConflictScrolling() + return nil } func (gui *Gui) handleStageAll() error { var err error if gui.allFilesStaged() { - err = gui.GitCommand.WithSpan(gui.Tr.Spans.UnstageAllFiles).UnstageAll() + gui.logAction(gui.Tr.Actions.UnstageAllFiles) + err = gui.Git.WorkingTree.UnstageAll() } else { - err = gui.GitCommand.WithSpan(gui.Tr.Spans.StageAllFiles).StageAll() + gui.logAction(gui.Tr.Actions.StageAllFiles) + err = gui.Git.WorkingTree.StageAll() } if err != nil { _ = gui.surfaceError(err) @@ -274,7 +280,7 @@ func (gui *Gui) handleStageAll() error { return err } - return gui.selectFile(false) + return gui.State.Contexts.Files.HandleFocus() } func (gui *Gui) handleIgnoreFile() error { @@ -287,12 +293,10 @@ func (gui *Gui) handleIgnoreFile() error { return gui.createErrorPanel("Cannot ignore .gitignore") } - gitCommand := gui.GitCommand.WithSpan(gui.Tr.Spans.IgnoreFile) - unstageFiles := func() error { return node.ForEachFile(func(file *models.File) error { if file.HasStagedChanges { - if err := gitCommand.UnStageFile(file.Names(), file.Tracked); err != nil { + if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil { return err } } @@ -306,16 +310,17 @@ func (gui *Gui) handleIgnoreFile() error { title: gui.Tr.IgnoreTracked, prompt: gui.Tr.IgnoreTrackedPrompt, handleConfirm: func() error { + gui.logAction(gui.Tr.Actions.IgnoreFile) // not 100% sure if this is necessary but I'll assume it is if err := unstageFiles(); err != nil { return err } - if err := gitCommand.RemoveTrackedFiles(node.GetPath()); err != nil { + if err := gui.Git.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil { return err } - if err := gitCommand.Ignore(node.GetPath()); err != nil { + if err := gui.Git.WorkingTree.Ignore(node.GetPath()); err != nil { return err } return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}) @@ -323,11 +328,13 @@ func (gui *Gui) handleIgnoreFile() error { }) } + gui.logAction(gui.Tr.Actions.IgnoreFile) + if err := unstageFiles(); err != nil { return err } - if err := gitCommand.Ignore(node.GetPath()); err != nil { + if err := gui.Git.WorkingTree.Ignore(node.GetPath()); err != nil { return gui.surfaceError(err) } @@ -335,7 +342,7 @@ func (gui *Gui) handleIgnoreFile() error { } func (gui *Gui) handleWIPCommitPress() error { - skipHookPrefix := gui.Config.GetUserConfig().Git.SkipHookPrefix + skipHookPrefix := gui.UserConfig.Git.SkipHookPrefix if skipHookPrefix == "" { return gui.createErrorPanel(gui.Tr.SkipHookPrefixNotConfigured) } @@ -349,7 +356,7 @@ func (gui *Gui) handleWIPCommitPress() error { } func (gui *Gui) commitPrefixConfigForRepo() *config.CommitPrefixConfig { - cfg, ok := gui.Config.GetUserConfig().Git.CommitPrefixes[utils.GetCurrentRepoName()] + cfg, ok := gui.UserConfig.Git.CommitPrefixes[utils.GetCurrentRepoName()] if !ok { return nil } @@ -359,8 +366,9 @@ func (gui *Gui) commitPrefixConfigForRepo() *config.CommitPrefixConfig { func (gui *Gui) prepareFilesForCommit() error { noStagedFiles := len(gui.stagedFiles()) == 0 - if noStagedFiles && gui.Config.GetUserConfig().Gui.SkipNoStagedFilesWarning { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.StageAllFiles).StageAll() + if noStagedFiles && gui.UserConfig.Gui.SkipNoStagedFilesWarning { + gui.logAction(gui.Tr.Actions.StageAllFiles) + err := gui.Git.WorkingTree.StageAll() if err != nil { return err } @@ -376,7 +384,7 @@ func (gui *Gui) handleCommitPress() error { return gui.surfaceError(err) } - if gui.State.FileManager.GetItemsLength() == 0 { + if gui.State.FileTreeViewModel.GetItemsLength() == 0 { return gui.createErrorPanel(gui.Tr.NoFilesStagedTitle) } @@ -384,28 +392,31 @@ func (gui *Gui) handleCommitPress() error { return gui.promptToStageAllAndRetry(gui.handleCommitPress) } - commitPrefixConfig := gui.commitPrefixConfigForRepo() - if commitPrefixConfig != nil { - prefixPattern := commitPrefixConfig.Pattern - prefixReplace := commitPrefixConfig.Replace - rgx, err := regexp.Compile(prefixPattern) - if err != nil { - return gui.createErrorPanel(fmt.Sprintf("%s: %s", gui.Tr.LcCommitPrefixPatternError, err.Error())) - } - prefix := rgx.ReplaceAllString(gui.getCheckedOutBranch().Name, prefixReplace) + if len(gui.State.failedCommitMessage) > 0 { gui.Views.CommitMessage.ClearTextArea() - gui.Views.CommitMessage.TextArea.TypeString(prefix) - gui.render() + gui.Views.CommitMessage.TextArea.TypeString(gui.State.failedCommitMessage) + gui.Views.CommitMessage.RenderTextArea() + } else { + commitPrefixConfig := gui.commitPrefixConfigForRepo() + if commitPrefixConfig != nil { + prefixPattern := commitPrefixConfig.Pattern + prefixReplace := commitPrefixConfig.Replace + rgx, err := regexp.Compile(prefixPattern) + if err != nil { + return gui.createErrorPanel(fmt.Sprintf("%s: %s", gui.Tr.LcCommitPrefixPatternError, err.Error())) + } + prefix := rgx.ReplaceAllString(gui.getCheckedOutBranch().Name, prefixReplace) + gui.Views.CommitMessage.ClearTextArea() + gui.Views.CommitMessage.TextArea.TypeString(prefix) + gui.Views.CommitMessage.RenderTextArea() + } } - gui.g.Update(func(g *gocui.Gui) error { - if err := gui.pushContext(gui.State.Contexts.CommitMessage); err != nil { - return err - } + if err := gui.pushContext(gui.State.Contexts.CommitMessage); err != nil { + return err + } - gui.RenderCommitLength() - return nil - }) + gui.RenderCommitLength() return nil } @@ -414,7 +425,8 @@ func (gui *Gui) promptToStageAllAndRetry(retry func() error) error { title: gui.Tr.NoFilesStagedTitle, prompt: gui.Tr.NoFilesStagedPrompt, handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.StageAllFiles).StageAll(); err != nil { + gui.logAction(gui.Tr.Actions.StageAllFiles) + if err := gui.Git.WorkingTree.StageAll(); err != nil { return gui.surfaceError(err) } if err := gui.refreshFilesAndSubmodules(); err != nil { @@ -427,7 +439,7 @@ func (gui *Gui) promptToStageAllAndRetry(retry func() error) error { } func (gui *Gui) handleAmendCommitPress() error { - if gui.State.FileManager.GetItemsLength() == 0 { + if gui.State.FileTreeViewModel.GetItemsLength() == 0 { return gui.createErrorPanel(gui.Tr.NoFilesStagedTitle) } @@ -443,9 +455,9 @@ func (gui *Gui) handleAmendCommitPress() error { title: strings.Title(gui.Tr.AmendLastCommit), prompt: gui.Tr.SureToAmend, handleConfirm: func() error { - cmdStr := gui.GitCommand.AmendHeadCmdStr() - gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdStr, gui.Tr.Spans.AmendCommit, true)) - return gui.withGpgHandling(cmdStr, gui.Tr.AmendingStatus, nil) + cmdObj := gui.Git.Commit.AmendHeadCmdObj() + gui.logAction(gui.Tr.Actions.AmendCommit) + return gui.withGpgHandling(cmdObj, gui.Tr.AmendingStatus, nil) }, }) } @@ -453,7 +465,7 @@ func (gui *Gui) handleAmendCommitPress() error { // handleCommitEditorPress - handle when the user wants to commit changes via // their editor rather than via the popup panel func (gui *Gui) handleCommitEditorPress() error { - if gui.State.FileManager.GetItemsLength() == 0 { + if gui.State.FileTreeViewModel.GetItemsLength() == 0 { return gui.createErrorPanel(gui.Tr.NoFilesStagedTitle) } @@ -461,8 +473,9 @@ func (gui *Gui) handleCommitEditorPress() error { return gui.promptToStageAllAndRetry(gui.handleCommitEditorPress) } + gui.logAction(gui.Tr.Actions.Commit) return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.Commit).PrepareSubProcess("git", "commit"), + gui.Git.Commit.CommitEditorCmdObj(), ) } @@ -491,9 +504,9 @@ func (gui *Gui) handleStatusFilterPressed() error { return gui.createMenu(gui.Tr.FilteringMenuTitle, menuItems, createMenuOptions{showCancel: true}) } -func (gui *Gui) setStatusFiltering(filter filetree.FileManagerDisplayFilter) error { +func (gui *Gui) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error { state := gui.State - state.FileManager.SetDisplayFilter(filter) + state.FileTreeViewModel.SetFilter(filter) return gui.handleRefreshFiles() } @@ -502,13 +515,14 @@ func (gui *Gui) editFile(filename string) error { } func (gui *Gui) editFileAtLine(filename string, lineNumber int) error { - cmdStr, err := gui.GitCommand.EditFileCmdStr(filename, lineNumber) + cmdStr, err := gui.Git.File.GetEditCmdStr(filename, lineNumber) if err != nil { return gui.surfaceError(err) } + gui.logAction(gui.Tr.Actions.EditFile) return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).ShellCommandFromString(cmdStr), + gui.OSCommand.Cmd.NewShell(cmdStr), ) } @@ -547,30 +561,85 @@ func (gui *Gui) refreshStateFiles() error { selectedNode := gui.getSelectedFileNode() - prevNodes := gui.State.FileManager.GetAllItems() + prevNodes := gui.State.FileTreeViewModel.GetAllItems() prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx - files := gui.GitCommand.GetStatusFiles(commands.GetStatusFileOptions{}) - - // for when you stage the old file of a rename and the new file is in a collapsed dir - state.FileManager.RWMutex.Lock() - for _, file := range files { - if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path { - state.FileManager.ExpandToPath(file.Name) + // 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{} + prevConflictFileCount := 0 + for _, file := range state.FileTreeViewModel.GetAllFiles() { + if file.HasMergeConflicts { + prevConflictFileCount++ + } + if file.HasInlineMergeConflicts { + hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name) + if err != nil { + gui.Log.Error(err) + } else if !hasConflicts { + pathsToStage = append(pathsToStage, file.Name) + } } } - state.FileManager.SetFiles(files) - state.FileManager.RWMutex.Unlock() + if len(pathsToStage) > 0 { + gui.logAction(gui.Tr.Actions.StageResolvedFiles) + if err := gui.Git.WorkingTree.StageFiles(pathsToStage); err != nil { + return gui.surfaceError(err) + } + } + + files := gui.Git.Loaders.Files. + GetStatusFiles(loaders.GetStatusFileOptions{}) + + conflictFileCount := 0 + for _, file := range files { + if file.HasMergeConflicts { + conflictFileCount++ + } + } + + if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 { + gui.OnUIThread(func() error { return gui.promptToContinueRebase() }) + } + + // for when you stage the old file of a rename and the new file is in a collapsed dir + state.FileTreeViewModel.RWMutex.Lock() + for _, file := range files { + if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path { + state.FileTreeViewModel.ExpandToPath(file.Name) + } + } + + // only taking over the filter if it hasn't already been set by the user. + // Though this does make it impossible for the user to actually say they want to display all if + // conflicts are currently being shown. Hmm. Worth it I reckon. If we need to add some + // extra state here to see if the user's set the filter themselves we can do that, but + // I'd prefer to maintain as little state as possible. + if conflictFileCount > 0 { + if state.FileTreeViewModel.GetFilter() == filetree.DisplayAll { + state.FileTreeViewModel.SetFilter(filetree.DisplayConflicted) + } + } else if state.FileTreeViewModel.GetFilter() == filetree.DisplayConflicted { + state.FileTreeViewModel.SetFilter(filetree.DisplayAll) + } + + state.FileTreeViewModel.SetFiles(files) + state.FileTreeViewModel.RWMutex.Unlock() if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil { return err } if selectedNode != nil { - newIdx := gui.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], state.FileManager.GetAllItems()) + newIdx := gui.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], state.FileTreeViewModel.GetAllItems()) if newIdx != -1 && newIdx != prevSelectedLineIdx { - newNode := state.FileManager.GetItemAtIndex(newIdx) + newNode := state.FileTreeViewModel.GetItemAtIndex(newIdx) // when not in tree mode, we show merge conflict files at the top, so you // can work through them one by one without having to sift through a large // set of files. If you have just fixed the merge conflicts of a file, we @@ -579,7 +648,7 @@ func (gui *Gui) refreshStateFiles() error { // conflicts: the user in this case would rather work on the next file // with merge conflicts, which will have moved up to fill the gap left by // the last file, meaning the cursor doesn't need to move at all. - leaveCursor := !state.FileManager.InTreeMode() && newNode != nil && + leaveCursor := !state.FileTreeViewModel.InTreeMode() && newNode != nil && selectedNode.File != nil && selectedNode.File.HasMergeConflicts && newNode.File != nil && !newNode.File.HasMergeConflicts @@ -589,10 +658,23 @@ func (gui *Gui) refreshStateFiles() error { } } - gui.refreshSelectedLine(state.Panels.Files, state.FileManager.GetItemsLength()) + gui.refreshSelectedLine(state.Panels.Files, state.FileTreeViewModel.GetItemsLength()) return nil } +// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress +func (gui *Gui) promptToContinueRebase() error { + gui.takeOverMergeConflictScrolling() + + return gui.ask(askOpts{ + title: "continue", + prompt: gui.Tr.ConflictsResolved, + handleConfirm: func() error { + return gui.genericMergeCommand(REBASE_OPTION_CONTINUE) + }, + }) +} + // 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 @@ -636,7 +718,7 @@ func (gui *Gui) handlePullFiles() error { return nil } - span := gui.Tr.Spans.Pull + action := gui.Tr.Actions.Pull currentBranch := gui.currentBranch() if currentBranch == nil { @@ -646,42 +728,42 @@ func (gui *Gui) handlePullFiles() error { // if we have no upstream branch we need to set that first if !currentBranch.IsTrackingRemote() { - // see if we have this branch in our config with an upstream - conf, err := gui.GitCommand.Repo.Config() - if err != nil { - return gui.surfaceError(err) - } - for branchName, branch := range conf.Branches { - if branchName == currentBranch.Name { - return gui.pullFiles(PullFilesOptions{RemoteName: branch.Remote, BranchName: branch.Name, span: span}) - } - } + suggestedRemote := getSuggestedRemote(gui.State.Remotes) return gui.prompt(promptOpts{ title: gui.Tr.EnterUpstream, - initialContent: "origin/" + currentBranch.Name, - findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc("/"), + initialContent: suggestedRemote + " " + currentBranch.Name, + findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "), handleConfirm: func(upstream string) error { - if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil { + var upstreamBranch, upstreamRemote string + split := strings.Split(upstream, " ") + if len(split) != 2 { + return gui.createErrorPanel(gui.Tr.InvalidUpstream) + } + + upstreamRemote = split[0] + upstreamBranch = split[1] + + if err := gui.Git.Branch.SetCurrentBranchUpstream(upstreamRemote, upstreamBranch); err != nil { errorMessage := err.Error() if strings.Contains(errorMessage, "does not exist") { errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream) } return gui.createErrorPanel(errorMessage) } - return gui.pullFiles(PullFilesOptions{span: span}) + return gui.pullFiles(PullFilesOptions{UpstreamRemote: upstreamRemote, UpstreamBranch: upstreamBranch, action: action}) }, }) } - return gui.pullFiles(PullFilesOptions{span: span}) + return gui.pullFiles(PullFilesOptions{UpstreamRemote: currentBranch.UpstreamRemote, UpstreamBranch: currentBranch.UpstreamBranch, action: action}) } type PullFilesOptions struct { - RemoteName string - BranchName string + UpstreamRemote string + UpstreamBranch string FastForwardOnly bool - span string + action string } func (gui *Gui) pullFiles(opts PullFilesOptions) error { @@ -699,14 +781,13 @@ func (gui *Gui) pullWithLock(opts PullFilesOptions) error { gui.Mutexes.FetchMutex.Lock() defer gui.Mutexes.FetchMutex.Unlock() - gitCommand := gui.GitCommand.WithSpan(opts.span) + gui.logAction(opts.action) - err := gitCommand.Pull( - commands.PullOptions{ - PromptUserForCredential: gui.promptUserForCredential, - RemoteName: opts.RemoteName, - BranchName: opts.BranchName, - FastForwardOnly: opts.FastForwardOnly, + err := gui.Git.Sync.Pull( + git_commands.PullOptions{ + RemoteName: opts.UpstreamRemote, + BranchName: opts.UpstreamBranch, + FastForwardOnly: opts.FastForwardOnly, }, ) if err == nil { @@ -727,16 +808,16 @@ func (gui *Gui) push(opts pushOpts) error { return err } go utils.Safe(func() { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.Push).Push(commands.PushOpts{ - Force: opts.force, - UpstreamRemote: opts.upstreamRemote, - UpstreamBranch: opts.upstreamBranch, - SetUpstream: opts.setUpstream, - PromptUserForCredential: gui.promptUserForCredential, + gui.logAction(gui.Tr.Actions.Push) + err := gui.Git.Sync.Push(git_commands.PushOpts{ + Force: opts.force, + UpstreamRemote: opts.upstreamRemote, + UpstreamBranch: opts.upstreamBranch, + SetUpstream: opts.setUpstream, }) if err != nil && !opts.force && strings.Contains(err.Error(), "Updates were rejected") { - forcePushDisabled := gui.Config.GetUserConfig().Git.DisableForcePushing + forcePushDisabled := gui.UserConfig.Git.DisableForcePushing if forcePushDisabled { _ = gui.createErrorPanel(gui.Tr.UpdatesRejectedAndForcePushDisabled) return @@ -772,34 +853,26 @@ func (gui *Gui) pushFiles() error { } if currentBranch.IsTrackingRemote() { + opts := pushOpts{ + force: false, + upstreamRemote: currentBranch.UpstreamRemote, + upstreamBranch: currentBranch.UpstreamBranch, + } if currentBranch.HasCommitsToPull() { - return gui.requestToForcePush() + opts.force = true + return gui.requestToForcePush(opts) } else { - return gui.push(pushOpts{}) + return gui.push(opts) } } else { - // see if we have an upstream for this branch in our config - upstreamRemote, upstreamBranch, err := gui.upstreamForBranchInConfig(currentBranch.Name) - if err != nil { - return gui.surfaceError(err) - } + suggestedRemote := getSuggestedRemote(gui.State.Remotes) - if upstreamBranch != "" { - return gui.push( - pushOpts{ - force: false, - upstreamRemote: upstreamRemote, - upstreamBranch: upstreamBranch, - }, - ) - } - - if gui.GitCommand.PushToCurrent { + if gui.Git.Config.GetPushToCurrent() { return gui.push(pushOpts{setUpstream: true}) } else { return gui.prompt(promptOpts{ title: gui.Tr.EnterUpstream, - initialContent: "origin " + currentBranch.Name, + initialContent: suggestedRemote + " " + currentBranch.Name, findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "), handleConfirm: func(upstream string) error { var upstreamBranch, upstreamRemote string @@ -824,8 +897,22 @@ func (gui *Gui) pushFiles() error { } } -func (gui *Gui) requestToForcePush() error { - forcePushDisabled := gui.Config.GetUserConfig().Git.DisableForcePushing +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 +} + +func (gui *Gui) requestToForcePush(opts pushOpts) error { + forcePushDisabled := gui.UserConfig.Git.DisableForcePushing if forcePushDisabled { return gui.createErrorPanel(gui.Tr.ForcePushDisabled) } @@ -834,55 +921,40 @@ func (gui *Gui) requestToForcePush() error { title: gui.Tr.ForcePush, prompt: gui.Tr.ForcePushPrompt, handleConfirm: func() error { - return gui.push(pushOpts{force: true}) + return gui.push(opts) }, }) } -func (gui *Gui) upstreamForBranchInConfig(branchName string) (string, string, error) { - conf, err := gui.GitCommand.Repo.Config() - if err != nil { - return "", "", err - } - - for configBranchName, configBranch := range conf.Branches { - if configBranchName == branchName { - return configBranch.Remote, configBranchName, nil - } - } - - return "", "", nil -} - -func (gui *Gui) handleSwitchToMerge() error { +func (gui *Gui) switchToMerge() error { file := gui.getSelectedFile() if file == nil { return nil } - if !file.HasInlineMergeConflicts { - return gui.createErrorPanel(gui.Tr.FileNoMergeCons) + gui.takeOverMergeConflictScrolling() + + if gui.State.Panels.Merging.GetPath() != file.Name { + hasConflicts, err := gui.setMergeStateWithLock(file.Name) + if err != nil { + return err + } + if !hasConflicts { + return nil + } } return gui.pushContext(gui.State.Contexts.Merging) } func (gui *Gui) openFile(filename string) error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.OpenFile).OpenFile(filename); err != nil { + gui.logAction(gui.Tr.Actions.OpenFile) + if err := gui.OSCommand.OpenFile(filename); err != nil { return gui.surfaceError(err) } return nil } -func (gui *Gui) anyFilesWithMergeConflicts() bool { - for _, file := range gui.State.FileManager.GetAllFiles() { - if file.HasMergeConflicts { - return true - } - } - return false -} - func (gui *Gui) handleCustomCommand() error { return gui.prompt(promptOpts{ title: gui.Tr.CustomCommand, @@ -900,9 +972,9 @@ func (gui *Gui) handleCustomCommand() error { gui.Log.Error(err) } - gui.OnRunCommand(oscommands.NewCmdLogEntry(command, gui.Tr.Spans.CustomCommand, true)) + gui.logAction(gui.Tr.Actions.CustomCommand) return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.PrepareShellSubProcess(command), + gui.OSCommand.Cmd.NewShell(command), ) }, }) @@ -913,13 +985,15 @@ func (gui *Gui) handleCreateStashMenu() error { { displayString: gui.Tr.LcStashAllChanges, onPress: func() error { - return gui.handleStashSave(gui.GitCommand.WithSpan(gui.Tr.Spans.StashAllChanges).StashSave) + gui.logAction(gui.Tr.Actions.StashAllChanges) + return gui.handleStashSave(gui.Git.Stash.Save) }, }, { displayString: gui.Tr.LcStashStagedChanges, onPress: func() error { - return gui.handleStashSave(gui.GitCommand.WithSpan(gui.Tr.Spans.StashStagedChanges).StashSaveStagedChanges) + gui.logAction(gui.Tr.Actions.StashStagedChanges) + return gui.handleStashSave(gui.Git.Stash.SaveStagedChanges) }, }, } @@ -928,7 +1002,7 @@ func (gui *Gui) handleCreateStashMenu() error { } func (gui *Gui) handleStashChanges() error { - return gui.handleStashSave(gui.GitCommand.StashSave) + return gui.handleStashSave(gui.Git.Stash.Save) } func (gui *Gui) handleCreateResetToUpstreamMenu() error { @@ -941,7 +1015,7 @@ func (gui *Gui) handleToggleDirCollapsed() error { return nil } - gui.State.FileManager.ToggleCollapsed(node.GetPath()) + gui.State.FileTreeViewModel.ToggleCollapsed(node.GetPath()) if err := gui.postRefreshUpdate(gui.State.Contexts.Files); err != nil { gui.Log.Error(err) @@ -954,12 +1028,12 @@ func (gui *Gui) handleToggleFileTreeView() error { // get path of currently selected file path := gui.getSelectedPath() - gui.State.FileManager.ToggleShowTree() + gui.State.FileTreeViewModel.ToggleShowTree() // find that same node in the new format and move the cursor to it if path != "" { - gui.State.FileManager.ExpandToPath(path) - index, found := gui.State.FileManager.GetIndexForPath(path) + gui.State.FileTreeViewModel.ExpandToPath(path) + index, found := gui.State.FileTreeViewModel.GetIndexForPath(path) if found { gui.filesListContext().GetPanelState().SetSelectedLineIdx(index) } @@ -982,8 +1056,9 @@ func (gui *Gui) handleOpenMergeTool() error { title: gui.Tr.MergeToolTitle, prompt: gui.Tr.MergeToolPrompt, handleConfirm: func() error { + gui.logAction(gui.Tr.Actions.OpenMergeTool) return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.ExecutableFromString(gui.GitCommand.OpenMergeToolCmd()), + gui.Git.WorkingTree.OpenMergeToolCmdObj(), ) }, }) diff --git a/pkg/gui/files_panel_test.go b/pkg/gui/files_panel_test.go new file mode 100644 index 000000000..8946898e5 --- /dev/null +++ b/pkg/gui/files_panel_test.go @@ -0,0 +1,34 @@ +package gui + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "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 { + result := make([]*models.Remote, 0, len(names)) + + for _, name := range names { + result = append(result, &models.Remote{Name: name}) + } + + return result +} diff --git a/pkg/gui/filetree/README.md b/pkg/gui/filetree/README.md new file mode 100644 index 000000000..d2d16ace6 --- /dev/null +++ b/pkg/gui/filetree/README.md @@ -0,0 +1,22 @@ +## 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/commit_file_manager.go b/pkg/gui/filetree/commit_file_manager.go deleted file mode 100644 index e80528bcb..000000000 --- a/pkg/gui/filetree/commit_file_manager.go +++ /dev/null @@ -1,119 +0,0 @@ -package filetree - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/sirupsen/logrus" -) - -type CommitFileManager struct { - files []*models.CommitFile - tree *CommitFileNode - showTree bool - log *logrus.Entry - collapsedPaths CollapsedPaths - // parent is the identifier of the parent object e.g. a commit SHA if this commit file is for a commit, or a stash entry ref like 'stash@{1}' - parent string -} - -func (m *CommitFileManager) GetParent() string { - return m.parent -} - -func NewCommitFileManager(files []*models.CommitFile, log *logrus.Entry, showTree bool) *CommitFileManager { - return &CommitFileManager{ - files: files, - log: log, - showTree: showTree, - collapsedPaths: CollapsedPaths{}, - } -} - -func (m *CommitFileManager) ExpandToPath(path string) { - m.collapsedPaths.ExpandToPath(path) -} - -func (m *CommitFileManager) ToggleShowTree() { - m.showTree = !m.showTree - m.SetTree() -} - -func (m *CommitFileManager) GetItemAtIndex(index int) *CommitFileNode { - // need to traverse the three depth first until we get to the index. - return m.tree.GetNodeAtIndex(index+1, m.collapsedPaths) // ignoring root -} - -func (m *CommitFileManager) GetIndexForPath(path string) (int, bool) { - index, found := m.tree.GetIndexForPath(path, m.collapsedPaths) - return index - 1, found -} - -func (m *CommitFileManager) GetAllItems() []*CommitFileNode { - if m.tree == nil { - return nil - } - - return m.tree.Flatten(m.collapsedPaths)[1:] // ignoring root -} - -func (m *CommitFileManager) GetItemsLength() int { - return m.tree.Size(m.collapsedPaths) - 1 // ignoring root -} - -func (m *CommitFileManager) GetAllFiles() []*models.CommitFile { - return m.files -} - -func (m *CommitFileManager) SetFiles(files []*models.CommitFile, parent string) { - m.files = files - m.parent = parent - - m.SetTree() -} - -func (m *CommitFileManager) SetTree() { - if m.showTree { - m.tree = BuildTreeFromCommitFiles(m.files) - } else { - m.tree = BuildFlatTreeFromCommitFiles(m.files) - } -} - -func (m *CommitFileManager) IsCollapsed(path string) bool { - return m.collapsedPaths.IsCollapsed(path) -} - -func (m *CommitFileManager) ToggleCollapsed(path string) { - m.collapsedPaths.ToggleCollapsed(path) -} - -func (m *CommitFileManager) Render(diffName string, patchManager *patch.PatchManager) []string { - // can't rely on renderAux to check for nil because an interface won't be nil if its concrete value is nil - if m.tree == nil { - return []string{} - } - - return renderAux(m.tree, m.collapsedPaths, "", -1, func(n INode, depth int) string { - castN := n.(*CommitFileNode) - - // 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 - var status patch.PatchStatus - if castN.EveryFile(func(file *models.CommitFile) bool { - return patchManager.GetFileStatus(file.Name, m.parent) == patch.WHOLE - }) { - status = patch.WHOLE - } else if castN.EveryFile(func(file *models.CommitFile) bool { - return patchManager.GetFileStatus(file.Name, m.parent) == patch.UNSELECTED - }) { - status = patch.UNSELECTED - } else { - status = patch.PART - } - - return presentation.GetCommitFileLine(castN.NameAtDepth(depth), diffName, castN.File, status) - }) -} diff --git a/pkg/gui/filetree/commit_file_node.go b/pkg/gui/filetree/commit_file_node.go index 173281035..14960ee30 100644 --- a/pkg/gui/filetree/commit_file_node.go +++ b/pkg/gui/filetree/commit_file_node.go @@ -11,6 +11,8 @@ type CommitFileNode struct { CompressionLevel int // equal to the number of forward slashes you'll see in the path when it's rendered in tree mode } +var _ INode = &CommitFileNode{} + // methods satisfying ListItem interface func (s *CommitFileNode) ID() string { @@ -23,6 +25,10 @@ func (s *CommitFileNode) Description() string { // methods satisfying INode interface +func (s *CommitFileNode) IsNil() bool { + return s == nil +} + func (s *CommitFileNode) IsLeaf() bool { return s.File != nil } @@ -139,13 +145,6 @@ func (s *CommitFileNode) Compress() { compressAux(s) } -// This ignores the root -func (node *CommitFileNode) GetPathsMatching(test func(*CommitFileNode) bool) []string { - return getPathsMatching(node, func(n INode) bool { - return test(n.(*CommitFileNode)) - }) -} - func (s *CommitFileNode) GetLeaves() []*CommitFileNode { leaves := getLeaves(s) castLeaves := make([]*CommitFileNode, len(leaves)) diff --git a/pkg/gui/filetree/commit_file_tree_view_model.go b/pkg/gui/filetree/commit_file_tree_view_model.go new file mode 100644 index 000000000..301396462 --- /dev/null +++ b/pkg/gui/filetree/commit_file_tree_view_model.go @@ -0,0 +1,101 @@ +package filetree + +import ( + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/sirupsen/logrus" +) + +type CommitFileTreeViewModel struct { + files []*models.CommitFile + tree *CommitFileNode + showTree bool + log *logrus.Entry + collapsedPaths CollapsedPaths + // parent is the identifier of the parent object e.g. a commit SHA if this commit file is for a commit, or a stash entry ref like 'stash@{1}' + parent string +} + +func (self *CommitFileTreeViewModel) GetParent() string { + return self.parent +} + +func (self *CommitFileTreeViewModel) SetParent(parent string) { + self.parent = parent +} + +func NewCommitFileTreeViewModel(files []*models.CommitFile, log *logrus.Entry, showTree bool) *CommitFileTreeViewModel { + viewModel := &CommitFileTreeViewModel{ + log: log, + showTree: showTree, + collapsedPaths: CollapsedPaths{}, + } + + viewModel.SetFiles(files) + + return viewModel +} + +func (self *CommitFileTreeViewModel) ExpandToPath(path string) { + self.collapsedPaths.ExpandToPath(path) +} + +func (self *CommitFileTreeViewModel) ToggleShowTree() { + self.showTree = !self.showTree + self.SetTree() +} + +func (self *CommitFileTreeViewModel) GetItemAtIndex(index int) *CommitFileNode { + // need to traverse the three depth first until we get to the index. + return self.tree.GetNodeAtIndex(index+1, self.collapsedPaths) // ignoring root +} + +func (self *CommitFileTreeViewModel) GetIndexForPath(path string) (int, bool) { + index, found := self.tree.GetIndexForPath(path, self.collapsedPaths) + return index - 1, found +} + +func (self *CommitFileTreeViewModel) GetAllItems() []*CommitFileNode { + if self.tree == nil { + return nil + } + + return self.tree.Flatten(self.collapsedPaths)[1:] // ignoring root +} + +func (self *CommitFileTreeViewModel) GetItemsLength() int { + return self.tree.Size(self.collapsedPaths) - 1 // ignoring root +} + +func (self *CommitFileTreeViewModel) GetAllFiles() []*models.CommitFile { + return self.files +} + +func (self *CommitFileTreeViewModel) SetFiles(files []*models.CommitFile) { + self.files = files + + self.SetTree() +} + +func (self *CommitFileTreeViewModel) SetTree() { + if self.showTree { + self.tree = BuildTreeFromCommitFiles(self.files) + } else { + self.tree = BuildFlatTreeFromCommitFiles(self.files) + } +} + +func (self *CommitFileTreeViewModel) IsCollapsed(path string) bool { + return self.collapsedPaths.IsCollapsed(path) +} + +func (self *CommitFileTreeViewModel) ToggleCollapsed(path string) { + self.collapsedPaths.ToggleCollapsed(path) +} + +func (self *CommitFileTreeViewModel) Tree() INode { + return self.tree +} + +func (self *CommitFileTreeViewModel) CollapsedPaths() CollapsedPaths { + return self.collapsedPaths +} diff --git a/pkg/gui/filetree/constants.go b/pkg/gui/filetree/constants.go deleted file mode 100644 index d510650e2..000000000 --- a/pkg/gui/filetree/constants.go +++ /dev/null @@ -1,9 +0,0 @@ -package filetree - -const EXPANDED_ARROW = "▼" -const COLLAPSED_ARROW = "►" - -const INNER_ITEM = "├─ " -const LAST_ITEM = "└─ " -const NESTED = "│ " -const NOTHING = " " diff --git a/pkg/gui/filetree/file_manager.go b/pkg/gui/filetree/file_manager.go deleted file mode 100644 index 699e20332..000000000 --- a/pkg/gui/filetree/file_manager.go +++ /dev/null @@ -1,141 +0,0 @@ -package filetree - -import ( - "sync" - - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" - "github.com/sirupsen/logrus" -) - -type FileManagerDisplayFilter int - -const ( - DisplayAll FileManagerDisplayFilter = iota - DisplayStaged - DisplayUnstaged -) - -type FileManager struct { - files []*models.File - tree *FileNode - showTree bool - log *logrus.Entry - filter FileManagerDisplayFilter - collapsedPaths CollapsedPaths - sync.RWMutex -} - -func NewFileManager(files []*models.File, log *logrus.Entry, showTree bool) *FileManager { - return &FileManager{ - files: files, - log: log, - showTree: showTree, - filter: DisplayAll, - collapsedPaths: CollapsedPaths{}, - RWMutex: sync.RWMutex{}, - } -} - -func (m *FileManager) InTreeMode() bool { - return m.showTree -} - -func (m *FileManager) ExpandToPath(path string) { - m.collapsedPaths.ExpandToPath(path) -} - -func (m *FileManager) GetFilesForDisplay() []*models.File { - files := m.files - if m.filter == DisplayAll { - return files - } - - result := make([]*models.File, 0) - if m.filter == DisplayStaged { - for _, file := range files { - if file.HasStagedChanges { - result = append(result, file) - } - } - } else { - for _, file := range files { - if !file.HasStagedChanges { - result = append(result, file) - } - } - } - - return result -} - -func (m *FileManager) SetDisplayFilter(filter FileManagerDisplayFilter) { - m.filter = filter - m.SetTree() -} - -func (m *FileManager) ToggleShowTree() { - m.showTree = !m.showTree - m.SetTree() -} - -func (m *FileManager) GetItemAtIndex(index int) *FileNode { - // need to traverse the three depth first until we get to the index. - return m.tree.GetNodeAtIndex(index+1, m.collapsedPaths) // ignoring root -} - -func (m *FileManager) GetIndexForPath(path string) (int, bool) { - index, found := m.tree.GetIndexForPath(path, m.collapsedPaths) - return index - 1, found -} - -func (m *FileManager) GetAllItems() []*FileNode { - if m.tree == nil { - return nil - } - - return m.tree.Flatten(m.collapsedPaths)[1:] // ignoring root -} - -func (m *FileManager) GetItemsLength() int { - return m.tree.Size(m.collapsedPaths) - 1 // ignoring root -} - -func (m *FileManager) GetAllFiles() []*models.File { - return m.files -} - -func (m *FileManager) SetFiles(files []*models.File) { - m.files = files - - m.SetTree() -} - -func (m *FileManager) SetTree() { - filesForDisplay := m.GetFilesForDisplay() - if m.showTree { - m.tree = BuildTreeFromFiles(filesForDisplay) - } else { - m.tree = BuildFlatTreeFromFiles(filesForDisplay) - } -} - -func (m *FileManager) IsCollapsed(path string) bool { - return m.collapsedPaths.IsCollapsed(path) -} - -func (m *FileManager) ToggleCollapsed(path string) { - m.collapsedPaths.ToggleCollapsed(path) -} - -func (m *FileManager) Render(diffName string, submoduleConfigs []*models.SubmoduleConfig) []string { - // can't rely on renderAux to check for nil because an interface won't be nil if its concrete value is nil - if m.tree == nil { - return []string{} - } - - return renderAux(m.tree, m.collapsedPaths, "", -1, func(n INode, depth int) string { - castN := n.(*FileNode) - return presentation.GetFileLine(castN.GetHasUnstagedChanges(), castN.GetHasStagedChanges(), castN.NameAtDepth(depth), diffName, submoduleConfigs, castN.File) - }) -} diff --git a/pkg/gui/filetree/file_manager_test.go b/pkg/gui/filetree/file_manager_test.go deleted file mode 100644 index 83ba5b086..000000000 --- a/pkg/gui/filetree/file_manager_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package filetree - -import ( - "testing" - - "github.com/gookit/color" - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/stretchr/testify/assert" - "github.com/xo/terminfo" -) - -func init() { - color.ForceSetColorLevel(terminfo.ColorLevelNone) -} - -func TestRender(t *testing.T) { - scenarios := []struct { - name string - root *FileNode - collapsedPaths map[string]bool - expected []string - }{ - { - name: "nil node", - root: nil, - expected: []string{}, - }, - { - name: "leaf node", - root: &FileNode{ - Path: "", - Children: []*FileNode{ - {File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"}, - }, - }, - expected: []string{" M test"}, - }, - { - name: "big example", - root: &FileNode{ - Path: "", - Children: []*FileNode{ - { - Path: "dir1", - Children: []*FileNode{ - { - File: &models.File{Name: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true}, - Path: "dir1/file2", - }, - { - File: &models.File{Name: "dir1/file3", ShortStatus: "M ", HasUnstagedChanges: true}, - Path: "dir1/file3", - }, - }, - }, - { - Path: "dir2", - Children: []*FileNode{ - { - Path: "dir2/dir2", - Children: []*FileNode{ - { - File: &models.File{Name: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true}, - Path: "dir2/dir2/file3", - }, - { - File: &models.File{Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - Path: "dir2/dir2/file4", - }, - }, - }, - { - File: &models.File{Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - Path: "dir2/file5", - }, - }, - }, - { - File: &models.File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - Path: "file1", - }, - }, - }, - expected: []string{"dir1 ►", "dir2 ▼", "├─ dir2 ▼", "│ ├─ M file3", "│ └─ M file4", "└─ M file5", "M file1"}, - collapsedPaths: map[string]bool{"dir1": true}, - }, - } - - for _, s := range scenarios { - s := s - t.Run(s.name, func(t *testing.T) { - mngr := &FileManager{tree: s.root, collapsedPaths: s.collapsedPaths} - result := mngr.Render("", nil) - assert.EqualValues(t, s.expected, result) - }) - } -} - -func TestFilterAction(t *testing.T) { - scenarios := []struct { - name string - filter FileManagerDisplayFilter - files []*models.File - expected []*models.File - }{ - { - name: "filter files with unstaged changes", - filter: DisplayUnstaged, - files: []*models.File{ - {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Name: "dir2/file5", ShortStatus: "M ", HasStagedChanges: true}, - {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - expected: []*models.File{ - {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - }, - { - name: "filter files with staged changes", - filter: DisplayStaged, - files: []*models.File{ - {Name: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true}, - {Name: "dir2/file5", ShortStatus: "M ", HasStagedChanges: false}, - {Name: "file1", ShortStatus: "M ", HasStagedChanges: true}, - }, - expected: []*models.File{ - {Name: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true}, - {Name: "file1", ShortStatus: "M ", HasStagedChanges: true}, - }, - }, - { - name: "filter all files", - filter: DisplayAll, - files: []*models.File{ - {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - expected: []*models.File{ - {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, - {Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, - {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, - }, - }, - } - - for _, s := range scenarios { - s := s - t.Run(s.name, func(t *testing.T) { - mngr := &FileManager{files: s.files, filter: s.filter} - result := mngr.GetFilesForDisplay() - assert.EqualValues(t, s.expected, result) - }) - } -} diff --git a/pkg/gui/filetree/file_node.go b/pkg/gui/filetree/file_node.go index cb545d391..f332f0a76 100644 --- a/pkg/gui/filetree/file_node.go +++ b/pkg/gui/filetree/file_node.go @@ -11,6 +11,8 @@ type FileNode struct { CompressionLevel int // equal to the number of forward slashes you'll see in the path when it's rendered in tree mode } +var _ INode = &FileNode{} + // methods satisfying ListItem interface func (s *FileNode) ID() string { @@ -23,6 +25,12 @@ func (s *FileNode) Description() string { // methods satisfying INode interface +// interfaces values whose concrete value is nil are not themselves nil +// hence the existence of this method +func (s *FileNode) IsNil() bool { + return s == nil +} + func (s *FileNode) IsLeaf() bool { return s.File != nil } @@ -124,10 +132,13 @@ func (s *FileNode) Compress() { compressAux(s) } -// This ignores the root -func (node *FileNode) GetPathsMatching(test func(*FileNode) bool) []string { +func (node *FileNode) GetFilePathsMatching(test func(*models.File) bool) []string { return getPathsMatching(node, func(n INode) bool { - return test(n.(*FileNode)) + castNode := n.(*FileNode) + if castNode.File == nil { + return false + } + return test(castNode.File) }) } diff --git a/pkg/gui/filetree/file_node_test.go b/pkg/gui/filetree/file_node_test.go index 228b2036a..8961015ac 100644 --- a/pkg/gui/filetree/file_node_test.go +++ b/pkg/gui/filetree/file_node_test.go @@ -132,3 +132,32 @@ func TestCompress(t *testing.T) { }) } } + +func TestGetFile(t *testing.T) { + scenarios := []struct { + name string + viewModel *FileTreeViewModel + path string + expected *models.File + }{ + { + name: "valid case", + viewModel: NewFileTreeViewModel([]*models.File{{Name: "blah/one"}, {Name: "blah/two"}}, nil, false), + path: "blah/two", + expected: &models.File{Name: "blah/two"}, + }, + { + name: "not found", + viewModel: NewFileTreeViewModel([]*models.File{{Name: "blah/one"}, {Name: "blah/two"}}, nil, false), + path: "blah/three", + expected: nil, + }, + } + + for _, s := range scenarios { + s := s + 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_view_model.go b/pkg/gui/filetree/file_tree_view_model.go new file mode 100644 index 000000000..01eb751e3 --- /dev/null +++ b/pkg/gui/filetree/file_tree_view_model.go @@ -0,0 +1,159 @@ +package filetree + +import ( + "fmt" + "sync" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/sirupsen/logrus" +) + +type FileTreeDisplayFilter int + +const ( + DisplayAll FileTreeDisplayFilter = iota + DisplayStaged + DisplayUnstaged + // this shows files with merge conflicts + DisplayConflicted +) + +type FileTreeViewModel struct { + files []*models.File + tree *FileNode + showTree bool + log *logrus.Entry + filter FileTreeDisplayFilter + collapsedPaths CollapsedPaths + sync.RWMutex +} + +func NewFileTreeViewModel(files []*models.File, log *logrus.Entry, showTree bool) *FileTreeViewModel { + viewModel := &FileTreeViewModel{ + log: log, + showTree: showTree, + filter: DisplayAll, + collapsedPaths: CollapsedPaths{}, + RWMutex: sync.RWMutex{}, + } + + viewModel.SetFiles(files) + + return viewModel +} + +func (self *FileTreeViewModel) InTreeMode() bool { + return self.showTree +} + +func (self *FileTreeViewModel) ExpandToPath(path string) { + self.collapsedPaths.ExpandToPath(path) +} + +func (self *FileTreeViewModel) GetFilesForDisplay() []*models.File { + files := self.files + + switch self.filter { + case DisplayAll: + return files + 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 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 *FileTreeViewModel) FilterFiles(test func(*models.File) bool) []*models.File { + result := make([]*models.File, 0) + for _, file := range self.files { + if test(file) { + result = append(result, file) + } + } + return result +} + +func (self *FileTreeViewModel) SetFilter(filter FileTreeDisplayFilter) { + self.filter = filter + self.SetTree() +} + +func (self *FileTreeViewModel) ToggleShowTree() { + self.showTree = !self.showTree + self.SetTree() +} + +func (self *FileTreeViewModel) GetItemAtIndex(index int) *FileNode { + // need to traverse the three depth first until we get to the index. + return self.tree.GetNodeAtIndex(index+1, self.collapsedPaths) // ignoring root +} + +func (self *FileTreeViewModel) GetFile(path string) *models.File { + for _, file := range self.files { + if file.Name == path { + return file + } + } + + return nil +} + +func (self *FileTreeViewModel) GetIndexForPath(path string) (int, bool) { + index, found := self.tree.GetIndexForPath(path, self.collapsedPaths) + return index - 1, found +} + +func (self *FileTreeViewModel) GetAllItems() []*FileNode { + if self.tree == nil { + return nil + } + + return self.tree.Flatten(self.collapsedPaths)[1:] // ignoring root +} + +func (self *FileTreeViewModel) GetItemsLength() int { + return self.tree.Size(self.collapsedPaths) - 1 // ignoring root +} + +func (self *FileTreeViewModel) GetAllFiles() []*models.File { + return self.files +} + +func (self *FileTreeViewModel) SetFiles(files []*models.File) { + self.files = files + + self.SetTree() +} + +func (self *FileTreeViewModel) SetTree() { + filesForDisplay := self.GetFilesForDisplay() + if self.showTree { + self.tree = BuildTreeFromFiles(filesForDisplay) + } else { + self.tree = BuildFlatTreeFromFiles(filesForDisplay) + } +} + +func (self *FileTreeViewModel) IsCollapsed(path string) bool { + return self.collapsedPaths.IsCollapsed(path) +} + +func (self *FileTreeViewModel) ToggleCollapsed(path string) { + self.collapsedPaths.ToggleCollapsed(path) +} + +func (self *FileTreeViewModel) Tree() INode { + return self.tree +} + +func (self *FileTreeViewModel) CollapsedPaths() CollapsedPaths { + return self.collapsedPaths +} + +func (self *FileTreeViewModel) GetFilter() FileTreeDisplayFilter { + return self.filter +} diff --git a/pkg/gui/filetree/file_tree_view_model_test.go b/pkg/gui/filetree/file_tree_view_model_test.go new file mode 100644 index 000000000..89b8e74df --- /dev/null +++ b/pkg/gui/filetree/file_tree_view_model_test.go @@ -0,0 +1,81 @@ +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{ + {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "dir2/file5", ShortStatus: "M ", HasStagedChanges: true}, + {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, + }, + expected: []*models.File{ + {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, + }, + }, + { + name: "filter files with staged changes", + filter: DisplayStaged, + files: []*models.File{ + {Name: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true}, + {Name: "dir2/file5", ShortStatus: "M ", HasStagedChanges: false}, + {Name: "file1", ShortStatus: "M ", HasStagedChanges: true}, + }, + expected: []*models.File{ + {Name: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true}, + {Name: "file1", ShortStatus: "M ", HasStagedChanges: true}, + }, + }, + { + name: "filter all files", + filter: DisplayAll, + files: []*models.File{ + {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, + }, + expected: []*models.File{ + {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, + }, + }, + { + name: "filter conflicted files", + filter: DisplayConflicted, + files: []*models.File{ + {Name: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true}, + {Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "dir2/file6", ShortStatus: " M", HasStagedChanges: true}, + {Name: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true}, + }, + expected: []*models.File{ + {Name: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true}, + {Name: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true}, + }, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.name, func(t *testing.T) { + mngr := &FileTreeViewModel{files: s.files, filter: s.filter} + result := mngr.GetFilesForDisplay() + assert.EqualValues(t, s.expected, result) + }) + } +} diff --git a/pkg/gui/filetree/inode.go b/pkg/gui/filetree/inode.go index f7bfc0518..7d9035fe3 100644 --- a/pkg/gui/filetree/inode.go +++ b/pkg/gui/filetree/inode.go @@ -1,12 +1,11 @@ package filetree import ( - "fmt" "sort" - "strings" ) type INode interface { + IsNil() bool IsLeaf() bool GetPath() string GetChildren() []INode @@ -212,51 +211,3 @@ func getLeaves(node INode) []INode { return output } - -func renderAux(s INode, collapsedPaths CollapsedPaths, prefix string, depth int, renderLine func(INode, int) string) []string { - isRoot := depth == -1 - - renderLineWithPrefix := func() string { - return prefix + renderLine(s, depth) - } - - if s.IsLeaf() { - if isRoot { - return []string{} - } - return []string{renderLineWithPrefix()} - } - - if collapsedPaths.IsCollapsed(s.GetPath()) { - return []string{fmt.Sprintf("%s %s", renderLineWithPrefix(), COLLAPSED_ARROW)} - } - - arr := []string{} - if !isRoot { - arr = append(arr, fmt.Sprintf("%s %s", renderLineWithPrefix(), EXPANDED_ARROW)) - } - - newPrefix := prefix - if strings.HasSuffix(prefix, LAST_ITEM) { - newPrefix = strings.TrimSuffix(prefix, LAST_ITEM) + NOTHING - } else if strings.HasSuffix(prefix, INNER_ITEM) { - newPrefix = strings.TrimSuffix(prefix, INNER_ITEM) + NESTED - } - - for i, child := range s.GetChildren() { - isLast := i == len(s.GetChildren())-1 - - var childPrefix string - if isRoot { - childPrefix = newPrefix - } else if isLast { - childPrefix = newPrefix + LAST_ITEM - } else { - childPrefix = newPrefix + INNER_ITEM - } - - arr = append(arr, renderAux(child, collapsedPaths, childPrefix, depth+1+s.GetCompressionLevel(), renderLine)...) - } - - return arr -} diff --git a/pkg/gui/git_flow.go b/pkg/gui/git_flow.go index 31c64385b..e89c7637c 100644 --- a/pkg/gui/git_flow.go +++ b/pkg/gui/git_flow.go @@ -2,49 +2,17 @@ package gui import ( "fmt" - "regexp" - "strings" "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) error { - // 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(gitFlowConfig), "\n") { - if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) { - // now I just need to how do you say - 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 gui.createErrorPanel(gui.Tr.NotAGitFlowBranch) - } - - return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.GitFlowFinish).PrepareSubProcess("git", "flow", branchType, "finish", suffix), - ) -} - func (gui *Gui) handleCreateGitFlowMenu() error { branch := gui.getSelectedBranch() if branch == nil { return nil } - // get config - gitFlowConfig, err := gui.GitCommand.RunCommandWithOutput("git config --local --get-regexp gitflow") - if err != nil { + if !gui.Git.Flow.GitFlowEnabled() { return gui.createErrorPanel("You need to install git-flow and enable it in this repo to use git-flow features") } @@ -55,8 +23,9 @@ func (gui *Gui) handleCreateGitFlowMenu() error { return gui.prompt(promptOpts{ title: title, handleConfirm: func(name string) error { + gui.logAction(gui.Tr.Actions.GitFlowStart) return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.GitFlowStart).PrepareSubProcess("git", "flow", branchType, "start", name), + gui.Git.Flow.StartCmdObj(branchType, name), ) }, }) @@ -68,7 +37,7 @@ func (gui *Gui) handleCreateGitFlowMenu() error { // not localising here because it's one to one with the actual git flow commands displayString: fmt.Sprintf("finish branch '%s'", branch.Name), onPress: func() error { - return gui.gitFlowFinishBranch(gitFlowConfig, branch.Name) + return gui.gitFlowFinishBranch(branch.Name) }, }, { @@ -91,3 +60,13 @@ func (gui *Gui) handleCreateGitFlowMenu() error { return gui.createMenu("git flow", menuItems, createMenuOptions{}) } + +func (gui *Gui) gitFlowFinishBranch(branchName string) error { + cmdObj, err := gui.Git.Flow.FinishCmdObj(branchName) + if err != nil { + return gui.surfaceError(err) + } + + gui.logAction(gui.Tr.Actions.GitFlowFinish) + return gui.runSubprocessWithSuspenseAndRefresh(cmdObj) +} diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go index c104c1664..22b43b6b7 100644 --- a/pkg/gui/global_handlers.go +++ b/pkg/gui/global_handlers.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -64,7 +64,7 @@ func (gui *Gui) prevScreenMode() error { func (gui *Gui) scrollUpView(view *gocui.View) error { ox, oy := view.Origin() - newOy := int(math.Max(0, float64(oy-gui.Config.GetUserConfig().Gui.ScrollHeight))) + newOy := int(math.Max(0, float64(oy-gui.UserConfig.Gui.ScrollHeight))) return view.SetOrigin(ox, newOy) } @@ -86,12 +86,12 @@ func (gui *Gui) scrollDownView(view *gocui.View) error { func (gui *Gui) linesToScrollDown(view *gocui.View) int { _, oy := view.Origin() y := oy - canScrollPastBottom := gui.Config.GetUserConfig().Gui.ScrollPastBottom + canScrollPastBottom := gui.UserConfig.Gui.ScrollPastBottom if !canScrollPastBottom { _, sy := view.Size() y += sy } - scrollHeight := gui.Config.GetUserConfig().Gui.ScrollHeight + scrollHeight := gui.UserConfig.Gui.ScrollHeight scrollableLines := view.ViewLinesHeight() - y if scrollableLines < 0 { return 0 @@ -115,7 +115,7 @@ func (gui *Gui) linesToScrollDown(view *gocui.View) int { } func (gui *Gui) scrollUpMain() error { - if gui.canScrollMergePanel() { + if gui.renderingConflicts() { gui.State.Panels.Merging.UserVerticalScrolling = true } @@ -123,7 +123,7 @@ func (gui *Gui) scrollUpMain() error { } func (gui *Gui) scrollDownMain() error { - if gui.canScrollMergePanel() { + if gui.renderingConflicts() { gui.State.Panels.Merging.UserVerticalScrolling = true } @@ -189,9 +189,9 @@ func (gui *Gui) handleMouseDownMain() error { // set filename, set primary/secondary selected, set line number, then switch context // I'll need to know it was changed though. // Could I pass something along to the context change? - return gui.enterFile(false, gui.Views.Main.SelectedLineIdx()) + return gui.enterFile(OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()}) case gui.State.Contexts.CommitFiles: - return gui.enterCommitFile(gui.Views.Main.SelectedLineIdx()) + return gui.enterCommitFile(OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()}) } return nil @@ -204,24 +204,20 @@ func (gui *Gui) handleMouseDownSecondary() error { switch gui.g.CurrentView() { case gui.Views.Files: - return gui.enterFile(true, gui.Views.Secondary.SelectedLineIdx()) + return gui.enterFile(OnFocusOpts{ClickedViewName: "secondary", ClickedViewLineIdx: gui.Views.Secondary.SelectedLineIdx()}) } return nil } -func (gui *Gui) fetch(canPromptForCredentials bool, span string) (err error) { +func (gui *Gui) fetch() (err error) { gui.Mutexes.FetchMutex.Lock() defer gui.Mutexes.FetchMutex.Unlock() - fetchOpts := commands.FetchOptions{} - if canPromptForCredentials { - fetchOpts.PromptUserForCredential = gui.promptUserForCredential - } + gui.logAction("Fetch") + err = gui.Git.Sync.Fetch(git_commands.FetchOptions{}) - err = gui.GitCommand.WithSpan(span).Fetch(fetchOpts) - - if canPromptForCredentials && err != nil && strings.Contains(err.Error(), "exit status 128") { + if err != nil && strings.Contains(err.Error(), "exit status 128") { _ = gui.createErrorPanel(gui.Tr.PassUnameWrong) } @@ -230,6 +226,17 @@ func (gui *Gui) fetch(canPromptForCredentials bool, span string) (err error) { return err } +func (gui *Gui) backgroundFetch() (err error) { + gui.Mutexes.FetchMutex.Lock() + defer gui.Mutexes.FetchMutex.Unlock() + + err = gui.Git.Sync.Fetch(git_commands.FetchOptions{Background: true}) + + _ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, COMMITS, REMOTES, TAGS}, mode: ASYNC}) + + return err +} + func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error { // important to note that this assumes we've selected an item in a side context itemId := gui.getSideContextSelectedItemId() @@ -238,7 +245,8 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error { return nil } - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.CopyToClipboard).CopyToClipboard(itemId); err != nil { + gui.logAction(gui.Tr.Actions.CopyToClipboard) + if err := gui.OSCommand.CopyToClipboard(itemId); err != nil { return gui.surfaceError(err) } diff --git a/pkg/gui/gpg.go b/pkg/gui/gpg.go index 1811e4fb5..ca7e4b842 100644 --- a/pkg/gui/gpg.go +++ b/pkg/gui/gpg.go @@ -3,6 +3,7 @@ package gui import ( "fmt" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/gui/style" ) @@ -10,12 +11,13 @@ import ( // 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 (gui *Gui) withGpgHandling(cmdStr string, waitingStatus string, onSuccess func() error) error { - useSubprocess := gui.GitCommand.UsingGpg() +// TODO: work out if we actually need to use a shell command here +func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { + gui.logCommand(cmdObj.ToString(), true) + + useSubprocess := gui.Git.Config.UsingGpg() if useSubprocess { - // Need to remember why we use the shell for the subprocess but not in the other case - // Maybe there's no good reason - success, err := gui.runSubprocessWithSuspense(gui.OSCommand.ShellCommandFromString(cmdStr)) + success, err := gui.runSubprocessWithSuspense(gui.OSCommand.Cmd.NewShell(cmdObj.ToString())) if success && onSuccess != nil { if err := onSuccess(); err != nil { return err @@ -27,15 +29,16 @@ func (gui *Gui) withGpgHandling(cmdStr string, waitingStatus string, onSuccess f return err } else { - return gui.RunAndStream(cmdStr, waitingStatus, onSuccess) + return gui.RunAndStream(cmdObj, waitingStatus, onSuccess) } } -func (gui *Gui) RunAndStream(cmdStr string, waitingStatus string, onSuccess func() error) error { +func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { return gui.WithWaitingStatus(waitingStatus, func() error { - cmd := gui.OSCommand.ShellCommandFromString(cmdStr) - cmd.Env = append(cmd.Env, "TERM=dumb") + cmdObj := gui.OSCommand.Cmd.NewShell(cmdObj.ToString()) + cmdObj.AddEnvVars("TERM=dumb") cmdWriter := gui.getCmdWriter() + cmd := cmdObj.GetCmd() cmd.Stdout = cmdWriter cmd.Stderr = cmdWriter @@ -46,7 +49,7 @@ func (gui *Gui) RunAndStream(cmdStr string, waitingStatus string, onSuccess func _ = gui.refreshSidePanels(refreshOptions{mode: ASYNC}) return gui.surfaceError( fmt.Errorf( - gui.Tr.GitCommandFailed, gui.Config.GetUserConfig().Keybinding.Universal.ExtrasMenu, + gui.Tr.GitCommandFailed, gui.UserConfig.Keybinding.Universal.ExtrasMenu, ), ) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 726a159c8..ed41537c2 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -7,14 +7,16 @@ import ( "os" "sync" - "os/exec" "strings" "time" "github.com/jesseduffield/gocui" "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/filetree" "github.com/jesseduffield/lazygit/pkg/gui/lbl" @@ -22,16 +24,15 @@ 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/presentation" "github.com/jesseduffield/lazygit/pkg/gui/presentation/authors" "github.com/jesseduffield/lazygit/pkg/gui/presentation/graph" "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/tasks" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/updates" "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sirupsen/logrus" "gopkg.in/ozeidan/fuzzy-patricia.v3/patricia" ) @@ -68,10 +69,10 @@ type Repo string // Gui wraps the gocui Gui object which handles rendering and events type Gui struct { - g *gocui.Gui - Log *logrus.Entry - GitCommand *commands.GitCommand - OSCommand *oscommands.OSCommand + *common.Common + g *gocui.Gui + Git *commands.GitCommand + OSCommand *oscommands.OSCommand // this is the state of the GUI for the current repo State *guiState @@ -80,7 +81,6 @@ type Gui struct { // gui state when returning from a subrepo RepoStateMap map[Repo]*guiState Config config.AppConfigurer - Tr *i18n.TranslationSet Updater *updates.Updater statusManager *statusManager credentials credentials @@ -114,13 +114,16 @@ type Gui struct { PauseBackgroundThreads bool // Log of the commands that get run, to be displayed to the user. - CmdLog []string - OnRunCommand func(entry oscommands.CmdLogEntry) + CmdLog []string // the extras window contains things like the command log ShowExtrasWindow bool suggestionsAsyncHandler *tasks.AsyncHandler + + PopupHandler PopupHandler + + IsNewRepo bool } type listPanelState struct { @@ -288,13 +291,13 @@ type guiMutexes struct { type guiState struct { // the file panels (files and commit files) can render as a tree, so we have // managers for them which handle rendering a flat list of files in tree form - FileManager *filetree.FileManager - CommitFileManager *filetree.CommitFileManager - Submodules []*models.SubmoduleConfig - Branches []*models.Branch - GithubState *GithubState - Commits []*models.Commit - StashEntries []*models.StashEntry + FileTreeViewModel *filetree.FileTreeViewModel + CommitFileTreeViewModel *filetree.CommitFileTreeViewModel + + Submodules []*models.SubmoduleConfig + Branches []*models.Branch + Commits []*models.Commit + StashEntries []*models.StashEntry // Suggestions will sometimes appear when typing into a prompt Suggestions []*types.Suggestion // FilteredReflogCommits are the ones that appear in the reflog panel. @@ -303,12 +306,14 @@ type guiState struct { // ReflogCommits are the ones used by the branches panel to obtain recency values // if we're not in filtering mode, CommitFiles and FilteredReflogCommits will be // one and the same - ReflogCommits []*models.Commit - SubCommits []*models.Commit - Remotes []*models.Remote - RemoteBranches []*models.RemoteBranch - Tags []*models.Tag - MenuItems []*menuItem + ReflogCommits []*models.Commit + SubCommits []*models.Commit + Remotes []*models.Remote + RemoteBranches []*models.RemoteBranch + Tags []*models.Tag + MenuItems []*menuItem + BisectInfo *git_commands.BisectInfo + Updating bool Panels *panelStates SplitMainPanel bool @@ -347,6 +352,9 @@ type guiState struct { // for displaying suggestions while typing in a file name FilesTrie *patricia.Trie + + // this is the message of the last failed commit attempt + failedCommitMessage string } type GithubState struct { @@ -377,7 +385,7 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) { } } - showTree := gui.Config.GetUserConfig().Gui.ShowFileTree + showTree := gui.UserConfig.Gui.ShowFileTree contexts := gui.contextTree() @@ -389,12 +397,13 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) { } gui.State = &guiState{ - FileManager: filetree.NewFileManager(make([]*models.File, 0), gui.Log, showTree), - CommitFileManager: filetree.NewCommitFileManager(make([]*models.CommitFile, 0), gui.Log, showTree), - Commits: make([]*models.Commit, 0), - FilteredReflogCommits: make([]*models.Commit, 0), - ReflogCommits: make([]*models.Commit, 0), - StashEntries: make([]*models.StashEntry, 0), + FileTreeViewModel: filetree.NewFileTreeViewModel(make([]*models.File, 0), gui.Log, showTree), + CommitFileTreeViewModel: filetree.NewCommitFileTreeViewModel(make([]*models.CommitFile, 0), gui.Log, showTree), + Commits: make([]*models.Commit, 0), + FilteredReflogCommits: make([]*models.Commit, 0), + ReflogCommits: make([]*models.Commit, 0), + StashEntries: make([]*models.StashEntry, 0), + BisectInfo: gui.Git.Bisect.GetInfo(), Panels: &panelStates{ // TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now Files: &filePanelState{listPanelState{SelectedLineIdx: -1}}, @@ -436,13 +445,17 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) { // for now the split view will always be on // NewGui builds a new gui handler -func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *oscommands.OSCommand, tr *i18n.TranslationSet, config config.AppConfigurer, updater *updates.Updater, filterPath string, showRecentRepos bool) (*Gui, error) { +func NewGui( + cmn *common.Common, + config config.AppConfigurer, + gitConfig git_config.IGitConfig, + updater *updates.Updater, + filterPath string, + showRecentRepos bool, +) (*Gui, error) { gui := &Gui{ - Log: log, - GitCommand: gitCommand, - OSCommand: oSCommand, + Common: cmn, Config: config, - Tr: tr, Updater: updater, statusManager: &statusManager{}, viewBufferManagerMap: map[string]*tasks.ViewBufferManager{}, @@ -450,19 +463,42 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *oscom RepoPathStack: []string{}, RepoStateMap: map[Repo]*guiState{}, CmdLog: []string{}, - ShowExtrasWindow: config.ShowCommandLogOnStartup(), suggestionsAsyncHandler: tasks.NewAsyncHandler(), + + // 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 + ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog, + } + + guiIO := oscommands.NewGuiIO( + cmn.Log, + gui.logCommand, + gui.getCmdWriter, + gui.promptUserForCredential, + ) + + osCommand := oscommands.NewOSCommand(cmn, oscommands.GetPlatform(), guiIO) + + gui.OSCommand = osCommand + var err error + gui.Git, err = commands.NewGitCommand( + cmn, + osCommand, + gitConfig, + ) + if err != nil { + return nil, err } gui.resetState(filterPath, false) gui.watchFilesForChanges() - onRunCommand := gui.GetOnRunCommand() - oSCommand.SetOnRunCommand(onRunCommand) - gui.OnRunCommand = onRunCommand + gui.PopupHandler = &RealPopupHandler{gui: gui} - authors.SetCustomAuthors(gui.Config.GetUserConfig().Gui.AuthorColors) + authors.SetCustomAuthors(gui.UserConfig.Gui.AuthorColors) + presentation.SetCustomBranches(gui.UserConfig.Gui.BranchColors) return gui, nil } @@ -512,7 +548,7 @@ func (gui *Gui) Run() error { if err := gui.Config.ReloadUserConfig(); err != nil { return nil } - userConfig := gui.Config.GetUserConfig() + userConfig := gui.UserConfig g.SearchEscapeKey = gui.getKey(userConfig.Keybinding.Universal.Return) g.NextSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.NextMatch) g.PrevSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.PrevMatch) @@ -528,7 +564,7 @@ func (gui *Gui) Run() error { } gui.waitForIntro.Add(1) - if gui.Config.GetUserConfig().Git.AutoFetch { + if gui.UserConfig.Git.AutoFetch { go utils.Safe(gui.startBackgroundFetch) } @@ -581,7 +617,7 @@ func (gui *Gui) RunAndHandleError() error { } // returns whether command exited without error or not -func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess *exec.Cmd) error { +func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess oscommands.ICmdObj) error { _, err := gui.runSubprocessWithSuspense(subprocess) if err != nil { return err @@ -595,7 +631,7 @@ func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess *exec.Cmd) error } // returns whether command exited without error or not -func (gui *Gui) runSubprocessWithSuspense(subprocess *exec.Cmd) (bool, error) { +func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool, error) { gui.Mutexes.SubprocessMutex.Lock() defer gui.Mutexes.SubprocessMutex.Unlock() @@ -621,30 +657,33 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess *exec.Cmd) (bool, error) { gui.PauseBackgroundThreads = false - return cmdErr == nil, gui.surfaceError(cmdErr) + if cmdErr != nil { + return false, gui.surfaceError(cmdErr) + } + + return true, nil } -func (gui *Gui) runSubprocess(subprocess *exec.Cmd) error { +func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unparam + gui.logCommand(cmdObj.ToString(), true) + + subprocess := cmdObj.GetCmd() subprocess.Stdout = os.Stdout subprocess.Stderr = os.Stdout subprocess.Stdin = os.Stdin fmt.Fprintf(os.Stdout, "\n%s\n\n", style.FgBlue.Sprint("+ "+strings.Join(subprocess.Args, " "))) - if err := subprocess.Run(); err != nil { - // not handling the error explicitly because usually we're going to see it - // in the output anyway - gui.Log.Error(err) - } + err := subprocess.Run() subprocess.Stdout = ioutil.Discard subprocess.Stderr = ioutil.Discard subprocess.Stdin = nil - fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint(gui.Tr.PressEnterToReturn)) + fmt.Fprintf(os.Stdout, "\n%s\n", style.FgGreen.Sprint(gui.Tr.PressEnterToReturn)) fmt.Scanln() // wait for enter press - return nil + return err } func (gui *Gui) loadNewRepo() error { @@ -656,6 +695,10 @@ func (gui *Gui) loadNewRepo() error { return err } + if err := gui.OSCommand.UpdateWindowTitle(); err != nil { + return err + } + return nil } @@ -713,12 +756,12 @@ func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function fun func (gui *Gui) startBackgroundFetch() { gui.waitForIntro.Wait() - isNew := gui.Config.GetIsNewRepo() - userConfig := gui.Config.GetUserConfig() + isNew := gui.IsNewRepo + userConfig := gui.UserConfig if !isNew { time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second) } - err := gui.fetch(false, "") + err := gui.backgroundFetch() if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew { _ = gui.ask(askOpts{ title: gui.Tr.NoAutomaticGitFetchTitle, @@ -726,7 +769,7 @@ func (gui *Gui) startBackgroundFetch() { }) } else { gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error { - err := gui.fetch(false, "") + err := gui.backgroundFetch() gui.render() return err }) @@ -735,7 +778,7 @@ func (gui *Gui) startBackgroundFetch() { // setColorScheme sets the color scheme for the app based on the user config func (gui *Gui) setColorScheme() error { - userConfig := gui.Config.GetUserConfig() + userConfig := gui.UserConfig theme.UpdateTheme(userConfig.Gui.Theme) gui.g.FgColor = theme.InactiveBorderColor @@ -746,17 +789,23 @@ func (gui *Gui) setColorScheme() error { return nil } -func (gui *Gui) GetPr(branch *models.Branch) (*models.GithubPullRequest, bool, error) { - prs, err := gui.GitCommand.GenerateGithubPullRequestMap( - gui.State.GithubState.RecentPRs, - []*models.Branch{branch}, - gui.State.Remotes, - ) - if err != nil { - return nil, false, err - } +// func (gui *Gui) GetPr(branch *models.Branch) (*models.GithubPullRequest, bool, error) { +// prs, err := gui.GitCommand.GenerateGithubPullRequestMap( +// gui.State.GithubState.RecentPRs, +// []*models.Branch{branch}, +// gui.State.Remotes, +// ) +// if err != nil { +// return nil, false, err +// } - pr, hasPr := prs[branch] +// pr, hasPr := prs[branch] - return pr, hasPr, nil +// return pr, hasPr, nil +// } + +func (gui *Gui) OnUIThread(f func() error) { + gui.g.Update(func(*gocui.Gui) error { + return f() + }) } diff --git a/pkg/gui/gui_test.go b/pkg/gui/gui_test.go index 6e7a26b79..e35ab1896 100644 --- a/pkg/gui/gui_test.go +++ b/pkg/gui/gui_test.go @@ -39,8 +39,7 @@ import ( // original playback speed. Speed may be a decimal. func Test(t *testing.T) { - record := false - updateSnapshots := os.Getenv("UPDATE_SNAPSHOTS") != "" + mode := integration.GetModeFromEnv() speedEnv := os.Getenv("SPEED") includeSkipped := os.Getenv("INCLUDE_SKIPPED") != "" @@ -53,8 +52,7 @@ func Test(t *testing.T) { assert.NoError(t, err) }) }, - updateSnapshots, - record, + mode, speedEnv, func(t *testing.T, expected string, actual string, prefix string) { assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index d4776e50e..93d956061 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -204,7 +204,7 @@ func (gui *Gui) getKey(key string) interface{} { // GetInitialKeybindings is a function. func (gui *Gui) GetInitialKeybindings() []*Binding { - config := gui.Config.GetUserConfig().Keybinding + config := gui.UserConfig.Keybinding bindings := []*Binding{ { @@ -743,14 +743,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "commits", Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)}, Key: gui.getKey(config.Commits.RenameCommit), - Handler: gui.handleRenameCommit, - Description: gui.Tr.LcRenameCommit, + Handler: gui.handleRewordCommit, + Description: gui.Tr.LcRewordCommit, }, { ViewName: "commits", Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)}, Key: gui.getKey(config.Commits.RenameCommitWithEditor), - Handler: gui.handleRenameCommitEditor, + Handler: gui.handleRewordCommitEditor, Description: gui.Tr.LcRenameCommitEditor, }, { @@ -901,6 +901,21 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleCopySelectedCommitMessageToClipboard, Description: gui.Tr.LcCopyCommitMessageToClipboard, }, + { + ViewName: "commits", + Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)}, + Key: gui.getKey(config.Commits.OpenInBrowser), + Handler: gui.handleOpenCommitInBrowser, + Description: gui.Tr.LcOpenCommitInBrowser, + }, + { + ViewName: "commits", + Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)}, + Key: gui.getKey(config.Commits.ViewBisectOptions), + Handler: gui.handleOpenBisectMenu, + Description: gui.Tr.LcViewBisectOptions, + OpensMenu: true, + }, { ViewName: "commits", Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)}, @@ -1525,20 +1540,6 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleSelectNextConflictHunk, Description: gui.Tr.SelectNextHunk, }, - { - ViewName: "main", - Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, - Key: gocui.MouseWheelUp, - Modifier: gocui.ModNone, - Handler: gui.handleSelectPrevConflictHunk, - }, - { - ViewName: "main", - Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, - Key: gocui.MouseWheelDown, - Modifier: gocui.ModNone, - Handler: gui.handleSelectNextConflictHunk, - }, { ViewName: "main", Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, @@ -1571,7 +1572,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "main", Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, Key: gui.getKey(config.Universal.Undo), - Handler: gui.handlePopFileSnapshot, + Handler: gui.handleMergeConflictUndo, Description: gui.Tr.LcUndo, }, { @@ -1723,8 +1724,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "files", Contexts: []string{string(SUBMODULES_CONTEXT_KEY)}, Key: gui.getKey(config.Universal.Remove), - Handler: gui.forSubmodule(gui.handleResetRemoveSubmodule), - Description: gui.Tr.LcViewResetAndRemoveOptions, + Handler: gui.forSubmodule(gui.removeSubmodule), + Description: gui.Tr.LcRemoveSubmodule, OpensMenu: true, }, { @@ -1770,6 +1771,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.toggleWhitespaceInDiffView, Description: gui.Tr.ToggleWhitespaceInDiffView, }, + { + ViewName: "", + Key: gui.getKey(config.Universal.IncreaseContextInDiffView), + Handler: gui.IncreaseContextInDiffView, + Description: gui.Tr.IncreaseContextInDiffView, + }, + { + ViewName: "", + Key: gui.getKey(config.Universal.DecreaseContextInDiffView), + Handler: gui.DecreaseContextInDiffView, + Description: gui.Tr.DecreaseContextInDiffView, + }, { ViewName: "extras", Key: gocui.MouseWheelUp, diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 59c6a761e..c5872654a 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -48,7 +48,7 @@ func (gui *Gui) createAllViews() error { gui.Views.SearchPrefix.BgColor = gocui.ColorDefault gui.Views.SearchPrefix.FgColor = gocui.ColorGreen gui.Views.SearchPrefix.Frame = false - gui.setViewContentSync(gui.Views.SearchPrefix, SEARCH_PREFIX) + gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX) gui.Views.Stash.Title = gui.Tr.StashTitle gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor @@ -124,8 +124,6 @@ func (gui *Gui) createAllViews() error { return err } - gui.GitCommand.GetCmdWriter = gui.getCmdWriter - return nil } @@ -237,7 +235,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { gui.Views.CommitFiles.Visible = gui.getViewNameForWindow(gui.State.Contexts.CommitFiles.GetWindowName()) == "commitFiles" if gui.State.OldInformation != informationStr { - gui.setViewContentSync(gui.Views.Information, informationStr) + gui.setViewContent(gui.Views.Information, informationStr) gui.State.OldInformation = informationStr } @@ -374,7 +372,7 @@ func (gui *Gui) onInitialViewsCreation() error { return err } - if !gui.Config.GetUserConfig().DisableStartupPopups { + if !gui.UserConfig.DisableStartupPopups { popupTasks := []func(chan struct{}) error{} storedPopupVersion := gui.Config.GetAppState().StartupPopupVersion if storedPopupVersion < StartupPopupVersion { diff --git a/pkg/gui/lbl/focus.go b/pkg/gui/lbl/focus.go index ea70fbb3d..780551e5d 100644 --- a/pkg/gui/lbl/focus.go +++ b/pkg/gui/lbl/focus.go @@ -47,6 +47,7 @@ func getNeedAndWantLineIdx(firstLineIdx int, lastLineIdx int, selectedLineIdx in case HUNK: return firstLineIdx, lastLineIdx default: + // we should never land here panic("unknown mode") } } diff --git a/pkg/gui/line_by_line_panel.go b/pkg/gui/line_by_line_panel.go index 809416451..0576d3c0f 100644 --- a/pkg/gui/line_by_line_panel.go +++ b/pkg/gui/line_by_line_panel.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/go-errors/errors" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/gui/lbl" ) @@ -90,9 +89,8 @@ func (gui *Gui) copySelectedToClipboard() error { return gui.withLBLActiveCheck(func(state *LblPanelState) error { selected := state.PlainRenderSelected() - if err := gui.OSCommand.WithSpan( - gui.Tr.Spans.CopySelectedTextToClipboard, - ).CopyToClipboard(selected); err != nil { + gui.logAction(gui.Tr.Actions.CopySelectedTextToClipboard) + if err := gui.OSCommand.CopyToClipboard(selected); err != nil { return gui.surfaceError(err) } @@ -135,7 +133,7 @@ func (gui *Gui) handleMouseDrag() error { func (gui *Gui) getSelectedCommitFileName() string { idx := gui.State.Panels.CommitFiles.SelectedLineIdx - return gui.State.CommitFileManager.GetItemAtIndex(idx).GetPath() + return gui.State.CommitFileTreeViewModel.GetItemAtIndex(idx).GetPath() } func (gui *Gui) refreshMainViewForLineByLine(state *LblPanelState) error { @@ -145,7 +143,7 @@ func (gui *Gui) refreshMainViewForLineByLine(state *LblPanelState) error { if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() { filename := gui.getSelectedCommitFileName() var err error - includedLineIndices, err = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename) + includedLineIndices, err = gui.Git.Patch.PatchManager.GetFileIncLineIndices(filename) if err != nil { return err } @@ -173,15 +171,11 @@ func (gui *Gui) focusSelection(state *LblPanelState) error { newOrigin := state.CalculateOrigin(origin, bufferHeight) - gui.g.Update(func(*gocui.Gui) error { - if err := stagingView.SetOriginY(newOrigin); err != nil { - return err - } + if err := stagingView.SetOriginY(newOrigin); err != nil { + return err + } - return stagingView.SetCursor(0, selectedLineIdx-newOrigin) - }) - - return nil + return stagingView.SetCursor(0, selectedLineIdx-newOrigin) } func (gui *Gui) handleToggleSelectRange() error { diff --git a/pkg/gui/list_context.go b/pkg/gui/list_context.go index a7c47e480..9f0d86372 100644 --- a/pkg/gui/list_context.go +++ b/pkg/gui/list_context.go @@ -9,7 +9,8 @@ import ( type ListContext struct { GetItemsLength func() int GetDisplayStrings func(startIdx int, length int) [][]string - OnFocus func() error + OnFocus func(...OnFocusOpts) error + OnRenderToMain func(...OnFocusOpts) error OnFocusLost func() error OnClickSelectedItem func() error @@ -29,7 +30,6 @@ type ListContext struct { type IListContext interface { GetSelectedItem() (ListItem, bool) GetSelectedItemId() string - OnRender() error handlePrevLine() error handleNextLine() error handleScrollLeft() error @@ -42,6 +42,7 @@ type IListContext interface { handleClick() error onSearchSelect(selectedLineIdx int) error FocusLine() + HandleRenderToMain() error GetPanelState() IListPanelState @@ -76,7 +77,7 @@ func (self *ListContext) FocusLine() { view.FocusPoint(view.OriginX(), self.GetPanelState().GetSelectedLineIdx()) if self.RenderSelection { _, originY := view.Origin() - displayStrings := self.GetDisplayStrings(originY, view.InnerHeight()) + displayStrings := self.GetDisplayStrings(originY, view.InnerHeight()+1) self.Gui.renderDisplayStringsAtPos(view, originY, displayStrings) } view.Footer = formatListFooter(self.GetPanelState().GetSelectedLineIdx(), self.GetItemsLength()) @@ -101,7 +102,7 @@ func (self *ListContext) GetSelectedItemId() string { } // 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 *ListContext) OnRender() error { +func (self *ListContext) HandleRender() error { view, err := self.Gui.g.View(self.ViewName) if err != nil { return nil @@ -131,7 +132,7 @@ func (self *ListContext) HandleFocusLost() error { return nil } -func (self *ListContext) HandleFocus() error { +func (self *ListContext) HandleFocus(opts ...OnFocusOpts) error { if self.Gui.popupPanelFocused() { return nil } @@ -143,16 +144,20 @@ func (self *ListContext) HandleFocus() error { } if self.OnFocus != nil { - return self.OnFocus() + if err := self.OnFocus(opts...); err != nil { + return err + } + } + + if self.OnRenderToMain != nil { + if err := self.OnRenderToMain(opts...); err != nil { + return err + } } return nil } -func (self *ListContext) HandleRender() error { - return self.OnRender() -} - func (self *ListContext) handlePrevLine() error { return self.handleLineChange(-1) } @@ -268,3 +273,11 @@ func (self *ListContext) onSearchSelect(selectedLineIdx int) error { self.GetPanelState().SetSelectedLineIdx(selectedLineIdx) return self.HandleFocus() } + +func (self *ListContext) HandleRenderToMain() error { + if self.OnRenderToMain != nil { + return self.OnRenderToMain() + } + + return nil +} diff --git a/pkg/gui/list_context_config.go b/pkg/gui/list_context_config.go index 818ae45df..7d9fb7947 100644 --- a/pkg/gui/list_context_config.go +++ b/pkg/gui/list_context_config.go @@ -4,6 +4,7 @@ import ( "log" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/style" ) @@ -18,7 +19,6 @@ func (gui *Gui) menuListContext() IListContext { }, GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Menu }, - OnFocus: gui.handleMenuSelect, OnClickSelectedItem: gui.onMenuPress, Gui: gui, @@ -34,13 +34,14 @@ func (gui *Gui) filesListContext() IListContext { Key: FILES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return gui.State.FileManager.GetItemsLength() }, + GetItemsLength: func() int { return gui.State.FileTreeViewModel.GetItemsLength() }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Files }, - OnFocus: gui.focusAndSelectFile, + OnFocus: OnFocusWrapper(gui.onFocusFile), + OnRenderToMain: OnFocusWrapper(gui.filesRenderToMain), OnClickSelectedItem: gui.handleFilePress, Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { - lines := gui.State.FileManager.Render(gui.State.Modes.Diffing.Ref, gui.State.Submodules) + lines := presentation.RenderFileTree(gui.State.FileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.State.Submodules) mappedLines := make([][]string, len(lines)) for i, line := range lines { mappedLines[i] = []string{line} @@ -65,7 +66,7 @@ func (gui *Gui) branchesListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.Branches) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Branches }, - OnFocus: gui.handleBranchSelect, + OnRenderToMain: OnFocusWrapper(gui.branchesRenderToMain), Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { prs, err := gui.GitCommand.GenerateGithubPullRequestMap(gui.State.GithubState.RecentPRs, gui.State.Branches, gui.State.Remotes) @@ -92,7 +93,7 @@ func (gui *Gui) remotesListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.Remotes) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Remotes }, - OnFocus: gui.handleRemoteSelect, + OnRenderToMain: OnFocusWrapper(gui.remotesRenderToMain), OnClickSelectedItem: gui.handleRemoteEnter, Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { @@ -115,7 +116,7 @@ func (gui *Gui) remoteBranchesListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.RemoteBranches) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.RemoteBranches }, - OnFocus: gui.handleRemoteBranchSelect, + OnRenderToMain: OnFocusWrapper(gui.remoteBranchesRenderToMain), Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref) @@ -137,7 +138,7 @@ func (gui *Gui) tagsListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.Tags) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Tags }, - OnFocus: gui.handleTagSelect, + OnRenderToMain: OnFocusWrapper(gui.tagsRenderToMain), Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Modes.Diffing.Ref) @@ -150,7 +151,7 @@ func (gui *Gui) tagsListContext() IListContext { } func (gui *Gui) branchCommitsListContext() IListContext { - parseEmoji := gui.Config.GetUserConfig().Git.ParseEmoji + parseEmoji := gui.UserConfig.Git.ParseEmoji return &ListContext{ BasicContext: &BasicContext{ ViewName: "commits", @@ -160,7 +161,8 @@ func (gui *Gui) branchCommitsListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.Commits) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Commits }, - OnFocus: gui.handleCommitSelect, + OnFocus: OnFocusWrapper(gui.onCommitFocus), + OnRenderToMain: OnFocusWrapper(gui.branchCommitsRenderToMain), OnClickSelectedItem: gui.handleViewCommitFiles, Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { @@ -181,6 +183,7 @@ func (gui *Gui) branchCommitsListContext() IListContext { startIdx, length, gui.shouldShowGraph(), + gui.State.BisectInfo, ) }, SelectedItem: func() (ListItem, bool) { @@ -191,8 +194,54 @@ func (gui *Gui) branchCommitsListContext() IListContext { } } +func (gui *Gui) subCommitsListContext() IListContext { + parseEmoji := gui.UserConfig.Git.ParseEmoji + return &ListContext{ + BasicContext: &BasicContext{ + ViewName: "branches", + WindowName: "branches", + Key: SUB_COMMITS_CONTEXT_KEY, + Kind: SIDE_CONTEXT, + }, + GetItemsLength: func() int { return len(gui.State.SubCommits) }, + OnGetPanelState: func() IListPanelState { return gui.State.Panels.SubCommits }, + OnRenderToMain: OnFocusWrapper(gui.subCommitsRenderToMain), + Gui: gui, + GetDisplayStrings: func(startIdx int, length int) [][]string { + selectedCommitSha := "" + if gui.currentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY { + selectedCommit := gui.getSelectedSubCommit() + if selectedCommit != nil { + selectedCommitSha = selectedCommit.Sha + } + } + return presentation.GetCommitListDisplayStrings( + gui.State.SubCommits, + gui.State.ScreenMode != SCREEN_NORMAL, + gui.cherryPickedCommitShaMap(), + gui.State.Modes.Diffing.Ref, + parseEmoji, + selectedCommitSha, + startIdx, + length, + gui.shouldShowGraph(), + git_commands.NewNullBisectInfo(), + ) + }, + SelectedItem: func() (ListItem, bool) { + item := gui.getSelectedSubCommit() + return item, item != nil + }, + RenderSelection: true, + } +} + func (gui *Gui) shouldShowGraph() bool { - value := gui.Config.GetUserConfig().Git.Log.ShowGraph + if gui.State.Modes.Filtering.Active() { + return false + } + + value := gui.UserConfig.Git.Log.ShowGraph switch value { case "always": return true @@ -207,7 +256,7 @@ func (gui *Gui) shouldShowGraph() bool { } func (gui *Gui) reflogCommitsListContext() IListContext { - parseEmoji := gui.Config.GetUserConfig().Git.ParseEmoji + parseEmoji := gui.UserConfig.Git.ParseEmoji return &ListContext{ BasicContext: &BasicContext{ ViewName: "commits", @@ -217,7 +266,7 @@ func (gui *Gui) reflogCommitsListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.FilteredReflogCommits) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.ReflogCommits }, - OnFocus: gui.handleReflogCommitSelect, + OnRenderToMain: OnFocusWrapper(gui.reflogCommitsRenderToMain), Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { return presentation.GetReflogCommitListDisplayStrings( @@ -235,47 +284,6 @@ func (gui *Gui) reflogCommitsListContext() IListContext { } } -func (gui *Gui) subCommitsListContext() IListContext { - parseEmoji := gui.Config.GetUserConfig().Git.ParseEmoji - return &ListContext{ - BasicContext: &BasicContext{ - ViewName: "branches", - WindowName: "branches", - Key: SUB_COMMITS_CONTEXT_KEY, - Kind: SIDE_CONTEXT, - }, - GetItemsLength: func() int { return len(gui.State.SubCommits) }, - OnGetPanelState: func() IListPanelState { return gui.State.Panels.SubCommits }, - OnFocus: gui.handleSubCommitSelect, - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { - selectedCommitSha := "" - if gui.currentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY { - selectedCommit := gui.getSelectedSubCommit() - if selectedCommit != nil { - selectedCommitSha = selectedCommit.Sha - } - } - return presentation.GetCommitListDisplayStrings( - gui.State.SubCommits, - gui.State.ScreenMode != SCREEN_NORMAL, - gui.cherryPickedCommitShaMap(), - gui.State.Modes.Diffing.Ref, - parseEmoji, - selectedCommitSha, - 0, - len(gui.State.SubCommits), - gui.shouldShowGraph(), - ) - }, - SelectedItem: func() (ListItem, bool) { - item := gui.getSelectedSubCommit() - return item, item != nil - }, - RenderSelection: true, - } -} - func (gui *Gui) stashListContext() IListContext { return &ListContext{ BasicContext: &BasicContext{ @@ -286,7 +294,7 @@ func (gui *Gui) stashListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.StashEntries) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Stash }, - OnFocus: gui.handleStashEntrySelect, + OnRenderToMain: OnFocusWrapper(gui.stashRenderToMain), Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref) @@ -306,16 +314,17 @@ func (gui *Gui) commitFilesListContext() IListContext { Key: COMMIT_FILES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return gui.State.CommitFileManager.GetItemsLength() }, + GetItemsLength: func() int { return gui.State.CommitFileTreeViewModel.GetItemsLength() }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.CommitFiles }, - OnFocus: gui.handleCommitFileSelect, + OnFocus: OnFocusWrapper(gui.onCommitFileFocus), + OnRenderToMain: OnFocusWrapper(gui.commitFilesRenderToMain), Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { - if gui.State.CommitFileManager.GetItemsLength() == 0 { + if gui.State.CommitFileTreeViewModel.GetItemsLength() == 0 { return [][]string{{style.FgRed.Sprint("(none)")}} } - lines := gui.State.CommitFileManager.Render(gui.State.Modes.Diffing.Ref, gui.GitCommand.PatchManager) + lines := presentation.RenderCommitFileTree(gui.State.CommitFileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.Git.Patch.PatchManager) mappedLines := make([][]string, len(lines)) for i, line := range lines { mappedLines[i] = []string{line} @@ -340,7 +349,7 @@ func (gui *Gui) submodulesListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.Submodules) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Submodules }, - OnFocus: gui.handleSubmoduleSelect, + OnRenderToMain: OnFocusWrapper(gui.submodulesRenderToMain), Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules) @@ -362,7 +371,6 @@ func (gui *Gui) suggestionsListContext() IListContext { }, GetItemsLength: func() int { return len(gui.State.Suggestions) }, OnGetPanelState: func() IListPanelState { return gui.State.Panels.Suggestions }, - OnFocus: func() error { return nil }, Gui: gui, GetDisplayStrings: func(startIdx int, length int) [][]string { return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions) @@ -391,7 +399,7 @@ func (gui *Gui) getListContexts() []IListContext { func (gui *Gui) getListContextKeyBindings() []*Binding { bindings := make([]*Binding, 0) - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding for _, listContext := range gui.getListContexts() { listContext := listContext @@ -411,10 +419,11 @@ func (gui *Gui) getListContextKeyBindings() []*Binding { {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: listContext.handleScrollRight}, }...) - // the commits panel needs to lazyload things so it has a couple of its own handlers openSearchHandler := gui.handleOpenSearch gotoBottomHandler := listContext.handleGotoBottom - if listContext.GetViewName() == "commits" { + + // the branch commits context needs to lazyload things so it has a couple of its own handlers + if listContext.GetKey() == BRANCH_COMMITS_CONTEXT_KEY { openSearchHandler = gui.handleOpenSearchForCommitsPanel gotoBottomHandler = gui.handleGotoBottomForCommitsPanel } diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 23c6d91cb..b32b3bf44 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -27,16 +26,10 @@ func (i *menuItem) ID() string { return strings.Join(i.displayStrings, "-") } -// list panel functions - -func (gui *Gui) handleMenuSelect() error { - return nil -} - // specific functions func (gui *Gui) getMenuOptions() map[string]string { - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding return map[string]string{ gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.Tr.LcClose, @@ -46,7 +39,7 @@ func (gui *Gui) getMenuOptions() map[string]string { } func (gui *Gui) handleMenuClose() error { - return gui.returnFromContextSync() + return gui.returnFromContext() } type createMenuOptions struct { @@ -95,10 +88,7 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr menuView.SetContent(list) gui.State.Panels.Menu.SelectedLineIdx = 0 - gui.g.Update(func(g *gocui.Gui) error { - return gui.pushContext(gui.State.Contexts.Menu) - }) - return nil + return gui.pushContext(gui.State.Contexts.Menu) } func (gui *Gui) onMenuPress() error { diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index f9e2de55c..dbd5b3be7 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -7,10 +7,7 @@ import ( "io/ioutil" "math" - "github.com/go-errors/errors" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" ) @@ -18,7 +15,7 @@ func (gui *Gui) handleSelectPrevConflictHunk() error { return gui.withMergeConflictLock(func() error { gui.takeOverMergeConflictScrolling() gui.State.Panels.Merging.SelectPrevConflictHunk() - return gui.refreshMergePanel() + return gui.renderConflictsWithFocus() }) } @@ -26,7 +23,7 @@ func (gui *Gui) handleSelectNextConflictHunk() error { return gui.withMergeConflictLock(func() error { gui.takeOverMergeConflictScrolling() gui.State.Panels.Merging.SelectNextConflictHunk() - return gui.refreshMergePanel() + return gui.renderConflictsWithFocus() }) } @@ -34,7 +31,7 @@ func (gui *Gui) handleSelectNextConflict() error { return gui.withMergeConflictLock(func() error { gui.takeOverMergeConflictScrolling() gui.State.Panels.Merging.SelectNextConflict() - return gui.refreshMergePanel() + return gui.renderConflictsWithFocus() }) } @@ -42,41 +39,29 @@ func (gui *Gui) handleSelectPrevConflict() error { return gui.withMergeConflictLock(func() error { gui.takeOverMergeConflictScrolling() gui.State.Panels.Merging.SelectPrevConflict() - return gui.refreshMergePanel() + return gui.renderConflictsWithFocus() }) } -func (gui *Gui) pushFileSnapshot() error { - content, err := gui.catSelectedFile() - if err != nil { - return err - } - gui.State.Panels.Merging.PushFileSnapshot(content) - return nil -} +func (gui *Gui) handleMergeConflictUndo() error { + state := gui.State.Panels.Merging -func (gui *Gui) handlePopFileSnapshot() error { - prevContent, ok := gui.State.Panels.Merging.PopFileSnapshot() + ok := state.Undo() if !ok { return nil } - gitFile := gui.getSelectedFile() - if gitFile == nil { - return nil - } - gui.OnRunCommand(oscommands.NewCmdLogEntry("Undoing last conflict resolution", "Undo merge conflict resolution", false)) - if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil { + gui.logAction("Restoring file to previous state") + gui.logCommand("Undoing last conflict resolution", false) + if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0644); err != nil { return err } - return gui.refreshMergePanel() + return gui.renderConflictsWithFocus() } func (gui *Gui) handlePickHunk() error { return gui.withMergeConflictLock(func() error { - gui.takeOverMergeConflictScrolling() - ok, err := gui.resolveConflict(gui.State.Panels.Merging.Selection()) if err != nil { return err @@ -86,19 +71,16 @@ func (gui *Gui) handlePickHunk() error { return nil } - if gui.State.Panels.Merging.IsFinalConflict() { - if err := gui.handleCompleteMerge(); err != nil { - return err - } + if gui.State.Panels.Merging.AllConflictsResolved() { + return gui.onLastConflictResolved() } - return gui.refreshMergePanel() + + return gui.renderConflictsWithFocus() }) } func (gui *Gui) handlePickAllHunks() error { return gui.withMergeConflictLock(func() error { - gui.takeOverMergeConflictScrolling() - ok, err := gui.resolveConflict(mergeconflicts.ALL) if err != nil { return err @@ -108,17 +90,20 @@ func (gui *Gui) handlePickAllHunks() error { return nil } - return gui.refreshMergePanel() + if gui.State.Panels.Merging.AllConflictsResolved() { + return gui.onLastConflictResolved() + } + + return gui.renderConflictsWithFocus() }) } func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) { - gitFile := gui.getSelectedFile() - if gitFile == nil { - return false, nil - } + gui.takeOverMergeConflictScrolling() - ok, output, err := gui.State.Panels.Merging.ContentAfterConflictResolve(gitFile.Name, selection) + state := gui.State.Panels.Merging + + ok, content, err := state.ContentAfterConflictResolve(selection) if err != nil { return false, err } @@ -127,10 +112,6 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error return false, nil } - if err := gui.pushFileSnapshot(); err != nil { - return false, gui.surfaceError(err) - } - var logStr string switch selection { case mergeconflicts.TOP: @@ -142,39 +123,33 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error case mergeconflicts.ALL: logStr = "Picking all hunks" } - gui.OnRunCommand(oscommands.NewCmdLogEntry(logStr, "Resolve merge conflict", false)) - return true, ioutil.WriteFile(gitFile.Name, []byte(output), 0644) + gui.logAction("Resolve merge conflict") + gui.logCommand(logStr, false) + state.PushContent(content) + return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0644) } -func (gui *Gui) refreshMergePanelWithLock() error { - return gui.withMergeConflictLock(gui.refreshMergePanel) -} +// precondition: we actually have conflicts to render +func (gui *Gui) renderConflicts(hasFocus bool) error { + state := gui.State.Panels.Merging.State + content := mergeconflicts.ColoredConflictFile(state, hasFocus) -func (gui *Gui) refreshMergePanel() error { - panelState := gui.State.Panels.Merging - cat, err := gui.catSelectedFile() - if err != nil { - return gui.refreshMainViews(refreshMainOpts{ - main: &viewUpdateOpts{ - title: "", - task: NewRenderStringTask(err.Error()), - }, + if !gui.State.Panels.Merging.UserVerticalScrolling { + // TODO: find a way to not have to do this OnUIThread thing. Why doesn't it work + // without it given that we're calling the 'no scroll' variant below? + gui.OnUIThread(func() error { + gui.State.Panels.Merging.Lock() + defer gui.State.Panels.Merging.Unlock() + + if !state.Active() { + return nil + } + + gui.centerYPos(gui.Views.Main, state.GetConflictMiddle()) + return nil }) } - panelState.SetConflictsFromCat(cat) - - if panelState.NoConflicts() { - return gui.handleCompleteMerge() - } - - hasFocus := gui.currentViewName() == "main" - content := mergeconflicts.ColoredConflictFile(cat, panelState.State, hasFocus) - - if err := gui.scrollToConflict(); err != nil { - return err - } - return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ title: gui.Tr.MergeConflictsTitle, @@ -184,50 +159,25 @@ func (gui *Gui) refreshMergePanel() error { }) } -func (gui *Gui) catSelectedFile() (string, error) { - item := gui.getSelectedFile() - if item == nil { - return "", errors.New(gui.Tr.NoFilesDisplay) - } - - if item.Type != "file" { - return "", errors.New(gui.Tr.NotAFile) - } - - cat, err := gui.GitCommand.CatFile(item.Name) - if err != nil { - gui.Log.Error(err) - return "", err - } - return cat, nil +func (gui *Gui) renderConflictsWithFocus() error { + return gui.renderConflicts(true) } -func (gui *Gui) scrollToConflict() error { - if gui.State.Panels.Merging.UserVerticalScrolling { - return nil - } - - panelState := gui.State.Panels.Merging - if panelState.NoConflicts() { - return nil - } - - gui.centerYPos(gui.Views.Main, panelState.GetConflictMiddle()) - - return nil +func (gui *Gui) renderConflictsWithLock(hasFocus bool) error { + return gui.withMergeConflictLock(func() error { + return gui.renderConflicts(hasFocus) + }) } func (gui *Gui) centerYPos(view *gocui.View, y int) { ox, _ := view.Origin() _, height := view.Size() newOriginY := int(math.Max(0, float64(y-(height/2)))) - gui.g.Update(func(g *gocui.Gui) error { - return view.SetOrigin(ox, newOriginY) - }) + _ = view.SetOrigin(ox, newOriginY) } func (gui *Gui) getMergingOptions() map[string]string { - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding return map[string]string{ fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.Tr.LcSelectHunk, @@ -239,72 +189,66 @@ func (gui *Gui) getMergingOptions() map[string]string { } func (gui *Gui) handleEscapeMerge() error { - gui.takeOverMergeConflictScrolling() - - gui.State.Panels.Merging.Reset() if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil { return err } - // it's possible this method won't be called from the merging view so we need to - // ensure we only 'return' focus if we already have it - if gui.g.CurrentView() == gui.Views.Main { - return gui.pushContext(gui.State.Contexts.Files) + + return gui.escapeMerge() +} + +func (gui *Gui) onLastConflictResolved() error { + // as part of refreshing files, we handle the situation where a file has had + // its merge conflicts resolved. + return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}}) +} + +func (gui *Gui) resetMergeState() { + gui.takeOverMergeConflictScrolling() + gui.State.Panels.Merging.Reset() +} + +func (gui *Gui) setMergeState(path string) (bool, error) { + content, err := gui.Git.File.Cat(path) + if err != nil { + return false, err } + + gui.State.Panels.Merging.SetContent(content, path) + + return !gui.State.Panels.Merging.NoConflicts(), nil +} + +func (gui *Gui) setMergeStateWithLock(path string) (bool, error) { + gui.State.Panels.Merging.Lock() + defer gui.State.Panels.Merging.Unlock() + + return gui.setMergeState(path) +} + +func (gui *Gui) resetMergeStateWithLock() { + gui.State.Panels.Merging.Lock() + defer gui.State.Panels.Merging.Unlock() + + gui.resetMergeState() +} + +func (gui *Gui) escapeMerge() error { + gui.resetMergeState() + + // doing this in separate UI thread so that we're not still holding the lock by the time refresh the file + gui.OnUIThread(func() error { + return gui.pushContext(gui.State.Contexts.Files) + }) return nil } -func (gui *Gui) handleCompleteMerge() error { - if err := gui.stageSelectedFile(); err != nil { - return err - } - if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil { - return err - } - // if we got conflicts after unstashing, we don't want to call any git - // commands to continue rebasing/merging here - if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_NORMAL { - return gui.handleEscapeMerge() - } - // if there are no more files with merge conflicts, we should ask whether the user wants to continue - if !gui.anyFilesWithMergeConflicts() { - return gui.promptToContinueRebase() - } - return gui.handleEscapeMerge() -} - -// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress -func (gui *Gui) promptToContinueRebase() error { - gui.takeOverMergeConflictScrolling() - - return gui.ask(askOpts{ - title: "continue", - prompt: gui.Tr.ConflictsResolved, - handlersManageFocus: true, - handleConfirm: func() error { - if err := gui.pushContext(gui.State.Contexts.Files); err != nil { - return err - } - - return gui.genericMergeCommand(REBASE_OPTION_CONTINUE) - }, - handleClose: func() error { - return gui.pushContext(gui.State.Contexts.Files) - }, - }) -} - -func (gui *Gui) canScrollMergePanel() bool { +func (gui *Gui) renderingConflicts() bool { currentView := gui.g.CurrentView() if currentView != gui.Views.Main && currentView != gui.Views.Files { return false } - file := gui.getSelectedFile() - if file == nil { - return false - } - - return file.HasInlineMergeConflicts + return gui.State.Panels.Merging.Active() } func (gui *Gui) withMergeConflictLock(f func() error) error { @@ -317,3 +261,44 @@ func (gui *Gui) withMergeConflictLock(f func() error) error { func (gui *Gui) takeOverMergeConflictScrolling() { gui.State.Panels.Merging.UserVerticalScrolling = false } + +func (gui *Gui) setConflictsAndRender(path string, hasFocus bool) (bool, error) { + hasConflicts, err := gui.setMergeState(path) + if err != nil { + return false, err + } + + // if we don't have conflicts we'll fall through and show the diff + if hasConflicts { + return true, gui.renderConflicts(hasFocus) + } + + return false, nil +} + +func (gui *Gui) setConflictsAndRenderWithLock(path string, hasFocus bool) (bool, error) { + gui.State.Panels.Merging.Lock() + defer gui.State.Panels.Merging.Unlock() + + return gui.setConflictsAndRender(path, hasFocus) +} + +func (gui *Gui) refreshMergeState() error { + gui.State.Panels.Merging.Lock() + defer gui.State.Panels.Merging.Unlock() + + if gui.currentContext().GetKey() != MAIN_MERGING_CONTEXT_KEY { + return nil + } + + hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true) + if err != nil { + return gui.surfaceError(err) + } + + if !hasConflicts { + return gui.escapeMerge() + } + + return nil +} diff --git a/pkg/gui/mergeconflicts/find_conflicts.go b/pkg/gui/mergeconflicts/find_conflicts.go index 80e487f85..14a08fd68 100644 --- a/pkg/gui/mergeconflicts/find_conflicts.go +++ b/pkg/gui/mergeconflicts/find_conflicts.go @@ -1,6 +1,10 @@ package mergeconflicts import ( + "bufio" + "bytes" + "io" + "os" "strings" "github.com/jesseduffield/lazygit/pkg/utils" @@ -53,19 +57,59 @@ func findConflicts(content string) []*mergeConflict { return conflicts } +var CONFLICT_START = "<<<<<<< " +var CONFLICT_END = ">>>>>>> " +var CONFLICT_START_BYTES = []byte(CONFLICT_START) +var 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, "<<<<<<< "): + case strings.HasPrefix(trimmedLine, CONFLICT_START): return START case strings.HasPrefix(trimmedLine, "||||||| "): return ANCESTOR case trimmedLine == "=======": return TARGET - case strings.HasPrefix(trimmedLine, ">>>>>>> "): + 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(bufio.ScanLines) + 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 index bcc70ce73..f4ab4d30c 100644 --- a/pkg/gui/mergeconflicts/find_conflicts_test.go +++ b/pkg/gui/mergeconflicts/find_conflicts_test.go @@ -1,6 +1,7 @@ package mergeconflicts import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -59,3 +60,42 @@ func TestDetermineLineType(t *testing.T) { 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/rendering.go b/pkg/gui/mergeconflicts/rendering.go index 5b44f2a14..54fc4e836 100644 --- a/pkg/gui/mergeconflicts/rendering.go +++ b/pkg/gui/mergeconflicts/rendering.go @@ -8,7 +8,8 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -func ColoredConflictFile(content string, state *State, hasFocus bool) string { +func ColoredConflictFile(state *State, hasFocus bool) string { + content := state.GetContent() if len(state.conflicts) == 0 { return content } diff --git a/pkg/gui/mergeconflicts/state.go b/pkg/gui/mergeconflicts/state.go index f202cf772..d84f05545 100644 --- a/pkg/gui/mergeconflicts/state.go +++ b/pkg/gui/mergeconflicts/state.go @@ -3,13 +3,19 @@ package mergeconflicts import ( "sync" - "github.com/golang-collections/collections/stack" "github.com/jesseduffield/lazygit/pkg/utils" ) type State struct { sync.Mutex + // 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 @@ -17,9 +23,6 @@ type State struct { // 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 - - // this allows us to undo actions - EditHistory *stack.Stack } func NewState() *State { @@ -28,7 +31,7 @@ func NewState() *State { conflictIndex: 0, selectionIndex: 0, conflicts: []*mergeConflict{}, - EditHistory: stack.New(), + contents: []string{}, } } @@ -63,18 +66,6 @@ func (s *State) SelectPrevConflict() { s.setConflictIndex(s.conflictIndex - 1) } -func (s *State) PushFileSnapshot(content string) { - s.EditHistory.Push(content) -} - -func (s *State) PopFileSnapshot() (string, bool) { - if s.EditHistory.Len() == 0 { - return "", false - } - - return s.EditHistory.Pop().(string), true -} - func (s *State) currentConflict() *mergeConflict { if len(s.conflicts) == 0 { return nil @@ -83,8 +74,48 @@ func (s *State) currentConflict() *mergeConflict { return s.conflicts[s.conflictIndex] } -func (s *State) SetConflictsFromCat(cat string) { - s.setConflicts(findConflicts(cat)) +// 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) { @@ -110,29 +141,37 @@ func (s *State) availableSelections() []Selection { return nil } -func (s *State) IsFinalConflict() bool { - return len(s.conflicts) == 1 +func (s *State) AllConflictsResolved() bool { + return len(s.conflicts) == 0 } func (s *State) Reset() { - s.EditHistory = stack.New() + s.contents = []string{} + s.path = "" +} + +func (s *State) Active() bool { + return s.path != "" } func (s *State) GetConflictMiddle() int { - return s.currentConflict().target + currentConflict := s.currentConflict() + + if currentConflict == nil { + return 0 + } + + return currentConflict.target } -func (s *State) ContentAfterConflictResolve( - path string, - selection Selection, -) (bool, string, error) { +func (s *State) ContentAfterConflictResolve(selection Selection) (bool, string, error) { conflict := s.currentConflict() if conflict == nil { return false, "", nil } content := "" - err := utils.ForEachLineInFile(path, func(line string, i int) { + err := utils.ForEachLineInFile(s.path, func(line string, i int) { if selection.isIndexToKeep(conflict, i) { content += line } diff --git a/pkg/gui/modes.go b/pkg/gui/modes.go index 92b7d3171..b60fafc8a 100644 --- a/pkg/gui/modes.go +++ b/pkg/gui/modes.go @@ -1,7 +1,9 @@ package gui import ( - "github.com/jesseduffield/lazygit/pkg/commands" + "fmt" + + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/gui/style" ) @@ -16,34 +18,34 @@ func (gui *Gui) modeStatuses() []modeStatus { { isActive: gui.State.Modes.Diffing.Active, description: func() string { - return style.FgMagenta.Sprintf( - "%s %s %s", - gui.Tr.LcShowingGitDiff, - "git diff "+gui.diffStr(), - style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses), + return gui.withResetButton( + fmt.Sprintf( + "%s %s", + gui.Tr.LcShowingGitDiff, + "git diff "+gui.diffStr(), + ), + style.FgMagenta, ) }, reset: gui.exitDiffMode, }, { - isActive: gui.GitCommand.PatchManager.Active, + isActive: gui.Git.Patch.PatchManager.Active, description: func() string { - return style.FgYellow.SetBold().Sprintf( - "%s %s", - gui.Tr.LcBuildingPatch, - style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses), - ) + return gui.withResetButton(gui.Tr.LcBuildingPatch, style.FgYellow.SetBold()) }, reset: gui.handleResetPatch, }, { isActive: gui.State.Modes.Filtering.Active, description: func() string { - return style.FgRed.SetBold().Sprintf( - "%s '%s' %s", - gui.Tr.LcFilteringBy, - gui.State.Modes.Filtering.GetPath(), - style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses), + return gui.withResetButton( + fmt.Sprintf( + "%s '%s'", + gui.Tr.LcFilteringBy, + gui.State.Modes.Filtering.GetPath(), + ), + style.FgRed, ) }, reset: gui.exitFilterMode, @@ -51,27 +53,44 @@ func (gui *Gui) modeStatuses() []modeStatus { { isActive: gui.State.Modes.CherryPicking.Active, description: func() string { - return style.FgCyan.Sprintf( - "%d commits copied %s", - len(gui.State.Modes.CherryPicking.CherryPickedCommits), - style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses), + return gui.withResetButton( + fmt.Sprintf( + "%d commits copied", + len(gui.State.Modes.CherryPicking.CherryPickedCommits), + ), + style.FgCyan, ) }, reset: gui.exitCherryPickingMode, }, { isActive: func() bool { - return gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL + return gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE }, description: func() string { - workingTreeState := gui.GitCommand.WorkingTreeState() - return style.FgYellow.Sprintf( - "%s %s", - workingTreeState, - style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses), + workingTreeState := gui.Git.Status.WorkingTreeState() + return gui.withResetButton( + formatWorkingTreeState(workingTreeState), style.FgYellow, ) }, reset: gui.abortMergeOrRebaseWithConfirm, }, + { + isActive: func() bool { + return gui.State.BisectInfo.Started() + }, + description: func() string { + return gui.withResetButton("bisecting", style.FgGreen) + }, + reset: gui.resetBisect, + }, } } + +func (gui *Gui) withResetButton(content string, textStyle style.TextStyle) string { + return textStyle.Sprintf( + "%s %s", + content, + style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses), + ) +} diff --git a/pkg/gui/patch_building_panel.go b/pkg/gui/patch_building_panel.go index 5c1d4de5f..eb7728100 100644 --- a/pkg/gui/patch_building_panel.go +++ b/pkg/gui/patch_building_panel.go @@ -17,8 +17,8 @@ func (gui *Gui) getFromAndReverseArgsForDiff(to string) (string, bool) { return from, reverse } -func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int, state *LblPanelState) error { - if !gui.GitCommand.PatchManager.Active() { +func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error { + if !gui.Git.Patch.PatchManager.Active() { return gui.handleEscapePatchBuildingPanel() } @@ -31,14 +31,14 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int, state *LblPanelSt return nil } - to := gui.State.CommitFileManager.GetParent() + to := gui.State.CommitFileTreeViewModel.GetParent() from, reverse := gui.getFromAndReverseArgsForDiff(to) - diff, err := gui.GitCommand.ShowFileDiff(from, to, reverse, node.GetPath(), true) + diff, err := gui.Git.WorkingTree.ShowFileDiff(from, to, reverse, node.GetPath(), true) if err != nil { return err } - secondaryDiff := gui.GitCommand.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true) + secondaryDiff := gui.Git.Patch.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true) if err != nil { return err } @@ -59,20 +59,31 @@ func (gui *Gui) handleRefreshPatchBuildingPanel(selectedLineIdx int) error { gui.Mutexes.LineByLinePanelMutex.Lock() defer gui.Mutexes.LineByLinePanelMutex.Unlock() - return gui.refreshPatchBuildingPanel(selectedLineIdx, gui.State.Panels.LineByLine) + return gui.refreshPatchBuildingPanel(selectedLineIdx) +} + +func (gui *Gui) onPatchBuildingFocus(selectedLineIdx int) error { + gui.Mutexes.LineByLinePanelMutex.Lock() + defer gui.Mutexes.LineByLinePanelMutex.Unlock() + + if gui.State.Panels.LineByLine == nil || selectedLineIdx != -1 { + return gui.refreshPatchBuildingPanel(selectedLineIdx) + } + + return nil } func (gui *Gui) handleToggleSelectionForPatch() error { err := gui.withLBLActiveCheck(func(state *LblPanelState) error { - toggleFunc := gui.GitCommand.PatchManager.AddFileLineRange + toggleFunc := gui.Git.Patch.PatchManager.AddFileLineRange filename := gui.getSelectedCommitFileName() - includedLineIndices, err := gui.GitCommand.PatchManager.GetFileIncLineIndices(filename) + includedLineIndices, err := gui.Git.Patch.PatchManager.GetFileIncLineIndices(filename) if err != nil { return err } currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.GetSelectedLineIdx()) if currentLineIsStaged { - toggleFunc = gui.GitCommand.PatchManager.RemoveFileLineRange + toggleFunc = gui.Git.Patch.PatchManager.RemoveFileLineRange } // add range of lines to those set for the file @@ -105,8 +116,8 @@ func (gui *Gui) handleToggleSelectionForPatch() error { func (gui *Gui) handleEscapePatchBuildingPanel() error { gui.escapeLineByLinePanel() - if gui.GitCommand.PatchManager.IsEmpty() { - gui.GitCommand.PatchManager.Reset() + if gui.Git.Patch.PatchManager.IsEmpty() { + gui.Git.Patch.PatchManager.Reset() } if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() { @@ -118,8 +129,8 @@ func (gui *Gui) handleEscapePatchBuildingPanel() error { } func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts { - if gui.GitCommand.PatchManager.Active() { - patch := gui.GitCommand.PatchManager.RenderAggregatedPatchColored(false) + if gui.Git.Patch.PatchManager.Active() { + patch := gui.Git.Patch.PatchManager.RenderAggregatedPatchColored(false) return &viewUpdateOpts{ title: "Custom Patch", diff --git a/pkg/gui/patch_options_panel.go b/pkg/gui/patch_options_panel.go index 3e36c4983..c9a3defce 100644 --- a/pkg/gui/patch_options_panel.go +++ b/pkg/gui/patch_options_panel.go @@ -3,11 +3,11 @@ package gui import ( "fmt" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" ) func (gui *Gui) handleCreatePatchOptionsMenu() error { - if !gui.GitCommand.PatchManager.Active() { + if !gui.Git.Patch.PatchManager.Active() { return gui.createErrorPanel(gui.Tr.NoPatchError) } @@ -26,10 +26,10 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error { }, } - if gui.GitCommand.PatchManager.CanRebase && gui.workingTreeState() == commands.REBASE_MODE_NORMAL { + if gui.Git.Patch.PatchManager.CanRebase && gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_NONE { menuItems = append(menuItems, []*menuItem{ { - displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.To), + displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.Git.Patch.PatchManager.To), onPress: gui.handleDeletePatchFromCommit, }, { @@ -44,7 +44,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error { if gui.currentContext().GetKey() == gui.State.Contexts.BranchCommits.GetKey() { selectedCommit := gui.getSelectedLocalCommit() - if selectedCommit != nil && gui.GitCommand.PatchManager.To != selectedCommit.Sha { + if selectedCommit != nil && gui.Git.Patch.PatchManager.To != selectedCommit.Sha { // adding this option to index 1 menuItems = append( menuItems[:1], @@ -66,7 +66,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error { func (gui *Gui) getPatchCommitIndex() int { for index, commit := range gui.State.Commits { - if commit.Sha == gui.GitCommand.PatchManager.To { + if commit.Sha == gui.Git.Patch.PatchManager.To { return index } } @@ -74,7 +74,7 @@ func (gui *Gui) getPatchCommitIndex() int { } func (gui *Gui) validateNormalWorkingTreeState() (bool, error) { - if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL { + if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE { return false, gui.createErrorPanel(gui.Tr.CantPatchWhileRebasingError) } return true, nil @@ -98,7 +98,8 @@ func (gui *Gui) handleDeletePatchFromCommit() error { return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error { commitIndex := gui.getPatchCommitIndex() - err := gui.GitCommand.WithSpan(gui.Tr.Spans.RemovePatchFromCommit).DeletePatchesFromCommit(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager) + gui.logAction(gui.Tr.Actions.RemovePatchFromCommit) + err := gui.Git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex) return gui.handleGenericMergeCommandResult(err) }) } @@ -114,7 +115,8 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error { return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error { commitIndex := gui.getPatchCommitIndex() - err := gui.GitCommand.WithSpan(gui.Tr.Spans.MovePatchToSelectedCommit).MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx, gui.GitCommand.PatchManager) + gui.logAction(gui.Tr.Actions.MovePatchToSelectedCommit) + err := gui.Git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx) return gui.handleGenericMergeCommandResult(err) }) } @@ -131,7 +133,8 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error { pull := func(stash bool) error { return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error { commitIndex := gui.getPatchCommitIndex() - err := gui.GitCommand.WithSpan(gui.Tr.Spans.MovePatchIntoIndex).MovePatchIntoIndex(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager, stash) + gui.logAction(gui.Tr.Actions.MovePatchIntoIndex) + err := gui.Git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash) return gui.handleGenericMergeCommandResult(err) }) } @@ -160,7 +163,8 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error { return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error { commitIndex := gui.getPatchCommitIndex() - err := gui.GitCommand.WithSpan(gui.Tr.Spans.MovePatchIntoNewCommit).PullPatchIntoNewCommit(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager) + gui.logAction(gui.Tr.Actions.MovePatchIntoNewCommit) + err := gui.Git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex) return gui.handleGenericMergeCommandResult(err) }) } @@ -170,18 +174,19 @@ func (gui *Gui) handleApplyPatch(reverse bool) error { return err } - span := gui.Tr.Spans.ApplyPatch + action := gui.Tr.Actions.ApplyPatch if reverse { - span = "Apply patch in reverse" + action = "Apply patch in reverse" } - if err := gui.GitCommand.WithSpan(span).PatchManager.ApplyPatches(reverse); err != nil { + gui.logAction(action) + if err := gui.Git.Patch.PatchManager.ApplyPatches(reverse); err != nil { return gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{mode: ASYNC}) } func (gui *Gui) handleResetPatch() error { - gui.GitCommand.PatchManager.Reset() + gui.Git.Patch.PatchManager.Reset() if gui.currentContextKeyIgnoringPopups() == MAIN_PATCH_BUILDING_CONTEXT_KEY { if err := gui.pushContext(gui.State.Contexts.CommitFiles); err != nil { return err diff --git a/pkg/gui/popup_handler.go b/pkg/gui/popup_handler.go new file mode 100644 index 000000000..9cacc3574 --- /dev/null +++ b/pkg/gui/popup_handler.go @@ -0,0 +1,87 @@ +package gui + +import ( + "strings" + + "github.com/jesseduffield/lazygit/pkg/gui/style" +) + +type PopupHandler interface { + Error(message string) error + Ask(opts askOpts) error + Prompt(opts promptOpts) error + Loader(message string) error +} + +type RealPopupHandler struct { + gui *Gui +} + +func (self *RealPopupHandler) Error(message string) error { + gui := self.gui + + coloredMessage := style.FgRed.Sprint(strings.TrimSpace(message)) + if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil { + return err + } + + return self.Ask(askOpts{ + title: gui.Tr.Error, + prompt: coloredMessage, + }) +} + +func (self *RealPopupHandler) Ask(opts askOpts) error { + gui := self.gui + + return gui.createPopupPanel(createPopupPanelOpts{ + title: opts.title, + prompt: opts.prompt, + handleConfirm: opts.handleConfirm, + handleClose: opts.handleClose, + handlersManageFocus: opts.handlersManageFocus, + }) +} + +func (self *RealPopupHandler) Prompt(opts promptOpts) error { + gui := self.gui + + return gui.createPopupPanel(createPopupPanelOpts{ + title: opts.title, + prompt: opts.initialContent, + editable: true, + handleConfirmPrompt: opts.handleConfirm, + findSuggestionsFunc: opts.findSuggestionsFunc, + }) +} + +func (self *RealPopupHandler) Loader(message string) error { + gui := self.gui + + return gui.createPopupPanel(createPopupPanelOpts{ + prompt: message, + hasLoader: true, + }) +} + +type TestPopupHandler struct { + onError func(message string) error + onAsk func(opts askOpts) error + onPrompt func(opts promptOpts) error +} + +func (self *TestPopupHandler) Error(message string) error { + return self.onError(message) +} + +func (self *TestPopupHandler) Ask(opts askOpts) error { + return self.onAsk(opts) +} + +func (self *TestPopupHandler) Prompt(opts promptOpts) error { + return self.onPrompt(opts) +} + +func (self *TestPopupHandler) Loader(message string) error { + return nil +} diff --git a/pkg/gui/presentation/authors/authors.go b/pkg/gui/presentation/authors/authors.go index c48683a12..d4eb8e465 100644 --- a/pkg/gui/presentation/authors/authors.go +++ b/pkg/gui/presentation/authors/authors.go @@ -13,9 +13,13 @@ import ( // 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) -var authorNameCache = make(map[string]string) -var authorStyleCache = make(map[string]style.TextStyle) +var ( + authorInitialCache = make(map[string]string) + authorNameCache = make(map[string]string) + authorStyleCache = make(map[string]style.TextStyle) +) + +const authorNameWildcard = "*" func ShortAuthor(authorName string) string { if value, ok := authorInitialCache[authorName]; ok { @@ -51,6 +55,11 @@ func AuthorStyle(authorName string) style.TextStyle { 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 @@ -105,8 +114,5 @@ func getFirstRune(str string) rune { } func SetCustomAuthors(customAuthorColors map[string]string) { - for authorName, colorSequence := range customAuthorColors { - style := style.New().SetFg(style.NewRGBColor(color.HEX(colorSequence, false))) - authorStyleCache[authorName] = style - } + authorStyleCache = utils.SetCustomColors(customAuthorColors) } diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go index 3fff6a4d5..4dac7328e 100644 --- a/pkg/gui/presentation/branches.go +++ b/pkg/gui/presentation/branches.go @@ -2,19 +2,17 @@ package presentation import ( "fmt" - "strconv" "strings" "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" ) -func GetBranchListDisplayStrings( - branches []*models.Branch, - prs map[*models.Branch]*models.GithubPullRequest, - fullDescription bool, - diffName string) [][]string { +var branchPrefixColorCache = make(map[string]style.TextStyle) + +func GetBranchListDisplayStrings(branches []*models.Branch, fullDescription bool, diffName string) [][]string { lines := make([][]string, len(branches)) for i := range branches { @@ -55,7 +53,13 @@ func getBranchDisplayStrings( res = append(res, coloredPrNumber(pr, hasPr), coloredName) if fullDescription { - res = append(res, style.FgYellow.Sprint(b.UpstreamName)) + return append( + res, + fmt.Sprintf("%s %s", + style.FgYellow.Sprint(b.UpstreamRemote), + style.FgYellow.Sprint(b.UpstreamBranch), + ), + ) } return res } @@ -64,6 +68,10 @@ func getBranchDisplayStrings( func GetBranchTextStyle(name string) style.TextStyle { branchType := strings.Split(name, "/")[0] + if value, ok := branchPrefixColorCache[branchType]; ok { + return value + } + switch branchType { case "feature": return style.FgGreen @@ -91,17 +99,6 @@ func BranchStatus(branch *models.Branch) string { return fmt.Sprintf("↑%s↓%s", branch.Pushables, branch.Pullables) } -func coloredPrNumber(pr *models.GithubPullRequest, hasPr bool) string { - if hasPr { - colour := style.FgMagenta // = state MERGED - switch pr.State { - case "OPEN": - colour = style.FgGreen - case "CLOSED": - colour = style.FgRed - } - return colour.Sprint("#" + strconv.Itoa(pr.Number)) - } - - return ("") +func SetCustomBranches(customBranchColors map[string]string) { + branchPrefixColorCache = utils.SetCustomColors(customBranchColors) } diff --git a/pkg/gui/presentation/commit_files.go b/pkg/gui/presentation/commit_files.go deleted file mode 100644 index 3b68b9eeb..000000000 --- a/pkg/gui/presentation/commit_files.go +++ /dev/null @@ -1,47 +0,0 @@ -package presentation - -import ( - "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/patch" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func GetCommitFileLine(name string, diffName string, commitFile *models.CommitFile, status patch.PatchStatus) string { - colour := theme.DefaultTextColor - if diffName == name { - colour = theme.DiffTerminalColor - } else { - switch status { - case patch.WHOLE: - colour = style.FgGreen - case patch.PART: - colour = style.FgYellow - } - } - - name = utils.EscapeSpecialChars(name) - if commitFile == nil { - return colour.Sprint(name) - } - - return getColorForChangeStatus(commitFile.ChangeStatus).Sprint(commitFile.ChangeStatus) + " " + colour.Sprint(name) -} - -func getColorForChangeStatus(changeStatus string) style.TextStyle { - switch changeStatus { - case "A": - return style.FgGreen - case "M", "R": - return style.FgYellow - case "D": - return style.FgRed - case "C": - return style.FgCyan - case "T": - return style.FgMagenta - default: - return theme.DefaultTextColor - } -} diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go index 68cd554fa..2bc9f475c 100644 --- a/pkg/gui/presentation/commits.go +++ b/pkg/gui/presentation/commits.go @@ -4,6 +4,7 @@ import ( "strings" "sync" + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/presentation/authors" "github.com/jesseduffield/lazygit/pkg/gui/presentation/graph" @@ -21,6 +22,11 @@ type pipeSetCacheKey struct { var pipeSetCache = make(map[pipeSetCacheKey][][]*graph.Pipe) var mutex sync.Mutex +type bisectBounds struct { + newIndex int + oldIndex int +} + func GetCommitListDisplayStrings( commits []*models.Commit, fullDescription bool, @@ -31,6 +37,7 @@ func GetCommitListDisplayStrings( startIdx int, length int, showGraph bool, + bisectInfo *git_commands.BisectInfo, ) [][]string { mutex.Lock() defer mutex.Unlock() @@ -39,6 +46,100 @@ func GetCommitListDisplayStrings( return nil } + if startIdx > len(commits) { + return nil + } + + end := utils.Min(startIdx+length, len(commits)) + // this is where my non-TODO commits begin + rebaseOffset := utils.Min(indexOfFirstNonTODOCommit(commits), end) + + filteredCommits := commits[startIdx:end] + + 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 { + // 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 := utils.Max(startIdx, rebaseOffset) + + pipeSets := loadPipesets(commits[rebaseOffset:]) + pipeSetOffset := utils.Max(startIdx-rebaseOffset, 0) + graphPipeSets := pipeSets[pipeSetOffset:utils.Max(end-rebaseOffset, 0)] + graphCommits := commits[graphOffset:end] + graphLines := graph.RenderAux( + graphPipeSets, + graphCommits, + selectedCommitSha, + ) + getGraphLine = func(idx int) string { + if idx >= graphOffset { + return graphLines[idx-graphOffset] + } else { + return "" + } + } + } else { + getGraphLine = func(idx int) string { return "" } + } + + lines := make([][]string, 0, len(filteredCommits)) + var bisectStatus BisectStatus + for i, commit := range filteredCommits { + unfilteredIdx := i + startIdx + bisectStatus = getBisectStatus(unfilteredIdx, commit.Sha, bisectInfo, bisectBounds) + lines = append(lines, displayCommit( + commit, + cherryPickedCommitShaMap, + diffName, + 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.Sha == bisectInfo.GetNewSha() { + bisectBounds.newIndex = i + } + + status, ok := bisectInfo.Status(commit.Sha) + 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 sha 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{ @@ -57,30 +158,77 @@ func GetCommitListDisplayStrings( pipeSetCache[cacheKey] = pipeSets } - if startIdx > len(commits) { - return nil - } - end := startIdx + length - if end > len(commits)-1 { - end = len(commits) - 1 + 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, commitSha string, bisectInfo *git_commands.BisectInfo, bisectBounds *bisectBounds) BisectStatus { + if !bisectInfo.Started() { + return BisectStatusNone } - filteredCommits := commits[startIdx : end+1] + if bisectInfo.GetCurrentSha() == commitSha { + return BisectStatusCurrent + } - var getGraphLine func(int) string - if showGraph { - filteredPipeSets := pipeSets[startIdx : end+1] - graphLines := graph.RenderAux(filteredPipeSets, filteredCommits, selectedCommitSha) - getGraphLine = func(idx int) string { return graphLines[idx] } + status, ok := bisectInfo.Status(commitSha) + if ok { + switch status { + case git_commands.BisectStatusNew: + return BisectStatusNew + case git_commands.BisectStatusOld: + return BisectStatusOld + case git_commands.BisectStatusSkipped: + return BisectStatusSkipped + } } else { - getGraphLine = func(idx int) string { return "" } + if bisectBounds != nil && index >= bisectBounds.newIndex && index <= bisectBounds.oldIndex { + return BisectStatusCandidate + } else { + return BisectStatusNone + } } - lines := make([][]string, 0, len(filteredCommits)) - for i, commit := range filteredCommits { - lines = append(lines, displayCommit(commit, cherryPickedCommitShaMap, diffName, parseEmoji, getGraphLine(i), fullDescription)) + // should never land here + return BisectStatusNone +} + +func getBisectStatusText(bisectStatus BisectStatus, bisectInfo *git_commands.BisectInfo) string { + if bisectStatus == BisectStatusNone { + return "" } - return lines + + 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("?") + } + + return "" } func displayCommit( @@ -90,9 +238,11 @@ func displayCommit( parseEmoji bool, graphLine string, fullDescription bool, + bisectStatus BisectStatus, + bisectInfo *git_commands.BisectInfo, ) []string { - - shaColor := getShaColor(commit, diffName, cherryPickedCommitShaMap) + shaColor := getShaColor(commit, diffName, cherryPickedCommitShaMap, bisectStatus, bisectInfo) + bisectString := getBisectStatusText(bisectStatus, bisectInfo) actionString := "" if commit.Action != "" { @@ -122,6 +272,7 @@ func displayCommit( cols := make([]string, 0, 5) cols = append(cols, shaColor.Sprint(commit.ShortSha())) + cols = append(cols, bisectString) if fullDescription { cols = append(cols, style.FgBlue.Sprint(utils.UnixToDate(commit.UnixTimestamp))) } @@ -133,10 +284,39 @@ func displayCommit( ) return cols - } -func getShaColor(commit *models.Commit, diffName string, cherryPickedCommitShaMap map[string]bool) style.TextStyle { +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 getShaColor( + commit *models.Commit, + diffName string, + cherryPickedCommitShaMap map[string]bool, + bisectStatus BisectStatus, + bisectInfo *git_commands.BisectInfo, +) style.TextStyle { + if bisectInfo.Started() { + return getBisectStatusColor(bisectStatus) + } + diffed := commit.Sha == diffName shaColor := theme.DefaultTextColor switch commit.Status { diff --git a/pkg/gui/presentation/commits_test.go b/pkg/gui/presentation/commits_test.go new file mode 100644 index 000000000..b7fd23468 --- /dev/null +++ b/pkg/gui/presentation/commits_test.go @@ -0,0 +1,229 @@ +package presentation + +import ( + "strings" + "testing" + + "github.com/gookit/color" + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/stretchr/testify/assert" + "github.com/xo/terminfo" +) + +func init() { + color.ForceSetColorLevel(terminfo.ColorLevelNone) +} + +func formatExpected(expected string) string { + return strings.TrimSpace(strings.ReplaceAll(expected, "\t", "")) +} + +func TestGetCommitListDisplayStrings(t *testing.T) { + scenarios := []struct { + testName string + commits []*models.Commit + fullDescription bool + cherryPickedCommitShaMap map[string]bool + diffName string + parseEmoji bool + selectedCommitSha string + startIdx int + length int + showGraph bool + bisectInfo *git_commands.BisectInfo + expected string + focus bool + }{ + { + testName: "no commits", + commits: []*models.Commit{}, + startIdx: 0, + length: 1, + showGraph: false, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: "", + }, + { + testName: "some commits", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1"}, + {Name: "commit2", Sha: "sha2"}, + }, + startIdx: 0, + length: 2, + showGraph: false, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha1 commit1 + sha2 commit2 + `), + }, + { + testName: "showing graph", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1", Parents: []string{"sha2", "sha3"}}, + {Name: "commit2", Sha: "sha2", Parents: []string{"sha3"}}, + {Name: "commit3", Sha: "sha3", Parents: []string{"sha4"}}, + {Name: "commit4", Sha: "sha4", Parents: []string{"sha5"}}, + {Name: "commit5", Sha: "sha5", Parents: []string{"sha7"}}, + }, + startIdx: 0, + length: 5, + showGraph: true, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha1 ⏣─╮ commit1 + sha2 ◯ │ commit2 + sha3 ◯─╯ commit3 + sha4 ◯ commit4 + sha5 ◯ commit5 + `), + }, + { + testName: "showing graph, including rebase commits", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1", Parents: []string{"sha2", "sha3"}, Action: "pick"}, + {Name: "commit2", Sha: "sha2", Parents: []string{"sha3"}, Action: "pick"}, + {Name: "commit3", Sha: "sha3", Parents: []string{"sha4"}}, + {Name: "commit4", Sha: "sha4", Parents: []string{"sha5"}}, + {Name: "commit5", Sha: "sha5", Parents: []string{"sha7"}}, + }, + startIdx: 0, + length: 5, + showGraph: true, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha1 pick commit1 + sha2 pick commit2 + sha3 ◯ commit3 + sha4 ◯ commit4 + sha5 ◯ commit5 + `), + }, + { + testName: "showing graph, including rebase commits, with offset", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1", Parents: []string{"sha2", "sha3"}, Action: "pick"}, + {Name: "commit2", Sha: "sha2", Parents: []string{"sha3"}, Action: "pick"}, + {Name: "commit3", Sha: "sha3", Parents: []string{"sha4"}}, + {Name: "commit4", Sha: "sha4", Parents: []string{"sha5"}}, + {Name: "commit5", Sha: "sha5", Parents: []string{"sha7"}}, + }, + startIdx: 1, + length: 10, + showGraph: true, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha2 pick commit2 + sha3 ◯ commit3 + sha4 ◯ commit4 + sha5 ◯ commit5 + `), + }, + { + testName: "startIdx is passed TODO commits", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1", Parents: []string{"sha2", "sha3"}, Action: "pick"}, + {Name: "commit2", Sha: "sha2", Parents: []string{"sha3"}, Action: "pick"}, + {Name: "commit3", Sha: "sha3", Parents: []string{"sha4"}}, + {Name: "commit4", Sha: "sha4", Parents: []string{"sha5"}}, + {Name: "commit5", Sha: "sha5", Parents: []string{"sha7"}}, + }, + startIdx: 3, + length: 2, + showGraph: true, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha4 ◯ commit4 + sha5 ◯ commit5 + `), + }, + { + testName: "only showing TODO commits", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1", Parents: []string{"sha2", "sha3"}, Action: "pick"}, + {Name: "commit2", Sha: "sha2", Parents: []string{"sha3"}, Action: "pick"}, + {Name: "commit3", Sha: "sha3", Parents: []string{"sha4"}}, + {Name: "commit4", Sha: "sha4", Parents: []string{"sha5"}}, + {Name: "commit5", Sha: "sha5", Parents: []string{"sha7"}}, + }, + startIdx: 0, + length: 2, + showGraph: true, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha1 pick commit1 + sha2 pick commit2 + `), + }, + { + testName: "no TODO commits, towards bottom", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1", Parents: []string{"sha2", "sha3"}}, + {Name: "commit2", Sha: "sha2", Parents: []string{"sha3"}}, + {Name: "commit3", Sha: "sha3", Parents: []string{"sha4"}}, + {Name: "commit4", Sha: "sha4", Parents: []string{"sha5"}}, + {Name: "commit5", Sha: "sha5", Parents: []string{"sha7"}}, + }, + startIdx: 4, + length: 2, + showGraph: true, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha5 ◯ commit5 + `), + }, + { + testName: "only TODO commits except last", + commits: []*models.Commit{ + {Name: "commit1", Sha: "sha1", Parents: []string{"sha2", "sha3"}, Action: "pick"}, + {Name: "commit2", Sha: "sha2", Parents: []string{"sha3"}, Action: "pick"}, + {Name: "commit3", Sha: "sha3", Parents: []string{"sha4"}, Action: "pick"}, + {Name: "commit4", Sha: "sha4", Parents: []string{"sha5"}, Action: "pick"}, + {Name: "commit5", Sha: "sha5", Parents: []string{"sha7"}}, + }, + startIdx: 0, + length: 2, + showGraph: true, + bisectInfo: git_commands.NewNullBisectInfo(), + expected: formatExpected(` + sha1 pick commit1 + sha2 pick commit2 + `), + }, + } + + focusing := false + for _, scenario := range scenarios { + if scenario.focus { + focusing = true + } + } + + for _, s := range scenarios { + s := s + if !focusing || s.focus { + t.Run(s.testName, func(t *testing.T) { + result := GetCommitListDisplayStrings( + s.commits, + s.fullDescription, + s.cherryPickedCommitShaMap, + s.diffName, + s.parseEmoji, + s.selectedCommitSha, + s.startIdx, + s.length, + s.showGraph, + s.bisectInfo, + ) + + renderedResult := utils.RenderDisplayStrings(result) + 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 index 4d00b7e84..116d4fc4b 100644 --- a/pkg/gui/presentation/files.go +++ b/pkg/gui/presentation/files.go @@ -1,13 +1,124 @@ package presentation import ( + "fmt" + "strings" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/patch" + "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/utils" ) -func GetFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, diffName string, submoduleConfigs []*models.SubmoduleConfig, file *models.File) string { +const EXPANDED_ARROW = "▼" +const COLLAPSED_ARROW = "►" + +const INNER_ITEM = "├─ " +const LAST_ITEM = "└─ " +const NESTED = "│ " +const NOTHING = " " + +func RenderFileTree( + fileMgr *filetree.FileTreeViewModel, + diffName string, + submoduleConfigs []*models.SubmoduleConfig, +) []string { + return renderAux(fileMgr.Tree(), fileMgr.CollapsedPaths(), "", -1, func(n filetree.INode, depth int) string { + castN := n.(*filetree.FileNode) + return getFileLine(castN.GetHasUnstagedChanges(), castN.GetHasStagedChanges(), castN.NameAtDepth(depth), diffName, submoduleConfigs, castN.File) + }) +} + +func RenderCommitFileTree( + commitFileMgr *filetree.CommitFileTreeViewModel, + diffName string, + patchManager *patch.PatchManager, +) []string { + return renderAux(commitFileMgr.Tree(), commitFileMgr.CollapsedPaths(), "", -1, func(n filetree.INode, depth int) string { + castN := n.(*filetree.CommitFileNode) + + // 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 + var status patch.PatchStatus + if castN.EveryFile(func(file *models.CommitFile) bool { + return patchManager.GetFileStatus(file.Name, commitFileMgr.GetParent()) == patch.WHOLE + }) { + status = patch.WHOLE + } else if castN.EveryFile(func(file *models.CommitFile) bool { + return patchManager.GetFileStatus(file.Name, commitFileMgr.GetParent()) == patch.UNSELECTED + }) { + status = patch.UNSELECTED + } else { + status = patch.PART + } + + return getCommitFileLine(castN.NameAtDepth(depth), diffName, castN.File, status) + }) +} + +func renderAux( + s filetree.INode, + collapsedPaths filetree.CollapsedPaths, + prefix string, + depth int, + renderLine func(filetree.INode, int) string, +) []string { + if s == nil || s.IsNil() { + return []string{} + } + + isRoot := depth == -1 + + renderLineWithPrefix := func() string { + return prefix + renderLine(s, depth) + } + + if s.IsLeaf() { + if isRoot { + return []string{} + } + return []string{renderLineWithPrefix()} + } + + if collapsedPaths.IsCollapsed(s.GetPath()) { + return []string{fmt.Sprintf("%s %s", renderLineWithPrefix(), COLLAPSED_ARROW)} + } + + arr := []string{} + if !isRoot { + arr = append(arr, fmt.Sprintf("%s %s", renderLineWithPrefix(), EXPANDED_ARROW)) + } + + newPrefix := prefix + if strings.HasSuffix(prefix, LAST_ITEM) { + newPrefix = strings.TrimSuffix(prefix, LAST_ITEM) + NOTHING + } else if strings.HasSuffix(prefix, INNER_ITEM) { + newPrefix = strings.TrimSuffix(prefix, INNER_ITEM) + NESTED + } + + for i, child := range s.GetChildren() { + isLast := i == len(s.GetChildren())-1 + + var childPrefix string + if isRoot { + childPrefix = newPrefix + } else if isLast { + childPrefix = newPrefix + LAST_ITEM + } else { + childPrefix = newPrefix + INNER_ITEM + } + + arr = append(arr, renderAux(child, collapsedPaths, childPrefix, depth+1+s.GetCompressionLevel(), renderLine)...) + } + + return arr +} + +func getFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, diffName string, submoduleConfigs []*models.SubmoduleConfig, file *models.File) string { // potentially inefficient to be instantiating these color // objects with each render partiallyModifiedColor := style.FgYellow @@ -51,3 +162,43 @@ func GetFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, di return output } + +func getCommitFileLine(name string, diffName string, commitFile *models.CommitFile, status patch.PatchStatus) string { + var colour style.TextStyle + if diffName == name { + colour = theme.DiffTerminalColor + } else { + switch status { + case patch.WHOLE: + colour = style.FgGreen + case patch.PART: + colour = style.FgYellow + case patch.UNSELECTED: + colour = theme.DefaultTextColor + } + } + + name = utils.EscapeSpecialChars(name) + if commitFile == nil { + return colour.Sprint(name) + } + + return getColorForChangeStatus(commitFile.ChangeStatus).Sprint(commitFile.ChangeStatus) + " " + colour.Sprint(name) +} + +func getColorForChangeStatus(changeStatus string) style.TextStyle { + switch changeStatus { + case "A": + return style.FgGreen + case "M", "R": + return style.FgYellow + case "D": + return style.FgRed + case "C": + return style.FgCyan + case "T": + return style.FgMagenta + default: + return theme.DefaultTextColor + } +} diff --git a/pkg/gui/presentation/files_test.go b/pkg/gui/presentation/files_test.go new file mode 100644 index 000000000..c40f6247e --- /dev/null +++ b/pkg/gui/presentation/files_test.go @@ -0,0 +1,146 @@ +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/gui/filetree" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/stretchr/testify/assert" + "github.com/xo/terminfo" +) + +func init() { + color.ForceSetColorLevel(terminfo.ColorLevelNone) +} + +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 + expected []string + }{ + { + name: "nil node", + files: nil, + expected: []string{}, + }, + { + name: "leaf node", + files: []*models.File{ + {Name: "test", ShortStatus: " M", HasStagedChanges: true}, + }, + expected: []string{" M test"}, + }, + { + name: "big example", + files: []*models.File{ + {Name: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "dir1/file3", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true}, + {Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true}, + {Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, + }, + expected: toStringSlice( + ` +dir1 ► +dir2 ▼ +├─ dir2 ▼ +│ ├─ M file3 +│ └─ M file4 +└─ M file5 +M file1 +`, + ), + collapsedPaths: []string{"dir1"}, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.name, func(t *testing.T) { + viewModel := filetree.NewFileTreeViewModel(s.files, utils.NewDummyLog(), true) + for _, path := range s.collapsedPaths { + viewModel.ToggleCollapsed(path) + } + result := RenderFileTree(viewModel, "", nil) + assert.EqualValues(t, s.expected, result) + }) + } +} + +func TestRenderCommitFileTree(t *testing.T) { + scenarios := []struct { + name string + root *filetree.FileNode + files []*models.CommitFile + collapsedPaths []string + expected []string + }{ + { + name: "nil node", + files: nil, + expected: []string{}, + }, + { + name: "leaf node", + files: []*models.CommitFile{ + {Name: "test", ChangeStatus: "A"}, + }, + expected: []string{"A test"}, + }, + { + name: "big example", + files: []*models.CommitFile{ + {Name: "dir1/file2", ChangeStatus: "M"}, + {Name: "dir1/file3", ChangeStatus: "A"}, + {Name: "dir2/dir2/file3", ChangeStatus: "D"}, + {Name: "dir2/dir2/file4", ChangeStatus: "M"}, + {Name: "dir2/file5", ChangeStatus: "M"}, + {Name: "file1", ChangeStatus: "M"}, + }, + expected: toStringSlice( + ` +dir1 ► +dir2 ▼ +├─ dir2 ▼ +│ ├─ D file3 +│ └─ M file4 +└─ M file5 +M file1 +`, + ), + collapsedPaths: []string{"dir1"}, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.name, func(t *testing.T) { + viewModel := filetree.NewCommitFileTreeViewModel(s.files, utils.NewDummyLog(), true) + for _, path := range s.collapsedPaths { + viewModel.ToggleCollapsed(path) + } + patchManager := patch.NewPatchManager( + utils.NewDummyLog(), + func(patch string, flags ...string) error { return nil }, + func(from string, to string, reverse bool, filename string, plain bool) (string, error) { + return "", nil + }, + ) + patchManager.Start("from", "to", false, false) + result := RenderCommitFileTree(viewModel, "", patchManager) + assert.EqualValues(t, s.expected, result) + }) + } +} diff --git a/pkg/gui/presentation/graph/cell.go b/pkg/gui/presentation/graph/cell.go index 9ba7d92fb..e970c6dd2 100644 --- a/pkg/gui/presentation/graph/cell.go +++ b/pkg/gui/presentation/graph/cell.go @@ -121,6 +121,7 @@ func (cell *Cell) setLeft(style style.TextStyle) *Cell { return cell } +//nolint:unparam func (cell *Cell) setRight(style style.TextStyle, override bool) *Cell { cell.right = true if cell.rightStyle == nil || override { diff --git a/pkg/gui/pty.go b/pkg/gui/pty.go index 56956c399..b6c3069f2 100644 --- a/pkg/gui/pty.go +++ b/pkg/gui/pty.go @@ -12,18 +12,24 @@ import ( "github.com/jesseduffield/gocui" ) +func (gui *Gui) desiredPtySize() *pty.Winsize { + width, height := gui.Views.Main.Size() + + return &pty.Winsize{Cols: uint16(width), Rows: uint16(height)} +} + func (gui *Gui) onResize() error { if gui.State.Ptmx == nil { return nil } - width, height := gui.Views.Main.Size() - if err := pty.Setsize(gui.State.Ptmx, &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}); err != nil { + // 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 + if err := pty.Setsize(gui.State.Ptmx, gui.desiredPtySize()); err != nil { return err } - // TODO: handle resizing properly - return nil } @@ -35,7 +41,7 @@ func (gui *Gui) onResize() error { // command. func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error { width, _ := gui.Views.Main.Size() - pager := gui.GitCommand.GetPager(width) + pager := gui.Git.Config.GetPager(width) if pager == "" { // if we're not using a custom pager we don't need to use a pty @@ -52,7 +58,7 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error manager := gui.getManager(view) start := func() (*exec.Cmd, io.Reader) { - ptmx, err := pty.Start(cmd) + ptmx, err := pty.StartWithSize(cmd, gui.desiredPtySize()) if err != nil { gui.Log.Error(err) } @@ -67,10 +73,6 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error gui.State.Ptmx = nil } - if err := gui.onResize(); err != nil { - return err - } - if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, onClose), cmdStr); err != nil { return err } diff --git a/pkg/gui/pull_request_menu_panel.go b/pkg/gui/pull_request_menu_panel.go index dcc78f893..cd39b0c6a 100644 --- a/pkg/gui/pull_request_menu_panel.go +++ b/pkg/gui/pull_request_menu_panel.go @@ -4,9 +4,8 @@ import ( "fmt" "strconv" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/hosting_service" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) func (gui *Gui) createOrOpenPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error { @@ -71,12 +70,25 @@ func (gui *Gui) createOrOpenPullRequestMenu(selectedBranch *models.Branch, check } func (gui *Gui) createPullRequest(from string, to string) error { - pullRequest := commands.NewPullRequest(gui.GitCommand) - url, err := pullRequest.Create(from, to) + hostingServiceMgr := gui.getHostingServiceMgr() + url, err := hostingServiceMgr.GetPullRequestURL(from, to) if err != nil { return gui.surfaceError(err) } - gui.OnRunCommand(oscommands.NewCmdLogEntry(fmt.Sprintf(gui.Tr.CreatingPullRequestAtUrl, url), gui.Tr.CreateOrShowPullRequest, false)) + + // gui.OnRunCommand(oscommands.NewCmdLogEntry(fmt.Sprintf(gui.Tr.CreatingPullRequestAtUrl, url), gui.Tr.CreateOrShowPullRequest, false)) + + gui.logAction(gui.Tr.Actions.OpenPullRequest) + + if err := gui.OSCommand.OpenLink(url); err != nil { + return gui.surfaceError(err) + } return nil } + +func (gui *Gui) getHostingServiceMgr() *hosting_service.HostingServiceMgr { + remoteUrl := gui.Git.Config.GetRemoteURL() + configServices := gui.UserConfig.Services + return hosting_service.NewHostingServiceMgr(gui.Log, gui.Tr, remoteUrl, configServices) +} diff --git a/pkg/gui/quitting.go b/pkg/gui/quitting.go index 24b92efc2..c3fd2ce2f 100644 --- a/pkg/gui/quitting.go +++ b/pkg/gui/quitting.go @@ -60,7 +60,7 @@ func (gui *Gui) handleTopLevelReturn() error { return gui.dispatchSwitchToRepo(path, true) } - if gui.Config.GetUserConfig().QuitOnTopLevelReturn { + if gui.UserConfig.QuitOnTopLevelReturn { return gui.handleQuit() } @@ -72,7 +72,7 @@ func (gui *Gui) quit() error { return gui.createUpdateQuitConfirmation() } - if gui.Config.GetUserConfig().ConfirmOnQuit { + if gui.UserConfig.ConfirmOnQuit { return gui.ask(askOpts{ title: "", prompt: gui.Tr.ConfirmQuit, diff --git a/pkg/gui/rebase_options_panel.go b/pkg/gui/rebase_options_panel.go index 280022000..a9e7d9317 100644 --- a/pkg/gui/rebase_options_panel.go +++ b/pkg/gui/rebase_options_panel.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" ) type RebaseOption string @@ -18,7 +18,7 @@ const ( func (gui *Gui) handleCreateRebaseOptionsMenu() error { options := []string{REBASE_OPTION_CONTINUE, REBASE_OPTION_ABORT} - if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_REBASING { + if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING { options = append(options, REBASE_OPTION_SKIP) } @@ -35,7 +35,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error { } var title string - if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_MERGING { + if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING { title = gui.Tr.MergeOptionsTitle } else { title = gui.Tr.RebaseOptionsTitle @@ -45,26 +45,34 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error { } func (gui *Gui) genericMergeCommand(command string) error { - status := gui.GitCommand.WorkingTreeState() + status := gui.Git.Status.WorkingTreeState() - if status != commands.REBASE_MODE_MERGING && status != commands.REBASE_MODE_REBASING { + if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING { return gui.createErrorPanel(gui.Tr.NotMergingOrRebasing) } - gitCommand := gui.GitCommand.WithSpan(fmt.Sprintf("Merge/Rebase: %s", command)) + gui.logAction(fmt.Sprintf("Merge/Rebase: %s", command)) + + commandType := "" + switch status { + case enums.REBASE_MODE_MERGING: + commandType = "merge" + case enums.REBASE_MODE_REBASING: + commandType = "rebase" + default: + // shouldn't be possible to land here + } - commandType := strings.Replace(status, "ing", "e", 1) // 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 - if status == commands.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.Config.GetUserConfig().Git.Merging.ManualCommit { - sub := gitCommand.OSCommand.PrepareSubProcess("git", commandType, fmt.Sprintf("--%s", command)) - if sub != nil { - return gui.runSubprocessWithSuspenseAndRefresh(sub) - } - return nil + if status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.UserConfig.Git.Merging.ManualCommit { + // TODO: see if we should be calling more of the code from gui.Git.Rebase.GenericMergeOrRebaseAction + return gui.runSubprocessWithSuspenseAndRefresh( + gui.Git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command), + ) } - result := gitCommand.GenericMergeOrRebaseAction(commandType, command) + result := gui.Git.Rebase.GenericMergeOrRebaseAction(commandType, command) if err := gui.handleGenericMergeCommandResult(result); err != nil { return err } @@ -135,11 +143,11 @@ func (gui *Gui) abortMergeOrRebaseWithConfirm() error { } func (gui *Gui) workingTreeStateNoun() string { - workingTreeState := gui.GitCommand.WorkingTreeState() + workingTreeState := gui.Git.Status.WorkingTreeState() switch workingTreeState { - case commands.REBASE_MODE_NORMAL: + case enums.REBASE_MODE_NONE: return "" - case commands.REBASE_MODE_MERGING: + case enums.REBASE_MODE_MERGING: return "merge" default: return "rebase" diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go index 5460a2155..7bf6b068c 100644 --- a/pkg/gui/recent_repos_panel.go +++ b/pkg/gui/recent_repos_panel.go @@ -4,7 +4,6 @@ import ( "os" "path/filepath" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/env" @@ -38,10 +37,8 @@ func (gui *Gui) handleCreateRecentReposMenu() error { } func (gui *Gui) handleShowAllBranchLogs() error { - cmd := gui.OSCommand.ExecutableFromString( - gui.Config.GetUserConfig().Git.AllBranchesLogCmd, - ) - task := NewRunPtyTask(cmd) + cmdObj := gui.Git.Branch.AllBranchesLogCmdObj() + task := NewRunPtyTask(cmdObj.GetCmd()) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ @@ -73,25 +70,21 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error { return err } - newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config, git_config.NewStdCachedGitConfig(gui.Log)) + newGitCommand, err := commands.NewGitCommand(gui.Common, gui.OSCommand, git_config.NewStdCachedGitConfig(gui.Log)) if err != nil { return err } - gui.GitCommand = newGitCommand + gui.Git = newGitCommand - gui.g.Update(func(*gocui.Gui) error { - // these two mutexes are used by our background goroutines (triggered via `gui.goEvery`. We don't want to - // switch to a repo while one of these goroutines is in the process of updating something - gui.Mutexes.FetchMutex.Lock() - defer gui.Mutexes.FetchMutex.Unlock() + // these two mutexes are used by our background goroutines (triggered via `gui.goEvery`. We don't want to + // switch to a repo while one of these goroutines is in the process of updating something + gui.Mutexes.FetchMutex.Lock() + defer gui.Mutexes.FetchMutex.Unlock() - gui.Mutexes.RefreshingFilesMutex.Lock() - defer gui.Mutexes.RefreshingFilesMutex.Unlock() + gui.Mutexes.RefreshingFilesMutex.Lock() + defer gui.Mutexes.RefreshingFilesMutex.Unlock() - gui.resetState("", reuse) - - return nil - }) + gui.resetState("", reuse) return nil } @@ -99,7 +92,7 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error { // 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.GitCommand.IsBareRepo() { + 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 @@ -113,7 +106,7 @@ func (gui *Gui) updateRecentRepoList() error { return err } known, recentRepos := newRecentReposList(recentRepos, currentRepo) - gui.Config.SetIsNewRepo(known) + gui.IsNewRepo = known gui.Config.GetAppState().RecentRepos = recentRepos return gui.Config.SaveAppState() } diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index e00d7c223..af8e8092c 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -16,17 +16,15 @@ func (gui *Gui) getSelectedReflogCommit() *models.Commit { return reflogComits[selectedLine] } -func (gui *Gui) handleReflogCommitSelect() error { +func (gui *Gui) reflogCommitsRenderToMain() error { commit := gui.getSelectedReflogCommit() var task updateTask if commit == nil { task = NewRenderStringTask("No reflog history") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), - ) + cmdObj := gui.Git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath()) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ @@ -54,7 +52,8 @@ func (gui *Gui) refreshReflogCommits() error { } refresh := func(stateCommits *[]*models.Commit, filterPath string) error { - commits, onlyObtainedNewReflogCommits, err := gui.GitCommand.GetReflogCommits(lastReflogCommit, filterPath) + commits, onlyObtainedNewReflogCommits, err := gui.Git.Loaders.ReflogCommits. + GetReflogCommits(lastReflogCommit, filterPath) if err != nil { return gui.surfaceError(err) } @@ -92,7 +91,8 @@ func (gui *Gui) handleCheckoutReflogCommit() error { title: gui.Tr.LcCheckoutCommit, prompt: gui.Tr.SureCheckoutThisCommit, handleConfirm: func() error { - return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{span: gui.Tr.Spans.CheckoutReflogCommit}) + gui.logAction(gui.Tr.Actions.CheckoutReflogCommit) + return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{}) }, }) if err != nil { diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go index a53a7470c..29ab59187 100644 --- a/pkg/gui/remote_branches_panel.go +++ b/pkg/gui/remote_branches_panel.go @@ -18,16 +18,14 @@ func (gui *Gui) getSelectedRemoteBranch() *models.RemoteBranch { return gui.State.RemoteBranches[selectedLine] } -func (gui *Gui) handleRemoteBranchSelect() error { +func (gui *Gui) remoteBranchesRenderToMain() error { var task updateTask remoteBranch := gui.getSelectedRemoteBranch() if remoteBranch == nil { task = NewRenderStringTask("No branches for this remote") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.GetBranchGraphCmdStr(remoteBranch.FullName()), - ) - task = NewRunCommandTask(cmd) + cmdObj := gui.Git.Branch.GetGraphCmdObj(remoteBranch.FullName()) + task = NewRunCommandTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ @@ -59,7 +57,8 @@ func (gui *Gui) handleDeleteRemoteBranch() error { prompt: message, handleConfirm: func() error { return gui.WithWaitingStatus(gui.Tr.DeletingStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.DeleteRemoteBranch).DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name, gui.promptUserForCredential) + gui.logAction(gui.Tr.Actions.DeleteRemoteBranch) + err := gui.Git.Remote.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name) gui.handleCredentialsPopup(err) return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}}) @@ -89,7 +88,8 @@ func (gui *Gui) handleSetBranchUpstream() error { title: gui.Tr.SetUpstreamTitle, prompt: message, handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.SetBranchUpstream).SetBranchUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil { + gui.logAction(gui.Tr.Actions.SetBranchUpstream) + if err := gui.Git.Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil { return gui.surfaceError(err) } diff --git a/pkg/gui/remotes_panel.go b/pkg/gui/remotes_panel.go index 29e723e38..c74653a27 100644 --- a/pkg/gui/remotes_panel.go +++ b/pkg/gui/remotes_panel.go @@ -20,7 +20,7 @@ func (gui *Gui) getSelectedRemote() *models.Remote { return gui.State.Remotes[selectedLine] } -func (gui *Gui) handleRemoteSelect() error { +func (gui *Gui) remotesRenderToMain() error { var task updateTask remote := gui.getSelectedRemote() if remote == nil { @@ -40,7 +40,7 @@ func (gui *Gui) handleRemoteSelect() error { func (gui *Gui) refreshRemotes() error { prevSelectedRemote := gui.getSelectedRemote() - remotes, err := gui.GitCommand.GetRemotes() + remotes, err := gui.Git.Loaders.Remotes.GetRemotes() if err != nil { return gui.surfaceError(err) } @@ -85,7 +85,8 @@ func (gui *Gui) handleAddRemote() error { return gui.prompt(promptOpts{ title: gui.Tr.LcNewRemoteUrl, handleConfirm: func(remoteUrl string) error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.AddRemote).AddRemote(remoteName, remoteUrl); err != nil { + gui.logAction(gui.Tr.Actions.AddRemote) + if err := gui.Git.Remote.AddRemote(remoteName, remoteUrl); err != nil { return err } return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{REMOTES}}) @@ -106,7 +107,8 @@ func (gui *Gui) handleRemoveRemote() error { title: gui.Tr.LcRemoveRemote, prompt: gui.Tr.LcRemoveRemotePrompt + " '" + remote.Name + "'?", handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.RemoveRemote).RemoveRemote(remote.Name); err != nil { + gui.logAction(gui.Tr.Actions.RemoveRemote) + if err := gui.Git.Remote.RemoveRemote(remote.Name); err != nil { return gui.surfaceError(err) } @@ -128,14 +130,13 @@ func (gui *Gui) handleEditRemote() error { }, ) - gitCommand := gui.GitCommand.WithSpan(gui.Tr.Spans.UpdateRemote) - return gui.prompt(promptOpts{ title: editNameMessage, initialContent: remote.Name, handleConfirm: func(updatedRemoteName string) error { if updatedRemoteName != remote.Name { - if err := gitCommand.RenameRemote(remote.Name, updatedRemoteName); err != nil { + gui.logAction(gui.Tr.Actions.UpdateRemote) + if err := gui.Git.Remote.RenameRemote(remote.Name, updatedRemoteName); err != nil { return gui.surfaceError(err) } } @@ -157,7 +158,8 @@ func (gui *Gui) handleEditRemote() error { title: editUrlMessage, initialContent: url, handleConfirm: func(updatedRemoteUrl string) error { - if err := gitCommand.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil { + gui.logAction(gui.Tr.Actions.UpdateRemote) + if err := gui.Git.Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil { return gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}}) @@ -177,7 +179,7 @@ func (gui *Gui) handleFetchRemote() error { gui.Mutexes.FetchMutex.Lock() defer gui.Mutexes.FetchMutex.Unlock() - err := gui.GitCommand.FetchRemote(remote.Name, gui.promptUserForCredential) + err := gui.Git.Sync.FetchRemote(remote.Name) gui.handleCredentialsPopup(err) return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}}) diff --git a/pkg/gui/reset_menu_panel.go b/pkg/gui/reset_menu_panel.go index fc15d36de..586987778 100644 --- a/pkg/gui/reset_menu_panel.go +++ b/pkg/gui/reset_menu_panel.go @@ -3,12 +3,11 @@ package gui import ( "fmt" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/gui/style" ) -func (gui *Gui) resetToRef(ref string, strength string, span string, options oscommands.RunCommandOptions) error { - if err := gui.GitCommand.WithSpan(span).ResetToCommit(ref, strength, options); err != nil { +func (gui *Gui) resetToRef(ref string, strength string, envVars []string) error { + if err := gui.Git.Commit.ResetToCommit(ref, strength, envVars); err != nil { return gui.surfaceError(err) } @@ -39,7 +38,8 @@ func (gui *Gui) createResetMenu(ref string) error { style.FgRed.Sprintf("reset --%s %s", strength, ref), }, onPress: func() error { - return gui.resetToRef(ref, strength, "Reset", oscommands.RunCommandOptions{}) + gui.logAction("Reset") + return gui.resetToRef(ref, strength, []string{}) }, } } diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go index 9d7c1ec84..dd7697363 100644 --- a/pkg/gui/searching.go +++ b/pkg/gui/searching.go @@ -43,11 +43,11 @@ func (gui *Gui) handleSearch() error { } func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error { - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding return func(y int, index int, total int) error { if total == 0 { - gui.renderString( + return gui.renderString( gui.Views.Search, fmt.Sprintf( "no matches for '%s' %s", @@ -55,9 +55,8 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in theme.OptionsFgColor.Sprintf("%s: exit search mode", gui.getKeyDisplay(keybindingConfig.Universal.Return)), ), ) - return nil } - gui.renderString( + _ = gui.renderString( gui.Views.Search, fmt.Sprintf( "matches for '%s' (%d of %d) %s", diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go index 1fb9e8989..ecb208dcc 100644 --- a/pkg/gui/staging_panel.go +++ b/pkg/gui/staging_panel.go @@ -34,8 +34,8 @@ func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx } // note for custom diffs, we'll need to send a flag here saying not to use the custom diff - diff := gui.GitCommand.WorktreeFileDiff(file, true, secondaryFocused, false) - secondaryDiff := gui.GitCommand.WorktreeFileDiff(file, true, !secondaryFocused, false) + diff := gui.Git.WorkingTree.WorktreeFileDiff(file, true, secondaryFocused, false) + secondaryDiff := gui.Git.WorkingTree.WorktreeFileDiff(file, true, !secondaryFocused, false) // if we have e.g. a deleted file with nothing else to the diff will have only // 4-5 lines in which case we'll swap panels @@ -74,6 +74,17 @@ func (gui *Gui) handleRefreshStagingPanel(forceSecondaryFocused bool, selectedLi return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx) } +func (gui *Gui) onStagingFocus(forceSecondaryFocused bool, selectedLineIdx int) error { + gui.Mutexes.LineByLinePanelMutex.Lock() + defer gui.Mutexes.LineByLinePanelMutex.Unlock() + + if gui.State.Panels.LineByLine == nil || selectedLineIdx != -1 { + return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx) + } + + return nil +} + func (gui *Gui) handleTogglePanel() error { return gui.withLBLActiveCheck(func(state *LblPanelState) error { state.SecondaryFocused = !state.SecondaryFocused @@ -100,23 +111,15 @@ func (gui *Gui) handleResetSelection() error { return gui.applySelection(true, state) } - if !gui.Config.GetUserConfig().Gui.SkipUnstageLineWarning { + if !gui.UserConfig.Gui.SkipUnstageLineWarning { return gui.ask(askOpts{ - title: gui.Tr.UnstageLinesTitle, - prompt: gui.Tr.UnstageLinesPrompt, - handlersManageFocus: true, + title: gui.Tr.UnstageLinesTitle, + prompt: gui.Tr.UnstageLinesPrompt, handleConfirm: func() error { return gui.withLBLActiveCheck(func(state *LblPanelState) error { - if err := gui.pushContext(gui.State.Contexts.Staging); err != nil { - return err - } - return gui.applySelection(true, state) }) }, - handleClose: func() error { - return gui.pushContext(gui.State.Contexts.Staging) - }, }) } else { return gui.applySelection(true, state) @@ -143,7 +146,8 @@ func (gui *Gui) applySelection(reverse bool, state *LblPanelState) error { if !reverse || state.SecondaryFocused { applyFlags = append(applyFlags, "cached") } - err := gui.GitCommand.WithSpan(gui.Tr.Spans.ApplyPatch).ApplyPatch(patch, applyFlags...) + gui.logAction(gui.Tr.Actions.ApplyPatch) + err := gui.Git.WorkingTree.ApplyPatch(patch, applyFlags...) if err != nil { return gui.surfaceError(err) } diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index d72b32451..2b9bae445 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -2,7 +2,6 @@ package gui import ( "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/utils" ) // list panel functions @@ -16,16 +15,13 @@ func (gui *Gui) getSelectedStashEntry() *models.StashEntry { return gui.State.StashEntries[selectedLine] } -func (gui *Gui) handleStashEntrySelect() error { +func (gui *Gui) stashRenderToMain() error { var task updateTask stashEntry := gui.getSelectedStashEntry() if stashEntry == nil { task = NewRenderStringTask(gui.Tr.NoStashEntries) } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index), - ) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(gui.Git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ @@ -37,18 +33,30 @@ func (gui *Gui) handleStashEntrySelect() error { } func (gui *Gui) refreshStashEntries() error { - gui.State.StashEntries = gui.GitCommand.GetStashEntries(gui.State.Modes.Filtering.GetPath()) + gui.State.StashEntries = gui.Git.Loaders.Stash. + GetStashEntries(gui.State.Modes.Filtering.GetPath()) - return gui.State.Contexts.Stash.HandleRender() + return gui.postRefreshUpdate(gui.State.Contexts.Stash) } // specific functions func (gui *Gui) handleStashApply() error { - skipStashWarning := gui.Config.GetUserConfig().Gui.SkipStashWarning + stashEntry := gui.getSelectedStashEntry() + if stashEntry == nil { + return nil + } + + skipStashWarning := gui.UserConfig.Gui.SkipStashWarning apply := func() error { - return gui.stashDo("apply") + gui.logAction(gui.Tr.Actions.Stash) + err := gui.Git.Stash.Apply(stashEntry.Index) + _ = gui.postStashRefresh() + if err != nil { + return gui.surfaceError(err) + } + return nil } if skipStashWarning { @@ -65,10 +73,21 @@ func (gui *Gui) handleStashApply() error { } func (gui *Gui) handleStashPop() error { - skipStashWarning := gui.Config.GetUserConfig().Gui.SkipStashWarning + stashEntry := gui.getSelectedStashEntry() + if stashEntry == nil { + return nil + } + + skipStashWarning := gui.UserConfig.Gui.SkipStashWarning pop := func() error { - return gui.stashDo("pop") + gui.logAction(gui.Tr.Actions.Stash) + err := gui.Git.Stash.Pop(stashEntry.Index) + _ = gui.postStashRefresh() + if err != nil { + return gui.surfaceError(err) + } + return nil } if skipStashWarning { @@ -85,30 +104,27 @@ func (gui *Gui) handleStashPop() error { } func (gui *Gui) handleStashDrop() error { + stashEntry := gui.getSelectedStashEntry() + if stashEntry == nil { + return nil + } + return gui.ask(askOpts{ title: gui.Tr.StashDrop, prompt: gui.Tr.SureDropStashEntry, handleConfirm: func() error { - return gui.stashDo("drop") + gui.logAction(gui.Tr.Actions.Stash) + err := gui.Git.Stash.Drop(stashEntry.Index) + _ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH}}) + if err != nil { + return gui.surfaceError(err) + } + return nil }, }) } -func (gui *Gui) stashDo(method string) error { - stashEntry := gui.getSelectedStashEntry() - if stashEntry == nil { - errorMessage := utils.ResolvePlaceholderString( - gui.Tr.NoStashTo, - map[string]string{ - "method": method, - }, - ) - - return gui.createErrorPanel(errorMessage) - } - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.Stash).StashDo(stashEntry.Index, method); err != nil { - return gui.surfaceError(err) - } +func (gui *Gui) postStashRefresh() error { return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH, FILES}}) } @@ -120,10 +136,12 @@ func (gui *Gui) handleStashSave(stashFunc func(message string) error) error { return gui.prompt(promptOpts{ title: gui.Tr.StashChanges, handleConfirm: func(stashComment string) error { - if err := stashFunc(stashComment); err != nil { + err := stashFunc(stashComment) + _ = gui.postStashRefresh() + if err != nil { return gui.surfaceError(err) } - return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH, FILES}}) + return nil }, }) } diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 0adc4d383..444c32da1 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/constants" "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/style" @@ -28,8 +28,9 @@ func (gui *Gui) refreshStatus() { status += presentation.ColoredBranchStatus(currentBranch) + " " } - if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL { - status += style.FgYellow.Sprintf("(%s) ", gui.GitCommand.WorkingTreeState()) + workingTreeState := gui.Git.Status.WorkingTreeState() + if workingTreeState != enums.REBASE_MODE_NONE { + status += style.FgYellow.Sprintf("(%s) ", formatWorkingTreeState(workingTreeState)) } name := presentation.GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name) @@ -71,9 +72,10 @@ func (gui *Gui) handleStatusClick() error { cx, _ := gui.Views.Status.Cursor() upstreamStatus := presentation.BranchStatus(currentBranch) repoName := utils.GetCurrentRepoName() - switch gui.GitCommand.WorkingTreeState() { - case commands.REBASE_MODE_REBASING, commands.REBASE_MODE_MERGING: - workingTreeStatus := fmt.Sprintf("(%s)", gui.GitCommand.WorkingTreeState()) + workingTreeState := gui.Git.Status.WorkingTreeState() + switch workingTreeState { + case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING: + workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState)) if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) { return gui.handleCreateRebaseOptionsMenu() } @@ -86,10 +88,21 @@ func (gui *Gui) handleStatusClick() error { } } - return gui.handleStatusSelect() + return nil } -func (gui *Gui) handleStatusSelect() error { +func formatWorkingTreeState(rebaseMode enums.RebaseMode) string { + switch rebaseMode { + case enums.REBASE_MODE_REBASING: + return "rebasing" + case enums.REBASE_MODE_MERGING: + return "merging" + default: + return "none" + } +} + +func (gui *Gui) statusRenderToMain() error { // TODO: move into some abstraction (status is currently not a listViewContext where a lot of this code lives) if gui.popupPanelFocused() { return nil @@ -98,13 +111,13 @@ func (gui *Gui) handleStatusSelect() error { dashboardString := strings.Join( []string{ lazygitTitle(), - "Copyright (c) 2018 Jesse Duffield", + "Copyright 2022 Jesse Duffield", fmt.Sprintf("Keybindings: %s", constants.Links.Docs.Keybindings), fmt.Sprintf("Config Options: %s", constants.Links.Docs.Config), 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 (github is matching all donations for 12 months): %s", constants.Links.Donate), // caffeine ain't free + style.FgMagenta.Sprintf("Become a sponsor: %s", constants.Links.Donate), // caffeine ain't free }, "\n\n") return gui.refreshMainViews(refreshMainOpts{ @@ -156,15 +169,3 @@ func lazygitTitle() string { __/ | __/ | |___/ |___/ ` } - -func (gui *Gui) workingTreeState() string { - rebaseMode, _ := gui.GitCommand.RebaseMode() - if rebaseMode != "" { - return commands.REBASE_MODE_REBASING - } - merging, _ := gui.GitCommand.IsInMergeState() - if merging { - return commands.REBASE_MODE_MERGING - } - return commands.REBASE_MODE_NORMAL -} diff --git a/pkg/gui/sub_commits_panel.go b/pkg/gui/sub_commits_panel.go index f1c27388a..d023d261f 100644 --- a/pkg/gui/sub_commits_panel.go +++ b/pkg/gui/sub_commits_panel.go @@ -1,7 +1,7 @@ package gui import ( - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" ) @@ -17,17 +17,15 @@ func (gui *Gui) getSelectedSubCommit() *models.Commit { return commits[selectedLine] } -func (gui *Gui) handleSubCommitSelect() error { +func (gui *Gui) subCommitsRenderToMain() error { commit := gui.getSelectedSubCommit() var task updateTask if commit == nil { task = NewRenderStringTask("No commits") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), - ) + cmdObj := gui.Git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath()) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ @@ -48,7 +46,8 @@ func (gui *Gui) handleCheckoutSubCommit() error { title: gui.Tr.LcCheckoutCommit, prompt: gui.Tr.SureCheckoutThisCommit, handleConfirm: func() error { - return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{span: gui.Tr.Spans.CheckoutCommit}) + gui.logAction(gui.Tr.Actions.CheckoutCommit) + return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{}) }, }) if err != nil { @@ -77,10 +76,8 @@ func (gui *Gui) handleViewSubCommitFiles() error { func (gui *Gui) switchToSubCommitsContext(refName string) error { // need to populate my sub commits - builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) - - commits, err := builder.GetCommits( - commands.GetCommitsOptions{ + commits, err := gui.Git.Loaders.Commits.GetCommits( + loaders.GetCommitsOptions{ Limit: gui.State.Panels.Commits.LimitCommits, FilterPath: gui.State.Modes.Filtering.GetPath(), IncludeRebaseCommits: false, diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go index ff1cb0aeb..be0574356 100644 --- a/pkg/gui/submodules_panel.go +++ b/pkg/gui/submodules_panel.go @@ -19,7 +19,7 @@ func (gui *Gui) getSelectedSubmodule() *models.SubmoduleConfig { return gui.State.Submodules[selectedLine] } -func (gui *Gui) handleSubmoduleSelect() error { +func (gui *Gui) submodulesRenderToMain() error { var task updateTask submodule := gui.getSelectedSubmodule() if submodule == nil { @@ -36,9 +36,8 @@ func (gui *Gui) handleSubmoduleSelect() error { if file == nil { task = NewRenderStringTask(prefix) } else { - cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView) - cmd := gui.OSCommand.ExecutableFromString(cmdStr) - task = NewRunCommandTaskWithPrefix(cmd, prefix) + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView) + task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix) } } @@ -51,7 +50,7 @@ func (gui *Gui) handleSubmoduleSelect() error { } func (gui *Gui) refreshStateSubmoduleConfigs() error { - configs, err := gui.GitCommand.GetSubmoduleConfigs() + configs, err := gui.Git.Submodule.GetConfigs() if err != nil { return err } @@ -80,7 +79,8 @@ func (gui *Gui) removeSubmodule(submodule *models.SubmoduleConfig) error { title: gui.Tr.RemoveSubmodule, prompt: fmt.Sprintf(gui.Tr.RemoveSubmodulePrompt, submodule.Name), handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.RemoveSubmodule).SubmoduleDelete(submodule); err != nil { + gui.logAction(gui.Tr.Actions.RemoveSubmodule) + if err := gui.Git.Submodule.Delete(submodule); err != nil { return gui.surfaceError(err) } @@ -96,7 +96,7 @@ func (gui *Gui) handleResetSubmodule(submodule *models.SubmoduleConfig) error { } func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File { - for _, file := range gui.State.FileManager.GetAllFiles() { + for _, file := range gui.State.FileTreeViewModel.GetAllFiles() { if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) { return file } @@ -106,19 +106,19 @@ func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File } func (gui *Gui) resetSubmodule(submodule *models.SubmoduleConfig) error { - gitCommand := gui.GitCommand.WithSpan(gui.Tr.Spans.ResetSubmodule) + gui.logAction(gui.Tr.Actions.ResetSubmodule) file := gui.fileForSubmodule(submodule) if file != nil { - if err := gitCommand.UnStageFile(file.Names(), file.Tracked); err != nil { + if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil { return gui.surfaceError(err) } } - if err := gitCommand.SubmoduleStash(submodule); err != nil { + if err := gui.Git.Submodule.Stash(submodule); err != nil { return gui.surfaceError(err) } - if err := gitCommand.SubmoduleReset(submodule); err != nil { + if err := gui.Git.Submodule.Reset(submodule); err != nil { return gui.surfaceError(err) } @@ -141,7 +141,8 @@ func (gui *Gui) handleAddSubmodule() error { initialContent: submoduleName, handleConfirm: func(submodulePath string) error { return gui.WithWaitingStatus(gui.Tr.LcAddingSubmoduleStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.AddSubmodule).SubmoduleAdd(submoduleName, submodulePath, submoduleUrl) + gui.logAction(gui.Tr.Actions.AddSubmodule) + err := gui.Git.Submodule.Add(submoduleName, submodulePath, submoduleUrl) gui.handleCredentialsPopup(err) return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}}) @@ -161,7 +162,8 @@ func (gui *Gui) handleEditSubmoduleUrl(submodule *models.SubmoduleConfig) error initialContent: submodule.Url, handleConfirm: func(newUrl string) error { return gui.WithWaitingStatus(gui.Tr.LcUpdatingSubmoduleUrlStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.UpdateSubmoduleUrl).SubmoduleUpdateUrl(submodule.Name, submodule.Path, newUrl) + gui.logAction(gui.Tr.Actions.UpdateSubmoduleUrl) + err := gui.Git.Submodule.UpdateUrl(submodule.Name, submodule.Path, newUrl) gui.handleCredentialsPopup(err) return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}}) @@ -172,7 +174,8 @@ func (gui *Gui) handleEditSubmoduleUrl(submodule *models.SubmoduleConfig) error func (gui *Gui) handleSubmoduleInit(submodule *models.SubmoduleConfig) error { return gui.WithWaitingStatus(gui.Tr.LcInitializingSubmoduleStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.InitialiseSubmodule).SubmoduleInit(submodule.Path) + gui.logAction(gui.Tr.Actions.InitialiseSubmodule) + err := gui.Git.Submodule.Init(submodule.Path) gui.handleCredentialsPopup(err) return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}}) @@ -190,32 +193,15 @@ func (gui *Gui) forSubmodule(callback func(*models.SubmoduleConfig) error) func( } } -func (gui *Gui) handleResetRemoveSubmodule(submodule *models.SubmoduleConfig) error { - menuItems := []*menuItem{ - { - displayString: gui.Tr.LcSubmoduleStashAndReset, - onPress: func() error { - return gui.resetSubmodule(submodule) - }, - }, - { - displayString: gui.Tr.LcRemoveSubmodule, - onPress: func() error { - return gui.removeSubmodule(submodule) - }, - }, - } - - return gui.createMenu(submodule.Name, menuItems, createMenuOptions{showCancel: true}) -} - func (gui *Gui) handleBulkSubmoduleActionsMenu() error { menuItems := []*menuItem{ { - displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.GitCommand.SubmoduleBulkInitCmdStr())}, + displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.Git.Submodule.BulkInitCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkInitialiseSubmodules).RunCommand(gui.GitCommand.SubmoduleBulkInitCmdStr()); err != nil { + gui.logAction(gui.Tr.Actions.BulkInitialiseSubmodules) + err := gui.Git.Submodule.BulkInitCmdObj().Run() + if err != nil { return gui.surfaceError(err) } @@ -224,10 +210,11 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error { }, }, { - displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.GitCommand.SubmoduleBulkUpdateCmdStr())}, + displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.Git.Submodule.BulkUpdateCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkUpdateSubmodules).RunCommand(gui.GitCommand.SubmoduleBulkUpdateCmdStr()); err != nil { + gui.logAction(gui.Tr.Actions.BulkUpdateSubmodules) + if err := gui.Git.Submodule.BulkUpdateCmdObj().Run(); err != nil { return gui.surfaceError(err) } @@ -236,10 +223,11 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error { }, }, { - displayStrings: []string{gui.Tr.LcSubmoduleStashAndReset, style.FgRed.Sprintf("git stash in each submodule && %s", gui.GitCommand.SubmoduleForceBulkUpdateCmdStr())}, + displayStrings: []string{gui.Tr.LcSubmoduleStashAndReset, style.FgRed.Sprintf("git stash in each submodule && %s", gui.Git.Submodule.ForceBulkUpdateCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.BulkStashAndResetSubmodules).ResetSubmodules(gui.State.Submodules); err != nil { + gui.logAction(gui.Tr.Actions.BulkStashAndResetSubmodules) + if err := gui.Git.Submodule.ResetSubmodules(gui.State.Submodules); err != nil { return gui.surfaceError(err) } @@ -248,10 +236,11 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error { }, }, { - displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.GitCommand.SubmoduleBulkDeinitCmdStr())}, + displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.Git.Submodule.BulkDeinitCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkDeinitialiseSubmodules).RunCommand(gui.GitCommand.SubmoduleBulkDeinitCmdStr()); err != nil { + gui.logAction(gui.Tr.Actions.BulkDeinitialiseSubmodules) + if err := gui.Git.Submodule.BulkDeinitCmdObj().Run(); err != nil { return gui.surfaceError(err) } @@ -266,7 +255,8 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error { func (gui *Gui) handleUpdateSubmodule(submodule *models.SubmoduleConfig) error { return gui.WithWaitingStatus(gui.Tr.LcUpdatingSubmoduleStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.UpdateSubmodule).SubmoduleUpdate(submodule.Path) + gui.logAction(gui.Tr.Actions.UpdateSubmodule) + err := gui.Git.Submodule.Update(submodule.Path) gui.handleCredentialsPopup(err) return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}}) diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go index 2ce52d54a..efad33fbf 100644 --- a/pkg/gui/tags_panel.go +++ b/pkg/gui/tags_panel.go @@ -15,43 +15,18 @@ func (gui *Gui) getSelectedTag() *models.Tag { } func (gui *Gui) handleCreateTag() error { - return gui.prompt(promptOpts{ - title: gui.Tr.CreateTagTitle, - handleConfirm: func(tagName string) error { - // leaving commit SHA blank so that we're just creating the tag for the current commit - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.CreateLightweightTag).CreateLightweightTag(tagName, ""); err != nil { - return gui.surfaceError(err) - } - return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{COMMITS, TAGS}, then: func() { - // find the index of the tag and set that as the currently selected line - for i, tag := range gui.State.Tags { - if tag.Name == tagName { - gui.State.Panels.Tags.SelectedLineIdx = i - if err := gui.State.Contexts.Tags.HandleRender(); err != nil { - gui.Log.Error(err) - } - - return - } - } - }, - }) - }, - }) + // leaving commit SHA blank so that we're just creating the tag for the current commit + return gui.createTagMenu("") } -// tag-specific handlers -// view model would need to raise an event called 'tag selected', perhaps containing a tag. The listener would _be_ the main view, or the main context, and it would be able to render to itself. -func (gui *Gui) handleTagSelect() error { +func (gui *Gui) tagsRenderToMain() error { var task updateTask tag := gui.getSelectedTag() if tag == nil { task = NewRenderStringTask("No tags") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.GetBranchGraphCmdStr(tag.Name), - ) - task = NewRunCommandTask(cmd) + cmdObj := gui.Git.Branch.GetGraphCmdObj(tag.Name) + task = NewRunCommandTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ @@ -64,7 +39,7 @@ func (gui *Gui) handleTagSelect() error { // this is a controller: it can't access tags directly. Or can it? It should be able to get but not set. But that's exactly what I'm doing here, setting it. but through a mutator which encapsulates the event. func (gui *Gui) refreshTags() error { - tags, err := gui.GitCommand.GetTags() + tags, err := gui.Git.Loaders.Tags.GetTags() if err != nil { return gui.surfaceError(err) } @@ -85,8 +60,11 @@ func (gui *Gui) withSelectedTag(f func(tag *models.Tag) error) func() error { } } +// tag-specific handlers + func (gui *Gui) handleCheckoutTag(tag *models.Tag) error { - if err := gui.handleCheckoutRef(tag.Name, handleCheckoutRefOptions{span: gui.Tr.Spans.CheckoutTag}); err != nil { + gui.logAction(gui.Tr.Actions.CheckoutTag) + if err := gui.handleCheckoutRef(tag.Name, handleCheckoutRefOptions{}); err != nil { return err } return gui.pushContext(gui.State.Contexts.Branches) @@ -104,7 +82,8 @@ func (gui *Gui) handleDeleteTag(tag *models.Tag) error { title: gui.Tr.DeleteTagTitle, prompt: prompt, handleConfirm: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DeleteTag).DeleteTag(tag.Name); err != nil { + gui.logAction(gui.Tr.Actions.DeleteTag) + if err := gui.Git.Tag.Delete(tag.Name); err != nil { return gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}}) @@ -126,7 +105,8 @@ func (gui *Gui) handlePushTag(tag *models.Tag) error { findSuggestionsFunc: gui.getRemoteSuggestionsFunc(), handleConfirm: func(response string) error { return gui.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error { - err := gui.GitCommand.WithSpan(gui.Tr.Spans.PushTag).PushTag(response, tag.Name, gui.promptUserForCredential) + gui.logAction(gui.Tr.Actions.PushTag) + err := gui.Git.Tag.Push(response, tag.Name) gui.handleCredentialsPopup(err) return nil diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index fad3e6284..4c268c14d 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -68,8 +68,7 @@ func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) e manager := gui.getManager(view) f := func(stop chan struct{}) error { - gui.renderString(view, str) - return nil + return gui.renderString(view, str) } if err := manager.NewTask(f, key); err != nil { diff --git a/pkg/gui/undoing.go b/pkg/gui/undoing.go index bd67408d4..72a4ef302 100644 --- a/pkg/gui/undoing.go +++ b/pkg/gui/undoing.go @@ -1,8 +1,7 @@ package gui import ( - "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -89,12 +88,10 @@ func (gui *Gui) reflogUndo() error { undoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"} undoingStatus := gui.Tr.UndoingStatus - if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_REBASING { + if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING { return gui.createErrorPanel(gui.Tr.LcCantUndoWhileRebasing) } - span := gui.Tr.Spans.Undo - return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) { if counter != 0 { return false, nil @@ -102,17 +99,19 @@ func (gui *Gui) reflogUndo() error { switch action.kind { case COMMIT, REBASE: + gui.logAction(gui.Tr.Actions.Undo) return true, gui.handleHardResetWithAutoStash(action.from, handleHardResetWithAutoStashOptions{ EnvVars: undoEnvVars, WaitingStatus: undoingStatus, - span: span, }) case CHECKOUT: + gui.logAction(gui.Tr.Actions.Undo) return true, gui.handleCheckoutRef(action.from, handleCheckoutRefOptions{ EnvVars: undoEnvVars, WaitingStatus: undoingStatus, - span: span, }) + case CURRENT_REBASE: + // do nothing } gui.Log.Error("didn't match on the user action when trying to undo") @@ -124,12 +123,10 @@ func (gui *Gui) reflogRedo() error { redoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"} redoingStatus := gui.Tr.RedoingStatus - if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_REBASING { + if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING { return gui.createErrorPanel(gui.Tr.LcCantRedoWhileRebasing) } - span := gui.Tr.Spans.Redo - return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) { // if we're redoing and the counter is zero, we just return if counter == 0 { @@ -140,17 +137,19 @@ func (gui *Gui) reflogRedo() error { switch action.kind { case COMMIT, REBASE: + gui.logAction(gui.Tr.Actions.Redo) return true, gui.handleHardResetWithAutoStash(action.to, handleHardResetWithAutoStashOptions{ EnvVars: redoEnvVars, WaitingStatus: redoingStatus, - span: span, }) case CHECKOUT: + gui.logAction(gui.Tr.Actions.Redo) return true, gui.handleCheckoutRef(action.to, handleCheckoutRefOptions{ EnvVars: redoEnvVars, WaitingStatus: redoingStatus, - span: span, }) + case CURRENT_REBASE: + // do nothing } gui.Log.Error("didn't match on the user action when trying to redo") @@ -161,15 +160,12 @@ func (gui *Gui) reflogRedo() error { type handleHardResetWithAutoStashOptions struct { WaitingStatus string EnvVars []string - span string } // only to be used in the undo flow for now func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error { - gitCommand := gui.GitCommand.WithSpan(options.span) - reset := func() error { - if err := gui.resetToRef(commitSha, "hard", options.span, oscommands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil { + if err := gui.resetToRef(commitSha, "hard", options.EnvVars); err != nil { return gui.surfaceError(err) } return nil @@ -184,14 +180,14 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar prompt: gui.Tr.AutoStashPrompt, handleConfirm: func() error { return gui.WithWaitingStatus(options.WaitingStatus, func() error { - if err := gitCommand.StashSave(gui.Tr.StashPrefix + commitSha); err != nil { + if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + commitSha); err != nil { return gui.surfaceError(err) } if err := reset(); err != nil { return err } - err := gitCommand.StashDo(0, "pop") + err := gui.Git.Stash.Pop(0) if err := gui.refreshSidePanels(refreshOptions{}); err != nil { return err } diff --git a/pkg/gui/updates.go b/pkg/gui/updates.go index 0d33c8728..cce723c4f 100644 --- a/pkg/gui/updates.go +++ b/pkg/gui/updates.go @@ -36,7 +36,7 @@ func (gui *Gui) onBackgroundUpdateCheckFinish(newVersion string, err error) erro if newVersion == "" { return nil } - if gui.Config.GetUserConfig().Update.Method == "background" { + if gui.UserConfig.Update.Method == "background" { gui.startUpdating(newVersion) return nil } @@ -52,10 +52,14 @@ func (gui *Gui) startUpdating(newVersion string) { func (gui *Gui) onUpdateFinish(statusId int, err error) error { gui.State.Updating = false gui.statusManager.removeStatus(statusId) - gui.renderString(gui.Views.AppStatus, "") - if err != nil { - return gui.createErrorPanel("Update failed: " + err.Error()) - } + gui.OnUIThread(func() error { + _ = gui.renderString(gui.Views.AppStatus, "") + if err != nil { + return gui.createErrorPanel("Update failed: " + err.Error()) + } + return nil + }) + return nil } diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index e77fbf751..300571ad3 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -28,6 +28,8 @@ const ( REMOTES STATUS SUBMODULES + // not actually a view. Will refactor this later + BISECT_INFO ) func getScopeNames(scopes []RefreshableView) []string { @@ -105,18 +107,18 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error { f := func() { var scopeMap map[RefreshableView]bool if len(options.scope) == 0 { - scopeMap = arrToMap([]RefreshableView{COMMITS, BRANCHES, FILES, STASH, REFLOG, TAGS, REMOTES, STATUS}) + scopeMap = arrToMap([]RefreshableView{COMMITS, BRANCHES, FILES, STASH, REFLOG, TAGS, REMOTES, STATUS, BISECT_INFO}) } else { scopeMap = arrToMap(options.scope) } - if scopeMap[COMMITS] || scopeMap[BRANCHES] || scopeMap[REFLOG] { + if scopeMap[COMMITS] || scopeMap[BRANCHES] || scopeMap[REFLOG] || scopeMap[BISECT_INFO] { wg.Add(1) func() { if options.mode == ASYNC { - go utils.Safe(func() { _ = gui.refreshCommits() }) + go utils.Safe(func() { gui.refreshCommits() }) } else { - _ = gui.refreshCommits() + gui.refreshCommits() } wg.Done() }() @@ -180,7 +182,7 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error { } if options.mode == BLOCK_UI { - gui.g.Update(func(g *gocui.Gui) error { + gui.OnUIThread(func() error { f() return nil }) @@ -201,32 +203,19 @@ func (gui *Gui) cleanString(s string) string { return utils.NormalizeLinefeeds(output) } -func (gui *Gui) setViewContentSync(v *gocui.View, s string) { +func (gui *Gui) setViewContent(v *gocui.View, s string) { v.SetContent(gui.cleanString(s)) } -func (gui *Gui) setViewContent(v *gocui.View, s string) { - gui.g.Update(func(*gocui.Gui) error { - gui.setViewContentSync(v, s) - return nil - }) -} - // renderString resets the origin of a view and sets its content -func (gui *Gui) renderString(view *gocui.View, s string) { - gui.g.Update(func(*gocui.Gui) error { - return gui.renderStringSync(view, s) - }) -} - -func (gui *Gui) renderStringSync(view *gocui.View, s string) error { +func (gui *Gui) renderString(view *gocui.View, s string) error { if err := view.SetOrigin(0, 0); err != nil { return err } if err := view.SetCursor(0, 0); err != nil { return err } - gui.setViewContentSync(view, s) + gui.setViewContent(view, s) return nil } @@ -240,7 +229,7 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string { } func (gui *Gui) renderOptionsMap(optionsMap map[string]string) { - gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap)) + _ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap)) } func (gui *Gui) currentViewName() string { @@ -314,7 +303,7 @@ func (gui *Gui) renderDisplayStringsAtPos(v *gocui.View, y int, displayStrings [ } func (gui *Gui) globalOptionsMap() map[string]string { - keybindingConfig := gui.Config.GetUserConfig().Keybinding + keybindingConfig := gui.UserConfig.Keybinding return map[string]string{ fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollUpMain), gui.getKeyDisplay(keybindingConfig.Universal.ScrollDownMain)): gui.Tr.LcScroll, @@ -391,5 +380,5 @@ func getTabbedView(gui *Gui) *gocui.View { } func (gui *Gui) render() { - gui.g.Update(func(g *gocui.Gui) error { return nil }) + gui.OnUIThread(func() error { return nil }) } diff --git a/pkg/gui/workspace_reset_options_panel.go b/pkg/gui/workspace_reset_options_panel.go index de5440d8d..ba2df22e6 100644 --- a/pkg/gui/workspace_reset_options_panel.go +++ b/pkg/gui/workspace_reset_options_panel.go @@ -21,7 +21,8 @@ func (gui *Gui) handleCreateResetMenu() error { red.Sprint(nukeStr), }, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.NukeWorkingTree).ResetAndClean(); err != nil { + gui.logAction(gui.Tr.Actions.NukeWorkingTree) + if err := gui.Git.WorkingTree.ResetAndClean(); err != nil { return gui.surfaceError(err) } @@ -34,7 +35,8 @@ func (gui *Gui) handleCreateResetMenu() error { red.Sprint("git checkout -- ."), }, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.DiscardUnstagedFileChanges).DiscardAnyUnstagedFileChanges(); err != nil { + gui.logAction(gui.Tr.Actions.DiscardUnstagedFileChanges) + if err := gui.Git.WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil { return gui.surfaceError(err) } @@ -47,7 +49,8 @@ func (gui *Gui) handleCreateResetMenu() error { red.Sprint("git clean -fd"), }, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.RemoveUntrackedFiles).RemoveUntrackedFiles(); err != nil { + gui.logAction(gui.Tr.Actions.RemoveUntrackedFiles) + if err := gui.Git.WorkingTree.RemoveUntrackedFiles(); err != nil { return gui.surfaceError(err) } @@ -60,7 +63,8 @@ func (gui *Gui) handleCreateResetMenu() error { red.Sprint("git reset --soft HEAD"), }, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.SoftReset).ResetSoft("HEAD"); err != nil { + gui.logAction(gui.Tr.Actions.SoftReset) + if err := gui.Git.WorkingTree.ResetSoft("HEAD"); err != nil { return gui.surfaceError(err) } @@ -73,7 +77,8 @@ func (gui *Gui) handleCreateResetMenu() error { red.Sprint("git reset --mixed HEAD"), }, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.MixedReset).ResetMixed("HEAD"); err != nil { + gui.logAction(gui.Tr.Actions.MixedReset) + if err := gui.Git.WorkingTree.ResetMixed("HEAD"); err != nil { return gui.surfaceError(err) } @@ -86,7 +91,8 @@ func (gui *Gui) handleCreateResetMenu() error { red.Sprint("git reset --hard HEAD"), }, onPress: func() error { - if err := gui.GitCommand.WithSpan(gui.Tr.Spans.HardReset).ResetHard("HEAD"); err != nil { + gui.logAction(gui.Tr.Actions.HardReset) + if err := gui.Git.WorkingTree.ResetHard("HEAD"); err != nil { return gui.surfaceError(err) } diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index ebd0c2744..060689db7 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -13,19 +13,19 @@ package i18n // reset 重置 const chineseIntroPopupMessage = ` -感谢您使用 lazygit!与您分享三件事: +感谢使用 lazygit!下面几点你可能会感兴趣: - 1) 如果您想了解 lazygit 的功能,请观看此视频: + 1) 观看此视频,快速了解 lazygit 的功能: https://youtu.be/CPLdltN7wgE - 2) 请务必阅读最新的发行说明, 网址为: + 2) 记得阅读最新的发行说明: https://github.com/jesseduffield/lazygit/releases - 3) 如果您使用 git,这将使您成为一名程序员!在您的帮助下,我们可以使 - lazygit 更好,因此请考虑成为一名贡献者: + 3) 使用 git 说明你是一位程序员!你可以和我们一起让 + lazygit 变得更好。考虑贡献一些代码: https://github.com/jesseduffield/lazygit - 您也可以赞助我,并通过单击右下角的捐赠按钮,告诉我要做什么改进。 - 只是给仓库加个星星也很棒! + 也可以赞助并告诉我哪里需要改进,点右下角的捐赠按钮就好了。 + 就算给仓库点个星星也很棒! ` func chineseTranslationSet() TranslationSet { @@ -73,7 +73,6 @@ func chineseTranslationSet() TranslationSet { PullWait: "拉取中……", PushWait: "推送中……", FetchWait: "正在抓取……", - FileNoMergeCons: "该文件没有合并冲突", LcSoftReset: "软重置", AlreadyCheckedOutBranch: "您已经检出了这个分支", SureForceCheckout: "您确定要强制检出吗?您将丢失所有本地更改", @@ -109,8 +108,7 @@ func chineseTranslationSet() TranslationSet { Squash: "压缩", LcPickCommit: "选择提交(变基过程中)", LcRevertCommit: "还原提交", - OnlyRenameTopCommit: "只能从 lazygit 内部重写最高的提交。请使用 shift-R", - LcRenameCommit: "改写提交", + LcRewordCommit: "改写提交", LcDeleteCommit: "删除提交", LcMoveDownCommit: "下移提交", LcMoveUpCommit: "上移提交", @@ -135,7 +133,6 @@ func chineseTranslationSet() TranslationSet { SurePopStashEntry: "您确定要应用并删除此贮藏条目吗?", StashApply: "应用贮藏", SureApplyStashEntry: "您确定要应用此贮藏条目?", - NoStashTo: "没有贮藏条目可以 {{.method}}", NoTrackedStagedFilesStash: "没有可以贮藏的已跟踪/暂存文件", StashChanges: "贮藏更改", MergeAborted: "合并中止", @@ -263,7 +260,7 @@ func chineseTranslationSet() TranslationSet { LcViewDiscardOptions: "查看'放弃更改‘选项", LcCancel: "取消", LcDiscardAllChanges: "放弃所有更改", - LcDiscardUnstagedChanges: "放弃未进行的变更", + LcDiscardUnstagedChanges: "放弃未暂存的变更", LcDiscardAllChangesToAllFiles: "清空工作区", LcDiscardAnyUnstagedChanges: "丢弃未暂存的变更", LcDiscardUntrackedFiles: "丢弃未跟踪的文件", @@ -408,7 +405,6 @@ func chineseTranslationSet() TranslationSet { LcEditSubmoduleUrl: "更新子模块 URL", LcInitializingSubmoduleStatus: "正在初始化子模块", LcInitSubmodule: "初始化子模块", - LcViewResetAndRemoveOptions: "查看重置和删除子模块选项", LcSubmoduleUpdate: "更新子模块", LcUpdatingSubmoduleStatus: "正在更新子模块", LcBulkInitSubmodules: "批量初始化子模块", @@ -447,6 +443,8 @@ func chineseTranslationSet() TranslationSet { OpenPr: "公开公关 #", Spans: Spans{ + ConfirmRevertCommit: "您确定要撤消 {{.selectedCommit}} 吗?", + Actions: Actions{ // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) CheckoutCommit: "检出提交", CheckoutReflogCommit: "检出reflog提交", diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index f0e5e8d2a..09e79473c 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -44,17 +44,16 @@ func dutchTranslationSet() TranslationSet { PullWait: "Pullen...", PushWait: "Pushen...", FetchWait: "Fetchen...", - FileNoMergeCons: "Dit bestand heeft geen merge conflicten", LcSoftReset: "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}})", + NewBranchNameBranchOff: "Nieuw branch naam (Branch is afgeleid van '{{.branchName}}')", CantDeleteCheckOutBranch: "Je kan een uitgecheckte branch niet verwijderen!", DeleteBranch: "Verwijder branch", - DeleteBranchMessage: "Weet je zeker dat je branch {{.selectedBranchName}} wilt verwijderen?", - ForceDeleteBranchMessage: "Weet je zeker dat je branch {{.selectedBranchName}} geforceerd wil verwijderen?", + DeleteBranchMessage: "Weet je zeker dat je branch '{{.selectedBranchName}}' wilt verwijderen?", + ForceDeleteBranchMessage: "Weet je zeker dat je branch '{{.selectedBranchName}}' geforceerd wil verwijderen?", LcRebaseBranch: "rebase branch", CantRebaseOntoSelf: "Je kan niet een branch rebasen op zichzelf", CantMergeBranchIntoItself: "Je kan niet een branch in zichzelf mergen", @@ -79,8 +78,7 @@ func dutchTranslationSet() TranslationSet { Squash: "Squash", LcPickCommit: "kies commit (wanneer midden in rebase)", LcRevertCommit: "commit ongedaan maken", - OnlyRenameTopCommit: "Je kan alleen de bovenste commit hernoemen", - LcRenameCommit: "hernoem commit", + LcRewordCommit: "hernoem commit", LcDeleteCommit: "verwijder commit", LcMoveDownCommit: "verplaats commit 1 naar beneden", LcMoveUpCommit: "verplaats commit 1 naar boven", @@ -106,7 +104,6 @@ func dutchTranslationSet() TranslationSet { 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?", - NoStashTo: "Geen stash voor {{.method}}", NoTrackedStagedFilesStash: "Je hebt geen tracked/staged bestanden om te laten stashen", StashChanges: "Stash veranderingen", NoChangedFiles: "Geen veranderde bestanden", @@ -175,8 +172,8 @@ func dutchTranslationSet() TranslationSet { ConflictsResolved: "alle merge conflicten zijn opgelost. Wilt je verder gaan?", RebasingTitle: "Rebasen", MergingTitle: "Mergen", - ConfirmRebase: "Weet je zeker dat je {{.checkedOutBranch}} op {{.selectedBranch}} wil rebasen?", - ConfirmMerge: "Weet je zeker dat je {{.selectedBranch}} in {{.checkedOutBranch}} wil mergen?", + ConfirmRebase: "Weet je zeker dat je '{{.checkedOutBranch}}' op '{{.selectedBranch}}' wil rebasen?", + ConfirmMerge: "Weet je zeker dat je '{{.selectedBranch}}' in '{{.checkedOutBranch}}' wil mergen?", 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", @@ -295,7 +292,7 @@ func dutchTranslationSet() TranslationSet { SureCheckoutThisCommit: "Weet je zeker dat je deze commit wil uitchecken?", LcGitFlowOptions: "laat git-flow opties zien", NotAGitFlowBranch: "Dit lijkt geen git flow branch te zijn", - NewGitFlowBranchPrompt: "nieuwe {{.branchType}} naam:", + NewGitFlowBranchPrompt: "nieuwe '{{.branchType}}' naam:", IgnoreTracked: "Negeer tracked bestand", IgnoreTrackedPrompt: "weet je zeker dat je een getracked bestand wil negeren?", LcViewResetToUpstreamOptions: "bekijk upstream reset opties", @@ -358,12 +355,14 @@ func dutchTranslationSet() TranslationSet { LcCreateNewBranchFromCommit: "creëer nieuwe branch van commit", LcCopySubmoduleNameToClipboard: "kopieer submodule naam naar klembord", LcEnterSubmodule: "enter submodule", - LcViewResetAndRemoveOptions: "bekijk reset en verwijder submodule opties", LcAddSubmodule: "voeg nieuwe submodule toe", LcInitSubmodule: "initialiseer submodule", LcViewBulkSubmoduleOptions: "bekijk bulk submodule opties", LcViewStashFiles: "bekijk bestanden van stash entry", CreateOrOpenPullRequestOptions: "Bekijk opties voor pull-aanvraag", LcCreateOrOpenPullRequestOptions: "bekijk opties voor pull-aanvraag", + CreatePullRequestOptions: "Bekijk opties voor pull-aanvraag", + LcCreatePullRequestOptions: "bekijk opties voor pull-aanvraag", + ConfirmRevertCommit: "Weet u zeker dat u {{.selectedCommit}} ongedaan wilt maken?", } } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 85a4d8edb..e1d7c5b26 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -58,7 +58,6 @@ type TranslationSet struct { PullWait string PushWait string FetchWait string - FileNoMergeCons string LcSoftReset string AlreadyCheckedOutBranch string SureForceCheckout string @@ -93,8 +92,7 @@ type TranslationSet struct { Squash string LcPickCommit string LcRevertCommit string - OnlyRenameTopCommit string - LcRenameCommit string + LcRewordCommit string LcDeleteCommit string LcMoveDownCommit string LcMoveUpCommit string @@ -120,7 +118,6 @@ type TranslationSet struct { SurePopStashEntry string StashApply string SureApplyStashEntry string - NoStashTo string NoTrackedStagedFilesStash string StashChanges string MergeAborted string @@ -191,6 +188,7 @@ type TranslationSet struct { ConfirmRebase string ConfirmMerge string FwdNoUpstream string + FwdNoLocalUpstream string FwdCommitsToPush string ErrorOccurred string NoRoom string @@ -284,6 +282,7 @@ type TranslationSet struct { LcEnterFile string ExitLineByLineMode string EnterUpstream string + InvalidUpstream string ReturnToRemotesList string LcAddNewRemote string LcNewRemoteName string @@ -299,7 +298,11 @@ type TranslationSet struct { SetUpstreamMessage string LcEditRemote string LcTagCommit string + TagMenuTitle string TagNameTitle string + TagMessageTitle string + LcLightweightTag string + LcAnnotatedTag string LcDeleteTag string DeleteTagTitle string DeleteTagPrompt string @@ -396,7 +399,6 @@ type TranslationSet struct { LcEditSubmoduleUrl string LcInitializingSubmoduleStatus string LcInitSubmodule string - LcViewResetAndRemoveOptions string LcSubmoduleUpdate string LcUpdatingSubmoduleStatus string LcBulkInitSubmodules string @@ -434,6 +436,13 @@ type TranslationSet struct { LcSelectBranch string CreateOrShowPullRequest string CreatingPullRequestAtUrl string + IncreaseContextInDiffView string + DecreaseContextInDiffView string + CreatePullRequestOptions string + LcCreatePullRequestOptions string + LcDefaultBranch string + LcSelectBranch string + CreatePullRequest string SelectConfigFile string NoConfigFileFoundErr string LcLoadingFileSuggestions string @@ -451,10 +460,30 @@ type TranslationSet struct { ToggleShowGitGraphAll string ShowGitGraph string SortCommits string - Spans Spans + CantChangeContextSizeError string + LcOpenCommitInBrowser string + LcViewBisectOptions string + ConfirmRevertCommit string + Actions Actions + Bisect Bisect } -type Spans struct { +type Bisect struct { + MarkStart string + MarkSkipCurrent string + MarkSkipSelected string + ResetTitle string + ResetPrompt string + ResetOption string + BisectMenuTitle string + Mark string + Skip string + CompleteTitle string + CompletePrompt string + CompletePromptIndeterminate string +} + +type Actions struct { CheckoutCommit string CheckoutReflogCommit string CheckoutTag string @@ -487,6 +516,7 @@ type Spans struct { DiscardAllChangesInFile string DiscardAllUnstagedChangesInFile string StageFile string + StageResolvedFiles string UnstageFile string UnstageAllFiles string StageAllFiles string @@ -524,6 +554,7 @@ type Spans struct { BulkDeinitialiseSubmodules string UpdateSubmodule string CreateLightweightTag string + CreateAnnotatedTag string DeleteTag string PushTag string NukeWorkingTree string @@ -534,6 +565,14 @@ type Spans struct { HardReset string Undo string Redo string + CopyPullRequestURL string + OpenMergeTool string + OpenCommitInBrowser string + OpenPullRequest string + StartBisect string + ResetBisect string + BisectSkip string + BisectMark string } const englishIntroPopupMessage = ` @@ -553,7 +592,8 @@ Thanks for using lazygit! Seriously you rock. Three things to share with you: Or even just star the repo to share the love! ` -func englishTranslationSet() TranslationSet { +// exporting this so we can use it in tests +func EnglishTranslationSet() TranslationSet { return TranslationSet{ NotEnoughSpace: "Not enough space to render panels", DiffTitle: "Diff", @@ -601,17 +641,16 @@ func englishTranslationSet() TranslationSet { PullWait: "Pulling...", PushWait: "Pushing...", FetchWait: "Fetching...", - FileNoMergeCons: "This file has no inline merge conflicts", LcSoftReset: "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}})", + NewBranchNameBranchOff: "New Branch Name (Branch is off of '{{.branchName}}')", CantDeleteCheckOutBranch: "You cannot delete the checked out branch!", DeleteBranch: "Delete Branch", - DeleteBranchMessage: "Are you sure you want to delete the branch {{.selectedBranchName}}?", - ForceDeleteBranchMessage: "{{.selectedBranchName}} is not fully merged. Are you sure you want to delete it?", + DeleteBranchMessage: "Are you sure you want to delete the branch '{{.selectedBranchName}}'?", + ForceDeleteBranchMessage: "'{{.selectedBranchName}}' is not fully merged. Are you sure you want to delete it?", LcRebaseBranch: "rebase checked-out branch onto this branch", CantRebaseOntoSelf: "You cannot rebase a branch onto itself", CantMergeBranchIntoItself: "You cannot merge a branch into itself", @@ -637,14 +676,13 @@ func englishTranslationSet() TranslationSet { Squash: "Squash", LcPickCommit: "pick commit (when mid-rebase)", LcRevertCommit: "revert commit", - OnlyRenameTopCommit: "Can only reword topmost commit from within lazygit. Use shift+R instead", - LcRenameCommit: "reword commit", + LcRewordCommit: "reword commit", LcDeleteCommit: "delete commit", LcMoveDownCommit: "move commit down one", LcMoveUpCommit: "move commit up one", LcEditCommit: "edit commit", LcAmendToCommit: "amend commit with staged changes", - LcRenameCommitEditor: "rename commit with editor", + LcRenameCommitEditor: "reword commit with editor", Error: "Error", LcSelectHunk: "select hunk", LcNavigateConflicts: "navigate conflicts", @@ -663,7 +701,6 @@ func englishTranslationSet() TranslationSet { 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?", - NoStashTo: "No stash to {{.method}}", NoTrackedStagedFilesStash: "You have no tracked/staged files to stash", StashChanges: "Stash changes", MergeAborted: "Merge aborted", @@ -732,9 +769,10 @@ func englishTranslationSet() TranslationSet { GlobalTitle: "Global Keybindings", ConflictsResolved: "all merge conflicts resolved. Continue?", RebasingTitle: "Rebasing", - ConfirmRebase: "Are you sure you want to rebase {{.checkedOutBranch}} onto {{.selectedBranch}}?", - ConfirmMerge: "Are you sure you want to merge {{.selectedBranch}} into {{.checkedOutBranch}}?", + ConfirmRebase: "Are you sure you want to rebase '{{.checkedOutBranch}}' onto '{{.selectedBranch}}'?", + ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?", 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", ErrorOccurred: "An error occurred! Please create an issue at", NoRoom: "Not enough room", @@ -828,6 +866,7 @@ func englishTranslationSet() TranslationSet { LcEnterFile: "enter file to add selected lines to the patch (or toggle directory collapsed)", ExitLineByLineMode: `exit line-by-line mode`, EnterUpstream: `Enter upstream as ' '`, + InvalidUpstream: "Invalid upstream. Must be in the format ' '", ReturnToRemotesList: `Return to remotes list`, LcAddNewRemote: `add new remote`, LcNewRemoteName: `New remote name:`, @@ -843,7 +882,11 @@ func englishTranslationSet() TranslationSet { SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'", LcEditRemote: "edit remote", LcTagCommit: "tag commit", + TagMenuTitle: "Create tag", TagNameTitle: "Tag name:", + TagMessageTitle: "Tag message: ", + LcAnnotatedTag: "annotated tag", + LcLightweightTag: "lightweight tag", LcDeleteTag: "delete tag", DeleteTagTitle: "Delete tag", DeleteTagPrompt: "Are you sure you want to delete tag '{{.tagName}}'?", @@ -941,7 +984,6 @@ func englishTranslationSet() TranslationSet { LcEditSubmoduleUrl: "update submodule URL", LcInitializingSubmoduleStatus: "initializing submodule", LcInitSubmodule: "initialize submodule", - LcViewResetAndRemoveOptions: "view reset and remove submodule options", LcSubmoduleUpdate: "update submodule", LcUpdatingSubmoduleStatus: "updating submodule", LcBulkInitSubmodules: "bulk init submodules", @@ -977,9 +1019,13 @@ func englishTranslationSet() TranslationSet { CreateOrShowPullRequest: "create / open pull request", CreateOrOpenPullRequestOptions: "create / open pull request options", LcCreateOrOpenPullRequestOptions: "create / open pull request options", + IncreaseContextInDiffView: "Increase the size of the context shown around changes in the diff view", + DecreaseContextInDiffView: "Decrease the size of the context shown around changes in the diff view", + CreatePullRequest: "Create pull request", + CreatePullRequestOptions: "Create pull request options", + LcCreatePullRequestOptions: "create pull request options", LcDefaultBranch: "default branch", LcSelectBranch: "select branch", - CreatingPullRequestAtUrl: "Creating pull request at URL: %s", SelectConfigFile: "Select config file", NoConfigFileFoundErr: "No config file found", LcLoadingFileSuggestions: "loading file suggestions", @@ -995,7 +1041,11 @@ func englishTranslationSet() TranslationSet { ToggleShowGitGraphAll: "toggle show whole git graph (pass the `--all` flag to `git log`)", ShowGitGraph: "show git graph", SortCommits: "commit sort order", - Spans: Spans{ + 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!", + LcOpenCommitInBrowser: "open commit in browser", + LcViewBisectOptions: "view bisect options", + ConfirmRevertCommit: "Are you sure you want to revert {{.selectedCommit}}?", + Actions: Actions{ // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) CheckoutCommit: "Checkout commit", CheckoutReflogCommit: "Checkout reflog commit", @@ -1020,6 +1070,7 @@ func englishTranslationSet() TranslationSet { CreateFixupCommit: "Create fixup commit", SquashAllAboveFixupCommits: "Squash all above fixup commits", CreateLightweightTag: "Create lightweight tag", + CreateAnnotatedTag: "Create annotated tag", CopyCommitMessageToClipboard: "Copy commit message to clipboard", MoveCommitUp: "Move commit up", MoveCommitDown: "Move commit down", @@ -1029,6 +1080,7 @@ func englishTranslationSet() TranslationSet { DiscardAllChangesInFile: "Discard all changes in file", DiscardAllUnstagedChangesInFile: "Discard all unstaged changes in file", StageFile: "Stage file", + StageResolvedFiles: "Stage files whose merge conflicts were resolved", UnstageFile: "Unstage file", UnstageAllFiles: "Unstage all files", StageAllFiles: "Stage all files", @@ -1076,6 +1128,26 @@ func englishTranslationSet() TranslationSet { FastForwardBranch: "Fast forward branch", 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", + }, + Bisect: Bisect{ + Mark: "mark %s as %s", + MarkStart: "mark %s as %s (start bisect)", + Skip: "skip %s", + ResetTitle: "Reset 'git bisect'", + ResetPrompt: "Are you sure you want to reset 'git bisect'?", + ResetOption: "reset bisect", + 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?", }, } } diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index 8f6452c54..f6c57da8d 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -33,7 +33,7 @@ func NewTranslationSetFromConfig(log *logrus.Entry, configLanguage string) (*Tra func NewTranslationSet(log *logrus.Entry, language string) *TranslationSet { log.Info("language: " + language) - baseSet := englishTranslationSet() + baseSet := EnglishTranslationSet() for languageCode, translationSet := range GetTranslationSets() { if strings.HasPrefix(language, languageCode) { @@ -48,7 +48,7 @@ func GetTranslationSets() map[string]TranslationSet { return map[string]TranslationSet{ "pl": polishTranslationSet(), "nl": dutchTranslationSet(), - "en": englishTranslationSet(), + "en": EnglishTranslationSet(), "zh": chineseTranslationSet(), } } diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 6d73f8534..2eeecb61c 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -39,16 +39,15 @@ func polishTranslationSet() TranslationSet { PullWait: "Pobieranie zmian...", PushWait: "Wysyłanie zmian...", FetchWait: "Pobieram...", - FileNoMergeCons: "Brak konfliktów scalania w pliku", AlreadyCheckedOutBranch: "Już przęłączono na tą gałąź", SureForceCheckout: "Jesteś pewny, że chcesz wymusić przełączenie? Stracisz wszystkie lokalne zmiany", ForceCheckoutBranch: "Wymuś przełączenie gałęzi", BranchName: "Nazwa gałęzi", - NewBranchNameBranchOff: "Nazwa nowej gałęzi (gałąź na bazie {{.branchName}})", + NewBranchNameBranchOff: "Nazwa nowej gałęzi (gałąź na bazie '{{.branchName}}')", CantDeleteCheckOutBranch: "Nie możesz usunąć obecnie przełączonej gałęzi!", DeleteBranch: "Usuń gałąź", - DeleteBranchMessage: "Jesteś pewien, że chcesz usunąć gałąź {{.selectedBranchName}} ?", - ForceDeleteBranchMessage: "Na pewno wymusić usunięcie gałęzi {{.selectedBranchName}}?", + DeleteBranchMessage: "Jesteś pewien, że chcesz usunąć gałąź '{{.selectedBranchName}}' ?", + ForceDeleteBranchMessage: "Na pewno wymusić usunięcie gałęzi '{{.selectedBranchName}}'?", LcRebaseBranch: "zmiana bazy gałęzi", CantRebaseOntoSelf: "Nie możesz zmienić bazy gałęzi na nią samą", CantMergeBranchIntoItself: "Nie możesz scalić gałęzi do samej siebie", @@ -69,8 +68,7 @@ func polishTranslationSet() TranslationSet { YouNoCommitsToSquash: "Nie masz commitów do spłaszczenia", Fixup: "Napraw", SureFixupThisCommit: "Jesteś pewny, ze chcesz naprawić ten commit? Commit poniżej zostanie spłaszczony w górę wraz z tym", - OnlyRenameTopCommit: "Można zmienić nazwę tylko ostatniemu commitowi", - LcRenameCommit: "zmień nazwę commita", + LcRewordCommit: "zmień nazwę commita", LcRenameCommitEditor: "zmień nazwę commita w edytorze", Error: "Błąd", LcSelectHunk: "wybierz kawałek", @@ -84,7 +82,6 @@ func polishTranslationSet() TranslationSet { NoStashEntries: "Brak pozycji w schowku", StashDrop: "Porzuć schowek", SureDropStashEntry: "Jesteś pewny, że chcesz porzucić tę pozycję w schowku?", - NoStashTo: "Brak schowka dla {{.method}}", NoTrackedStagedFilesStash: "Nie masz śledzonych/zatwierdzonych plików do przechowania", StashChanges: "Przechowaj zmiany", MergeAborted: "Scalanie anulowane", @@ -119,8 +116,8 @@ func polishTranslationSet() TranslationSet { ReturnToFilesPanel: "wróć do panelu plików", RebasingTitle: "Zmiana bazy", MergingTitle: "Scalanie", - ConfirmRebase: "Czy napewno chcesz zmienić bazę {{.checkedOutBranch}} na {{.selectedBranch}}?", - ConfirmMerge: "Czy na pewno chcesz scalić {{.selectedBranch}} do {{.checkedOutBranch}}?", + ConfirmRebase: "Czy napewno chcesz zmienić bazę '{{.checkedOutBranch}}' na '{{.selectedBranch}}'?", + ConfirmMerge: "Czy na pewno chcesz scalić '{{.selectedBranch}}' do '{{.checkedOutBranch}}'?", FwdNoUpstream: "Nie można przewinąć gałęzi bez gałęzi nadrzędnej", FwdCommitsToPush: "Nie można przewinąć gałęzi z commitami do wysłania", ErrorOccurred: "Wystąpił błąd! Zgłoś problem na", @@ -232,5 +229,8 @@ func polishTranslationSet() TranslationSet { LcCopiedToClipboard: "skopiowany do schowka", CreateOrOpenPullRequestOptions: "Utwórz opcje żądania ściągnięcia", LcCreateOrOpenPullRequestOptions: "utwórz opcje żądania", + CreatePullRequestOptions: "Utwórz opcje żądania ściągnięcia", + LcCreatePullRequestOptions: "utwórz opcje żądania ściągnięcia", + ConfirmRevertCommit: "Czy na pewno chcesz obrócić {{.selectedCommit}}?", } } diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index 4b1842d44..a55d460fe 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -2,8 +2,10 @@ package integration import ( "encoding/json" + "errors" "fmt" "io/ioutil" + "log" "os" "os/exec" "path/filepath" @@ -23,6 +25,36 @@ type Test struct { Skip bool `json:"skip"` } +type Mode int + +const ( + // default: for when we're just running a test and comparing to the snapshot + TEST = iota + // for when we want to record a test and set the snapshot based on the result + RECORD + // when we just want to use the setup of the test for our own sandboxing purposes. + // This does not record the session and does not create/update snapshots + SANDBOX + // running a test but updating the snapshot + UPDATE_SNAPSHOT +) + +func GetModeFromEnv() Mode { + switch os.Getenv("MODE") { + case "record": + return RECORD + case "", "test": + return TEST + case "updateSnapshot": + return UPDATE_SNAPSHOT + case "sandbox": + return SANDBOX + default: + log.Fatalf("unknown test mode: %s, must be one of [test, record, update, sandbox]", os.Getenv("MODE")) + panic("unreachable") + } +} + // this function is used by both `go test` and from our lazyintegration gui, but // errors need to be handled differently in each (for example go test is always // working with *testing.T) so we pass in any differences as args here. @@ -30,8 +62,7 @@ func RunTests( logf func(format string, formatArgs ...interface{}), runCmd func(cmd *exec.Cmd) error, fnWrapper func(test *Test, f func(*testing.T) error), - updateSnapshots bool, - record bool, + mode Mode, speedEnv string, onFail func(t *testing.T, expected string, actual string, prefix string), includeSkipped bool, @@ -45,7 +76,7 @@ func RunTests( testDir := filepath.Join(rootDir, "test", "integration") osCommand := oscommands.NewDummyOSCommand() - err = osCommand.RunCommand("go build -o %s", tempLazygitPath()) + err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run() if err != nil { return err } @@ -64,22 +95,24 @@ func RunTests( } fnWrapper(test, func(t *testing.T) error { - speeds := getTestSpeeds(test.Speed, updateSnapshots, speedEnv) + speeds := getTestSpeeds(test.Speed, mode, speedEnv) testPath := filepath.Join(testDir, test.Name) actualRepoDir := filepath.Join(testPath, "actual") expectedRepoDir := filepath.Join(testPath, "expected") actualRemoteDir := filepath.Join(testPath, "actual_remote") expectedRemoteDir := filepath.Join(testPath, "expected_remote") + otherRepoDir := filepath.Join(testPath, "other_repo") logf("path: %s", testPath) - // three retries at normal speed for the sake of flakey tests - speeds = append(speeds, 1) for i, speed := range speeds { - logf("%s: attempting test at speed %f\n", test.Name, speed) + if mode != SANDBOX && mode != RECORD { + logf("%s: attempting test at speed %f\n", test.Name, speed) + } findOrCreateDir(testPath) prepareIntegrationTestDir(actualRepoDir) - removeRemoteDir(actualRemoteDir) + removeDir(otherRepoDir) + removeDir(actualRemoteDir) err := createFixture(testPath, actualRepoDir) if err != nil { return err @@ -87,7 +120,7 @@ func RunTests( configDir := filepath.Join(testPath, "used_config") - cmd, err := getLazygitCommand(testPath, rootDir, record, speed, test.ExtraCmdArgs) + cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs) if err != nil { return err } @@ -97,16 +130,21 @@ func RunTests( return err } - if updateSnapshots { + // submodule tests currently make use of a repo called 'other_repo' but we don't want that + // to stick around. Long-term we should have an 'actual' folder which itself contains + // repos, and there we can put the 'repo' repo which is the main one, alongside + // any others that we use as part of the test (including remotes). Then we'll do snapshots for + // each of them. + removeDir(otherRepoDir) + + if mode == UPDATE_SNAPSHOT || mode == RECORD { + // create/update snapshot err = oscommands.CopyDir(actualRepoDir, expectedRepoDir) if err != nil { return err } - err = os.Rename( - filepath.Join(expectedRepoDir, ".git"), - filepath.Join(expectedRepoDir, ".git_keep"), - ) - if err != nil { + + if err := renameGitDirs(expectedRepoDir); err != nil { return err } @@ -117,43 +155,46 @@ func RunTests( return err } } else { - removeRemoteDir(expectedRemoteDir) + removeDir(expectedRemoteDir) } - } - actualRepo, expectedRepo, err := generateSnapshots(actualRepoDir, expectedRepoDir) - if err != nil { - return err - } - - actualRemote := "remote folder does not exist" - expectedRemote := "remote folder does not exist" - if folderExists(expectedRemoteDir) { - actualRemote, expectedRemote, err = generateSnapshotsForRemote(actualRemoteDir, expectedRemoteDir) + logf("%s", "updated snapshot") + } else { + // compare result to snapshot + actualRepo, expectedRepo, err := generateSnapshots(actualRepoDir, expectedRepoDir) if err != nil { return err } - } else if folderExists(actualRemoteDir) { - actualRemote = "remote folder exists" - } - if expectedRepo == actualRepo && expectedRemote == actualRemote { - logf("%s: success at speed %f\n", test.Name, speed) - break - } - - // if the snapshots and we haven't tried all playback speeds different we'll retry at a slower speed - if i == len(speeds)-1 { - // get the log file and print that - bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log")) - if err != nil { - return err + actualRemote := "remote folder does not exist" + expectedRemote := "remote folder does not exist" + if folderExists(expectedRemoteDir) { + actualRemote, expectedRemote, err = generateSnapshotsForRemote(actualRemoteDir, expectedRemoteDir) + if err != nil { + return err + } + } else if folderExists(actualRemoteDir) { + actualRemote = "remote folder exists" } - logf("%s", string(bytes)) - if expectedRepo != actualRepo { - onFail(t, expectedRepo, actualRepo, "repo") - } else { - onFail(t, expectedRemote, actualRemote, "remote") + + if expectedRepo == actualRepo && expectedRemote == actualRemote { + logf("%s: success at speed %f\n", test.Name, speed) + break + } + + // if the snapshot doesn't match and we haven't tried all playback speeds different we'll retry at a slower speed + if i == len(speeds)-1 { + // get the log file and print that + bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log")) + if err != nil { + return err + } + logf("%s", string(bytes)) + if expectedRepo != actualRepo { + onFail(t, expectedRepo, actualRepo, "repo") + } else { + onFail(t, expectedRemote, actualRemote, "remote") + } } } } @@ -165,7 +206,7 @@ func RunTests( return nil } -func removeRemoteDir(dir string) { +func removeDir(dir string) { err := os.RemoveAll(dir) if err != nil { panic(err) @@ -210,18 +251,17 @@ func GetRootDirectory() string { path = filepath.Dir(path) if path == "/" { - panic("must run in lazygit folder or child folder") + log.Fatal("must run in lazygit folder or child folder") } } } func createFixture(testPath, actualDir string) error { - osCommand := oscommands.NewDummyOSCommand() bashScriptPath := filepath.Join(testPath, "setup.sh") cmd := secureexec.Command("bash", bashScriptPath, actualDir) - if err := osCommand.RunExecutable(cmd); err != nil { - return err + if output, err := cmd.CombinedOutput(); err != nil { + return errors.New(string(output)) } return nil @@ -231,8 +271,8 @@ func tempLazygitPath() string { return filepath.Join("/tmp", "lazygit", "test_lazygit") } -func getTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string) []float64 { - if updateSnapshots { +func getTestSpeeds(testStartSpeed float64, mode Mode, speedStr string) []float64 { + if mode != TEST { // have to go at original speed if updating snapshots in case we go to fast and create a junk snapshot return []float64{1.0} } @@ -254,7 +294,7 @@ func getTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string if startSpeed > 5 { speeds = append(speeds, 5) } - speeds = append(speeds, 1) + speeds = append(speeds, 1, 1) return speeds } @@ -302,6 +342,10 @@ func findOrCreateDir(path string) { } } +// note that we don't actually store this snapshot in the lazygit repo. +// Instead we store the whole expected git repo of our test, so that +// we can easily change what we want to compare without needing to regenerate +// snapshots for each test. func generateSnapshot(dir string) (string, error) { osCommand := oscommands.NewDummyOSCommand() @@ -313,18 +357,24 @@ func generateSnapshot(dir string) (string, error) { snapshot := "" cmdStrs := []string{ - fmt.Sprintf(`git -C %s status`, dir), // file tree - fmt.Sprintf(`git -C %s log --pretty=%%B -p -1`, dir), // log - fmt.Sprintf(`git -C %s tag -n`, dir), // tags + `status`, // file tree + `log --pretty=%B -p -1`, // log + `tag -n`, // tags + `stash list`, // stash + `submodule foreach 'git status'`, // submodule status + `submodule foreach 'git log --pretty=%B -p -1'`, // submodule log + `submodule foreach 'git tag -n'`, // submodule tags + `submodule foreach 'git stash list'`, // submodule stash } for _, cmdStr := range cmdStrs { // ignoring error for now. If there's an error it could be that there are no results - output, _ := osCommand.RunCommandWithOutput(cmdStr) + output, _ := osCommand.Cmd.New(fmt.Sprintf("git -C %s %s", dir, cmdStr)).RunWithOutput() - snapshot += output + "\n" + snapshot += fmt.Sprintf("git %s:\n%s\n", cmdStr, output) } + snapshot += "files in repo:\n" err = filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { if err != nil { return err @@ -341,7 +391,12 @@ func generateSnapshot(dir string) (string, error) { if err != nil { return err } - snapshot += string(bytes) + "\n" + + relativePath, err := filepath.Rel(dir, path) + if err != nil { + return err + } + snapshot += fmt.Sprintf("path: %s\ncontent:\n%s\n", relativePath, string(bytes)) return nil }) @@ -359,26 +414,26 @@ func generateSnapshots(actualDir string, expectedDir string) (string, string, er return "", "", err } - // git refuses to track .git folders in subdirectories so we need to rename it - // to git_keep after running a test, and then change it back again - defer func() { - err = os.Rename( - filepath.Join(expectedDir, ".git"), - filepath.Join(expectedDir, ".git_keep"), - ) + // there are a couple of reasons we're not generating the snapshot in expectedDir directly: + // Firstly we don't want to have to revert our .git file back to .git_keep. + // Secondly, the act of calling git commands like 'git status' actually changes the index + // for some reason, and we don't want to leave your lazygit working tree dirty as a result. + expectedDirCopyDir := filepath.Join(filepath.Dir(expectedDir), "expected_dir_test") + err = oscommands.CopyDir(expectedDir, expectedDirCopyDir) + if err != nil { + return "", "", err + } - if err != nil { - panic(err) - } - }() + if err := restoreGitDirs(expectedDirCopyDir); err != nil { + return "", "", err + } - // ignoring this error because we might not have a .git_keep file here yet. - _ = os.Rename( - filepath.Join(expectedDir, ".git_keep"), - filepath.Join(expectedDir, ".git"), - ) + expected, err := generateSnapshot(expectedDirCopyDir) + if err != nil { + return "", "", err + } - expected, err := generateSnapshot(expectedDir) + err = os.RemoveAll(expectedDirCopyDir) if err != nil { return "", "", err } @@ -386,6 +441,57 @@ func generateSnapshots(actualDir string, expectedDir string) (string, string, er return actual, expected, nil } +func getPathsToRename(dir string, needle string) []string { + pathsToRename := []string{} + + err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + if f.Name() == needle { + pathsToRename = append(pathsToRename, path) + } + + return nil + }) + if err != nil { + panic(err) + } + + return pathsToRename +} + +// Git refuses to track .git and .gitmodules folders in subdirectories so we need to rename it +// to git_keep after running a test, and then change it back again +var untrackedGitDirs []string = []string{".git", ".gitmodules"} + +func renameGitDirs(dir string) error { + for _, untrackedGitDir := range untrackedGitDirs { + for _, path := range getPathsToRename(dir, untrackedGitDir) { + err := os.Rename(path, path+"_keep") + if err != nil { + return err + } + } + } + + return nil +} + +func restoreGitDirs(dir string) error { + for _, untrackedGitDir := range untrackedGitDirs { + for _, path := range getPathsToRename(dir, untrackedGitDir+"_keep") { + err := os.Rename(path, strings.TrimSuffix(path, "_keep")) + if err != nil { + return err + } + } + } + + return nil +} + func generateSnapshotsForRemote(actualDir string, expectedDir string) (string, string, error) { actual, err := generateSnapshot(actualDir) if err != nil { @@ -400,7 +506,7 @@ func generateSnapshotsForRemote(actualDir string, expectedDir string) (string, s return actual, expected, nil } -func getLazygitCommand(testPath string, rootDir string, record bool, speed float64, extraCmdArgs string) (*exec.Cmd, error) { +func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64, extraCmdArgs string) (*exec.Cmd, error) { osCommand := oscommands.NewDummyOSCommand() replayPath := filepath.Join(testPath, "recording.json") @@ -429,22 +535,17 @@ func getLazygitCommand(testPath string, rootDir string, record bool, speed float cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualDir, extraCmdArgs) - cmd := osCommand.ExecutableFromString(cmdStr) - cmd.Env = append(cmd.Env, fmt.Sprintf("SPEED=%f", speed)) + cmdObj := osCommand.Cmd.New(cmdStr) + cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed)) - if record { - cmd.Env = append( - cmd.Env, - fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath), - ) - } else { - cmd.Env = append( - cmd.Env, - fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath), - ) + switch mode { + case RECORD: + cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath)) + case TEST, UPDATE_SNAPSHOT: + cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath)) } - return cmd, nil + return cmdObj.GetCmd(), nil } func folderExists(path string) bool { diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 479006808..4a987039c 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -19,18 +19,14 @@ 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 Task struct { - stop chan struct{} - stopped bool - stopMutex sync.Mutex - notifyStopped chan struct{} - Log *logrus.Entry - f func(chan struct{}) error -} - type ViewBufferManager struct { - writer io.Writer - currentTask *Task + // 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 + + // this is for when we wait to get waitingMutex sync.Mutex taskIDMutex sync.Mutex Log *logrus.Entry @@ -80,8 +76,15 @@ func (m *ViewBufferManager) ReadLines(n int) { }) } +// note: onDone may be called twice func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead int, onDone func()) func(chan struct{}) error { return func(stop chan struct{}) error { + var once sync.Once + var onDoneWrapper func() + if onDone != nil { + onDoneWrapper = func() { once.Do(onDone) } + } + if m.throttle { m.Log.Info("throttling task") time.Sleep(THROTTLE_TIME) @@ -109,6 +112,11 @@ func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), pref m.Log.Errorf("error when running cmd task: %v", err) } } + + // for pty's we need to call onDone here so that cmd.Wait() doesn't block forever + if onDoneWrapper != nil { + onDoneWrapper() + } }) loadingMutex := sync.Mutex{} @@ -118,34 +126,42 @@ func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), pref done := make(chan struct{}) + scanner := bufio.NewScanner(r) + scanner.Split(bufio.ScanLines) + + loaded := false + go utils.Safe(func() { - scanner := bufio.NewScanner(r) - scanner.Split(bufio.ScanLines) - - loaded := false - - go utils.Safe(func() { - ticker := time.NewTicker(time.Millisecond * 200) - defer ticker.Stop() - select { - case <-ticker.C: - loadingMutex.Lock() - if !loaded { - m.beforeStart() - _, _ = m.writer.Write([]byte("loading...")) - m.refreshView() - } - loadingMutex.Unlock() - case <-stop: - return + ticker := time.NewTicker(time.Millisecond * 200) + defer ticker.Stop() + select { + case <-stop: + return + case <-ticker.C: + loadingMutex.Lock() + if !loaded { + m.beforeStart() + _, _ = m.writer.Write([]byte("loading...")) + m.refreshView() } - }) + loadingMutex.Unlock() + } + }) + go utils.Safe(func() { outer: for { select { + case <-stop: + break outer case linesToRead := <-m.readLines: for i := 0; i < linesToRead; i++ { + select { + case <-stop: + break outer + default: + } + ok := scanner.Scan() loadingMutex.Lock() if !loaded { @@ -157,11 +173,6 @@ func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), pref } loadingMutex.Unlock() - select { - case <-stop: - break outer - default: - } 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 @@ -171,8 +182,6 @@ func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), pref _, _ = m.writer.Write(append(scanner.Bytes(), '\n')) } m.refreshView() - case <-stop: - break outer } } @@ -185,8 +194,9 @@ func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), pref } } - if onDone != nil { - onDone() + // calling onDoneWrapper here again in case the program ended on its own accord + if onDoneWrapper != nil { + onDoneWrapper() } close(done) @@ -202,14 +212,14 @@ func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), pref // Close closes the task manager, killing whatever task may currently be running func (t *ViewBufferManager) Close() { - if t.currentTask == nil { + if t.stopCurrentTask == nil { return } c := make(chan struct{}) go utils.Safe(func() { - t.currentTask.Stop() + t.stopCurrentTask() c <- struct{}{} }) @@ -245,19 +255,20 @@ func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key string return } + if m.stopCurrentTask != nil { + m.stopCurrentTask() + } + stop := make(chan struct{}) notifyStopped := make(chan struct{}) - if m.currentTask != nil { - m.currentTask.Stop() + var once sync.Once + onStop := func() { + close(stop) + <-notifyStopped } - m.currentTask = &Task{ - stop: stop, - notifyStopped: notifyStopped, - Log: m.Log, - f: f, - } + m.stopCurrentTask = func() { once.Do(onStop) } go utils.Safe(func() { if err := f(stop); err != nil { @@ -270,14 +281,3 @@ func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key string return nil } - -func (t *Task) Stop() { - t.stopMutex.Lock() - defer t.stopMutex.Unlock() - if t.stopped { - return - } - close(t.stop) - <-t.notifyStopped - t.stopped = true -} diff --git a/pkg/tasks/tasks_test.go b/pkg/tasks/tasks_test.go new file mode 100644 index 000000000..d580c95f5 --- /dev/null +++ b/pkg/tasks/tasks_test.go @@ -0,0 +1,136 @@ +package tasks + +import ( + "bytes" + "io" + "os/exec" + "sync" + "testing" + "time" + + "github.com/jesseduffield/lazygit/pkg/secureexec" + "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() + + manager := NewViewBufferManager( + utils.NewDummyLog(), + writer, + beforeStart, + refreshView, + onEndOfInput, + onNewKey, + ) + + stop := make(chan struct{}) + reader := bytes.NewBufferString("test") + start := func() (*exec.Cmd, io.Reader) { + // not actually starting this because it's not necessary + cmd := secureexec.Command("blah blah") + + close(stop) + + return cmd, reader + } + + fn := manager.NewCmdTask(start, "prefix\n", 20, onDone) + + _ = fn(stop) + + 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) + } + } + + expectedContent := "" + actualContent := writer.String() + if actualContent != expectedContent { + t.Errorf("expected writer to receive the following content: \n%s\n. But instead it recevied: %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() + + manager := NewViewBufferManager( + utils.NewDummyLog(), + writer, + beforeStart, + refreshView, + onEndOfInput, + onNewKey, + ) + + stop := make(chan struct{}) + reader := bytes.NewBufferString("test") + start := func() (*exec.Cmd, io.Reader) { + // not actually starting this because it's not necessary + cmd := secureexec.Command("blah blah") + + return cmd, reader + } + + fn := manager.NewCmdTask(start, "prefix\n", 20, onDone) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + time.Sleep(100 * time.Millisecond) + close(stop) + wg.Done() + }() + _ = fn(stop) + + 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) + } + } + + 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 recevied: %s", expectedContent, actualContent) + } +} diff --git a/pkg/test/log.go b/pkg/test/log.go new file mode 100644 index 000000000..3b166bb5d --- /dev/null +++ b/pkg/test/log.go @@ -0,0 +1,41 @@ +package test + +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) { + assert.EqualValues(t, expectedErrors, self.loggedErrors) +} diff --git a/pkg/updates/updates.go b/pkg/updates/updates.go index cb5a64788..1c52d0419 100644 --- a/pkg/updates/updates.go +++ b/pkg/updates/updates.go @@ -16,19 +16,17 @@ import ( "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/i18n" "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sirupsen/logrus" ) // Updater checks for updates and does updates type Updater struct { - Log *logrus.Entry + *common.Common Config config.AppConfigurer OSCommand *oscommands.OSCommand - Tr *i18n.TranslationSet } // Updaterer implements the check and update methods @@ -38,14 +36,11 @@ type Updaterer interface { } // NewUpdater creates a new updater -func NewUpdater(log *logrus.Entry, config config.AppConfigurer, osCommand *oscommands.OSCommand, tr *i18n.TranslationSet) (*Updater, error) { - contextLogger := log.WithField("context", "updates") - +func NewUpdater(cmn *common.Common, config config.AppConfigurer, osCommand *oscommands.OSCommand) (*Updater, error) { return &Updater{ - Log: contextLogger, + Common: cmn, Config: config, OSCommand: osCommand, - Tr: tr, }, nil } @@ -126,10 +121,8 @@ func (u *Updater) checkForNewUpdate() (string, error) { return "", errors.New(errMessage) } - rawUrl, err := u.getBinaryUrl(newVersion) - if err != nil { - return "", err - } + rawUrl := u.getBinaryUrl(newVersion) + u.Log.Info("Checking for resource at url " + rawUrl) if !u.verifyResourceFound(rawUrl) { errMessage := utils.ResolvePlaceholderString( @@ -177,7 +170,7 @@ func (u *Updater) skipUpdateCheck() bool { return true } - userConfig := u.Config.GetUserConfig() + 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 @@ -229,7 +222,7 @@ func (u *Updater) zipExtension() string { } // 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, error) { +func (u *Updater) getBinaryUrl(newVersion string) string { url := fmt.Sprintf( "%s/releases/download/%s/lazygit_%s_%s_%s.%s", constants.Links.RepoUrl, @@ -240,7 +233,7 @@ func (u *Updater) getBinaryUrl(newVersion string) (string, error) { u.zipExtension(), ) u.Log.Info("Url for latest release is " + url) - return url, nil + return url } // Update downloads the latest binary and replaces the current binary with it @@ -254,10 +247,7 @@ func (u *Updater) Update(newVersion string, onFinish func(error) error) { } func (u *Updater) update(newVersion string) error { - rawUrl, err := u.getBinaryUrl(newVersion) - if err != nil { - return err - } + rawUrl := u.getBinaryUrl(newVersion) u.Log.Info("Updating with url " + rawUrl) return u.downloadAndInstall(rawUrl) } @@ -300,7 +290,8 @@ func (u *Updater) downloadAndInstall(rawUrl string) error { } u.Log.Info("untarring tarball/unzipping zip file") - if err := u.OSCommand.RunCommand("tar -zxf %s %s", u.OSCommand.Quote(zipPath), "lazygit"); err != nil { + err = u.OSCommand.Cmd.New(fmt.Sprintf("tar -zxf %s %s", u.OSCommand.Quote(zipPath), "lazygit")).Run() + if err != nil { return err } diff --git a/pkg/utils/color.go b/pkg/utils/color.go index 76e4c41c1..37c60179a 100644 --- a/pkg/utils/color.go +++ b/pkg/utils/color.go @@ -3,6 +3,9 @@ package utils import ( "regexp" "sync" + + "github.com/gookit/color" + "github.com/jesseduffield/lazygit/pkg/gui/style" ) var decoloriseCache = make(map[string]string) @@ -48,3 +51,12 @@ func IsValidHexValue(v string) bool { return true } + +func SetCustomColors(customColors map[string]string) map[string]style.TextStyle { + colors := make(map[string]style.TextStyle) + for key, colorSequence := range customColors { + style := style.New().SetFg(style.NewRGBColor(color.HEX(colorSequence, false))) + colors[key] = style + } + return colors +} diff --git a/pkg/utils/dummies.go b/pkg/utils/dummies.go index ab6d041dd..c5fba3556 100644 --- a/pkg/utils/dummies.go +++ b/pkg/utils/dummies.go @@ -3,6 +3,9 @@ package utils import ( "io/ioutil" + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/sirupsen/logrus" ) @@ -12,3 +15,21 @@ func NewDummyLog() *logrus.Entry { log.Out = ioutil.Discard return log.WithField("test", "test") } + +func NewDummyCommon() *common.Common { + tr := i18n.EnglishTranslationSet() + return &common.Common{ + Log: NewDummyLog(), + Tr: &tr, + UserConfig: config.GetDefaultConfig(), + } +} + +func NewDummyCommonWithUserConfig(userConfig *config.UserConfig) *common.Common { + tr := i18n.EnglishTranslationSet() + return &common.Common{ + Log: NewDummyLog(), + Tr: &tr, + UserConfig: userConfig, + } +} diff --git a/pkg/utils/formatting.go b/pkg/utils/formatting.go index 80717bfad..d33028063 100644 --- a/pkg/utils/formatting.go +++ b/pkg/utils/formatting.go @@ -121,3 +121,10 @@ func SafeTruncate(str string, limit int) string { return str } } + +func ShortSha(sha string) string { + if len(sha) < 8 { + return sha + } + return sha[:8] +} diff --git a/pkg/utils/regexp.go b/pkg/utils/regexp.go new file mode 100644 index 000000000..2ceab1d71 --- /dev/null +++ b/pkg/utils/regexp.go @@ -0,0 +1,17 @@ +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 new file mode 100644 index 000000000..03afe7065 --- /dev/null +++ b/pkg/utils/regexp_test.go @@ -0,0 +1,44 @@ +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/slice.go b/pkg/utils/slice.go index dc3e85069..123fc7df9 100644 --- a/pkg/utils/slice.go +++ b/pkg/utils/slice.go @@ -2,8 +2,12 @@ package utils // IncludesString if the list contains the string func IncludesString(list []string, a string) bool { + return IncludesStringFunc(list, func(b string) bool { return b == a }) +} + +func IncludesStringFunc(list []string, fn func(string) bool) bool { for _, b := range list { - if b == a { + if fn(b) { return true } } diff --git a/pkg/utils/slice_test.go b/pkg/utils/slice_test.go index 67dcd00bf..fc80a46d7 100644 --- a/pkg/utils/slice_test.go +++ b/pkg/utils/slice_test.go @@ -85,6 +85,7 @@ func TestNextIndex(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { assert.EqualValues(t, s.expected, NextIndex(s.list, s.element)) }) @@ -128,6 +129,7 @@ func TestPrevIndex(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { assert.EqualValues(t, s.expected, PrevIndex(s.list, s.element)) }) @@ -160,6 +162,7 @@ func TestEscapeSpecialChars(t *testing.T) { } for _, s := range scenarios { + s := s t.Run(s.testName, func(t *testing.T) { assert.EqualValues(t, s.expected, EscapeSpecialChars(s.input)) }) diff --git a/scripts/bisect.sh b/scripts/bisect.sh new file mode 100755 index 000000000..0e5f404cb --- /dev/null +++ b/scripts/bisect.sh @@ -0,0 +1,20 @@ +#!/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/docs/Integration_Tests.md +# 3) checkout the commit that's known to be failing +# 4) run this script supplying the commit sha / 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/bump_gocui.sh b/scripts/bump_gocui.sh similarity index 100% rename from bump_gocui.sh rename to scripts/bump_gocui.sh diff --git a/scripts/cheatsheet/main.go b/scripts/cheatsheet/main.go new file mode 100644 index 000000000..bccf3324a --- /dev/null +++ b/scripts/cheatsheet/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/jesseduffield/lazygit/pkg/cheatsheet" +) + +func main() { + if len(os.Args) < 2 { + log.Fatal("Please provide a command: one of 'generate', 'check'") + } + + command := os.Args[1] + + switch command { + case "generate": + cheatsheet.Generate() + fmt.Printf("\nGenerated cheatsheets in %s\n", cheatsheet.GetDir()) + case "check": + cheatsheet.Check() + default: + log.Fatal("\nUnknown command. Expected one of 'generate', 'check'") + } +} diff --git a/scripts/push_new_patch/main.go b/scripts/push_new_patch/main.go deleted file mode 100755 index 40d95d9a0..000000000 --- a/scripts/push_new_patch/main.go +++ /dev/null @@ -1,64 +0,0 @@ -// call from project root with -// go run scripts/push_new_patch/main.go - -// goreleaser expects a $GITHUB_TOKEN env variable to be defined -// in order to push the release got github - -package main - -import ( - "fmt" - "io/ioutil" - "log" - "os" - "strconv" - "strings" - - "github.com/jesseduffield/lazygit/pkg/secureexec" -) - -func main() { - - version, err := ioutil.ReadFile("VERSION") - if err != nil { - log.Panicln(err.Error()) - } - stringVersion := string(version) - fmt.Println("VERSION was " + stringVersion) - - runCommand("git", "pull") - - splitVersion := strings.Split(stringVersion, ".") - patch := splitVersion[len(splitVersion)-1] - newPatch, _ := strconv.Atoi(patch) - splitVersion[len(splitVersion)-1] = strconv.FormatInt(int64(newPatch)+1, 10) - newVersion := strings.Join(splitVersion, ".") - - err = ioutil.WriteFile("VERSION", []byte(newVersion), 0644) - if err != nil { - log.Panicln(err.Error()) - } - - runCommand("git", "add", "VERSION") - runCommand("git", "commit", "-m", "bump version to "+newVersion, "--", "VERSION") - runCommand("git", "push") - runCommand("git", "tag", newVersion) - runCommand("git", "push", "origin", newVersion) - runCommand("goreleaser", "--rm-dist") - runCommand("rm", "-rf", "dist") -} - -func runCommand(args ...string) { - fmt.Println(strings.Join(args, " ")) - cmd := secureexec.Command(args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Start() - if err != nil { - panic(err.Error()) - } - err = cmd.Wait() - if err != nil { - panic(err.Error()) - } -} diff --git a/test/integration/rebaseReword/expected/.git_keep/FETCH_HEAD b/test/integration/bisect/expected/.git_keep/BISECT_ANCESTORS_OK similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/FETCH_HEAD rename to test/integration/bisect/expected/.git_keep/BISECT_ANCESTORS_OK diff --git a/test/integration/bisect/expected/.git_keep/BISECT_EXPECTED_REV b/test/integration/bisect/expected/.git_keep/BISECT_EXPECTED_REV new file mode 100644 index 000000000..aeb0e94fb --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/BISECT_EXPECTED_REV @@ -0,0 +1 @@ +e927f0f9467e772eea36f24053c9b534303b106a diff --git a/test/integration/bisect/expected/.git_keep/BISECT_LOG b/test/integration/bisect/expected/.git_keep/BISECT_LOG new file mode 100644 index 000000000..db4a771e2 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/BISECT_LOG @@ -0,0 +1,16 @@ +git bisect start +# bad: [054bdf969fdcf1f90f1998666f628d40f72fde4f] commit 19 +git bisect bad 054bdf969fdcf1f90f1998666f628d40f72fde4f +# good: [39983ea412adebe6c5a3d4451a7673cf0962c472] commit 9 +git bisect good 39983ea412adebe6c5a3d4451a7673cf0962c472 +# good: [e9d2f825e793bc9ac2be698348dbe669bad34cad] commit 14 +git bisect good e9d2f825e793bc9ac2be698348dbe669bad34cad +# skip: [d1f7a85555fe6f10dd44754d35459ae741cb107c] commit 15 +git bisect skip d1f7a85555fe6f10dd44754d35459ae741cb107c +# skip: [bc21c8fabc28201fab6c60503168ecda25ad8626] commit 17 +git bisect skip bc21c8fabc28201fab6c60503168ecda25ad8626 +# good: [67fbfb3b74c2381ad1e058949231f2b4f0c8921f] commit 16 +git bisect good 67fbfb3b74c2381ad1e058949231f2b4f0c8921f +# good: [e927f0f9467e772eea36f24053c9b534303b106a] commit 18 +git bisect good e927f0f9467e772eea36f24053c9b534303b106a +# first bad commit: [054bdf969fdcf1f90f1998666f628d40f72fde4f] commit 19 diff --git a/test/integration/bisect/expected/.git_keep/BISECT_NAMES b/test/integration/bisect/expected/.git_keep/BISECT_NAMES new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/BISECT_NAMES @@ -0,0 +1 @@ + diff --git a/test/integration/bisect/expected/.git_keep/BISECT_START b/test/integration/bisect/expected/.git_keep/BISECT_START new file mode 100644 index 000000000..1f7391f92 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/BISECT_START @@ -0,0 +1 @@ +master diff --git a/test/integration/bisect/expected/.git_keep/BISECT_TERMS b/test/integration/bisect/expected/.git_keep/BISECT_TERMS new file mode 100644 index 000000000..25dd30b14 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/BISECT_TERMS @@ -0,0 +1,2 @@ +bad +good diff --git a/test/integration/bisect/expected/.git_keep/COMMIT_EDITMSG b/test/integration/bisect/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..e6cf6d392 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +commit 20 diff --git a/test/integration/stashFiles/expected/.git_keep/FETCH_HEAD b/test/integration/bisect/expected/.git_keep/FETCH_HEAD similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/FETCH_HEAD rename to test/integration/bisect/expected/.git_keep/FETCH_HEAD diff --git a/test/integration/bisect/expected/.git_keep/HEAD b/test/integration/bisect/expected/.git_keep/HEAD new file mode 100644 index 000000000..cea9d05ed --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/test diff --git a/test/integration/rebaseReword/expected/.git_keep/config b/test/integration/bisect/expected/.git_keep/config similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/config rename to test/integration/bisect/expected/.git_keep/config diff --git a/test/integration/rebaseReword/expected/.git_keep/description b/test/integration/bisect/expected/.git_keep/description similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/description rename to test/integration/bisect/expected/.git_keep/description diff --git a/test/integration/bisect/expected/.git_keep/index b/test/integration/bisect/expected/.git_keep/index new file mode 100644 index 000000000..2ad3e517b Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/index differ diff --git a/test/integration/rebaseReword/expected/.git_keep/info/exclude b/test/integration/bisect/expected/.git_keep/info/exclude similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/info/exclude rename to test/integration/bisect/expected/.git_keep/info/exclude diff --git a/test/integration/bisect/expected/.git_keep/logs/HEAD b/test/integration/bisect/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..8f4ef5553 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/logs/HEAD @@ -0,0 +1,24 @@ +0000000000000000000000000000000000000000 005ca78c7fb8157683fa61158235b250d2316004 CI 1643185278 +1100 commit (initial): commit 1 +005ca78c7fb8157683fa61158235b250d2316004 d9328d9b2c9536fdf01641dd03f4a254d2c86601 CI 1643185278 +1100 commit: commit 2 +d9328d9b2c9536fdf01641dd03f4a254d2c86601 e59bbaffe94b06acaadab4245f30ff3e11c66e5b CI 1643185278 +1100 commit: commit 3 +e59bbaffe94b06acaadab4245f30ff3e11c66e5b 32e7b0308424a817ed5aa5bba94b06b72a1b8ce5 CI 1643185278 +1100 commit: commit 4 +32e7b0308424a817ed5aa5bba94b06b72a1b8ce5 96202a92c1d3bde1b20d6f3dec8e742d09732b4d CI 1643185278 +1100 commit: commit 5 +96202a92c1d3bde1b20d6f3dec8e742d09732b4d 82d721eb037f7045056023d0904989781ce1f526 CI 1643185278 +1100 commit: commit 6 +82d721eb037f7045056023d0904989781ce1f526 b97844c9437a4ab69c8165cadd97bc597b43135b CI 1643185278 +1100 commit: commit 7 +b97844c9437a4ab69c8165cadd97bc597b43135b ebe59a71e9750e75fb983f241687cdf7f0c8ce94 CI 1643185278 +1100 commit: commit 8 +ebe59a71e9750e75fb983f241687cdf7f0c8ce94 39983ea412adebe6c5a3d4451a7673cf0962c472 CI 1643185278 +1100 commit: commit 9 +39983ea412adebe6c5a3d4451a7673cf0962c472 dbb21289ee21b2ff0f3de2bc7d00038b30c4e353 CI 1643185278 +1100 commit: commit 10 +dbb21289ee21b2ff0f3de2bc7d00038b30c4e353 5f9397e5bcee1ac2a3fe6d834d42e36b74ef4ca8 CI 1643185278 +1100 commit: commit 11 +5f9397e5bcee1ac2a3fe6d834d42e36b74ef4ca8 267465454f74736bbe5b493c7f69dd3d024e26e5 CI 1643185278 +1100 commit: commit 12 +267465454f74736bbe5b493c7f69dd3d024e26e5 478a007451b33c7a234c60f0d13b164561b29094 CI 1643185278 +1100 commit: commit 13 +478a007451b33c7a234c60f0d13b164561b29094 e9d2f825e793bc9ac2be698348dbe669bad34cad CI 1643185278 +1100 commit: commit 14 +e9d2f825e793bc9ac2be698348dbe669bad34cad d1f7a85555fe6f10dd44754d35459ae741cb107c CI 1643185278 +1100 commit: commit 15 +d1f7a85555fe6f10dd44754d35459ae741cb107c 67fbfb3b74c2381ad1e058949231f2b4f0c8921f CI 1643185278 +1100 commit: commit 16 +67fbfb3b74c2381ad1e058949231f2b4f0c8921f bc21c8fabc28201fab6c60503168ecda25ad8626 CI 1643185278 +1100 commit: commit 17 +bc21c8fabc28201fab6c60503168ecda25ad8626 e927f0f9467e772eea36f24053c9b534303b106a CI 1643185278 +1100 commit: commit 18 +e927f0f9467e772eea36f24053c9b534303b106a 054bdf969fdcf1f90f1998666f628d40f72fde4f CI 1643185278 +1100 commit: commit 19 +054bdf969fdcf1f90f1998666f628d40f72fde4f 78d41b2abbd2f52c1ebf2f496268a915d59eb27b CI 1643185278 +1100 commit: commit 20 +78d41b2abbd2f52c1ebf2f496268a915d59eb27b e9d2f825e793bc9ac2be698348dbe669bad34cad CI 1643185283 +1100 checkout: moving from master to e9d2f825e793bc9ac2be698348dbe669bad34cad +e9d2f825e793bc9ac2be698348dbe669bad34cad 67fbfb3b74c2381ad1e058949231f2b4f0c8921f CI 1643185286 +1100 checkout: moving from e9d2f825e793bc9ac2be698348dbe669bad34cad to 67fbfb3b74c2381ad1e058949231f2b4f0c8921f +67fbfb3b74c2381ad1e058949231f2b4f0c8921f e927f0f9467e772eea36f24053c9b534303b106a CI 1643185295 +1100 checkout: moving from 67fbfb3b74c2381ad1e058949231f2b4f0c8921f to e927f0f9467e772eea36f24053c9b534303b106a +e927f0f9467e772eea36f24053c9b534303b106a 78d41b2abbd2f52c1ebf2f496268a915d59eb27b CI 1643185301 +1100 checkout: moving from e927f0f9467e772eea36f24053c9b534303b106a to test diff --git a/test/integration/bisect/expected/.git_keep/logs/refs/heads/master b/test/integration/bisect/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..61c607364 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,20 @@ +0000000000000000000000000000000000000000 005ca78c7fb8157683fa61158235b250d2316004 CI 1643185278 +1100 commit (initial): commit 1 +005ca78c7fb8157683fa61158235b250d2316004 d9328d9b2c9536fdf01641dd03f4a254d2c86601 CI 1643185278 +1100 commit: commit 2 +d9328d9b2c9536fdf01641dd03f4a254d2c86601 e59bbaffe94b06acaadab4245f30ff3e11c66e5b CI 1643185278 +1100 commit: commit 3 +e59bbaffe94b06acaadab4245f30ff3e11c66e5b 32e7b0308424a817ed5aa5bba94b06b72a1b8ce5 CI 1643185278 +1100 commit: commit 4 +32e7b0308424a817ed5aa5bba94b06b72a1b8ce5 96202a92c1d3bde1b20d6f3dec8e742d09732b4d CI 1643185278 +1100 commit: commit 5 +96202a92c1d3bde1b20d6f3dec8e742d09732b4d 82d721eb037f7045056023d0904989781ce1f526 CI 1643185278 +1100 commit: commit 6 +82d721eb037f7045056023d0904989781ce1f526 b97844c9437a4ab69c8165cadd97bc597b43135b CI 1643185278 +1100 commit: commit 7 +b97844c9437a4ab69c8165cadd97bc597b43135b ebe59a71e9750e75fb983f241687cdf7f0c8ce94 CI 1643185278 +1100 commit: commit 8 +ebe59a71e9750e75fb983f241687cdf7f0c8ce94 39983ea412adebe6c5a3d4451a7673cf0962c472 CI 1643185278 +1100 commit: commit 9 +39983ea412adebe6c5a3d4451a7673cf0962c472 dbb21289ee21b2ff0f3de2bc7d00038b30c4e353 CI 1643185278 +1100 commit: commit 10 +dbb21289ee21b2ff0f3de2bc7d00038b30c4e353 5f9397e5bcee1ac2a3fe6d834d42e36b74ef4ca8 CI 1643185278 +1100 commit: commit 11 +5f9397e5bcee1ac2a3fe6d834d42e36b74ef4ca8 267465454f74736bbe5b493c7f69dd3d024e26e5 CI 1643185278 +1100 commit: commit 12 +267465454f74736bbe5b493c7f69dd3d024e26e5 478a007451b33c7a234c60f0d13b164561b29094 CI 1643185278 +1100 commit: commit 13 +478a007451b33c7a234c60f0d13b164561b29094 e9d2f825e793bc9ac2be698348dbe669bad34cad CI 1643185278 +1100 commit: commit 14 +e9d2f825e793bc9ac2be698348dbe669bad34cad d1f7a85555fe6f10dd44754d35459ae741cb107c CI 1643185278 +1100 commit: commit 15 +d1f7a85555fe6f10dd44754d35459ae741cb107c 67fbfb3b74c2381ad1e058949231f2b4f0c8921f CI 1643185278 +1100 commit: commit 16 +67fbfb3b74c2381ad1e058949231f2b4f0c8921f bc21c8fabc28201fab6c60503168ecda25ad8626 CI 1643185278 +1100 commit: commit 17 +bc21c8fabc28201fab6c60503168ecda25ad8626 e927f0f9467e772eea36f24053c9b534303b106a CI 1643185278 +1100 commit: commit 18 +e927f0f9467e772eea36f24053c9b534303b106a 054bdf969fdcf1f90f1998666f628d40f72fde4f CI 1643185278 +1100 commit: commit 19 +054bdf969fdcf1f90f1998666f628d40f72fde4f 78d41b2abbd2f52c1ebf2f496268a915d59eb27b CI 1643185278 +1100 commit: commit 20 diff --git a/test/integration/bisect/expected/.git_keep/logs/refs/heads/test b/test/integration/bisect/expected/.git_keep/logs/refs/heads/test new file mode 100644 index 000000000..a2526105f --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/logs/refs/heads/test @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 78d41b2abbd2f52c1ebf2f496268a915d59eb27b CI 1643185301 +1100 branch: Created from master diff --git a/test/integration/bisect/expected/.git_keep/objects/00/5ca78c7fb8157683fa61158235b250d2316004 b/test/integration/bisect/expected/.git_keep/objects/00/5ca78c7fb8157683fa61158235b250d2316004 new file mode 100644 index 000000000..a22627c8d Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/00/5ca78c7fb8157683fa61158235b250d2316004 differ diff --git a/test/integration/bisect/expected/.git_keep/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba b/test/integration/bisect/expected/.git_keep/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba new file mode 100644 index 000000000..d3c45d51e Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba differ diff --git a/test/integration/bisect/expected/.git_keep/objects/05/4bdf969fdcf1f90f1998666f628d40f72fde4f b/test/integration/bisect/expected/.git_keep/objects/05/4bdf969fdcf1f90f1998666f628d40f72fde4f new file mode 100644 index 000000000..dd18604e8 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/objects/05/4bdf969fdcf1f90f1998666f628d40f72fde4f @@ -0,0 +1,2 @@ +xK +1D]?09FtP00Dfv{PU(CU!*%h-KcaG@f˻:hP&냆ylII7'VPn>ui4_SY[8D8!юQ]8a:E \ No newline at end of file diff --git a/test/integration/bisect/expected/.git_keep/objects/07/552205114379b7c1abd7cb39575cb7a30a2e8c b/test/integration/bisect/expected/.git_keep/objects/07/552205114379b7c1abd7cb39575cb7a30a2e8c new file mode 100644 index 000000000..2ea049967 Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/07/552205114379b7c1abd7cb39575cb7a30a2e8c differ diff --git a/test/integration/bisect/expected/.git_keep/objects/0c/fbf08886fca9a91cb753ec8734c84fcbe52c9f b/test/integration/bisect/expected/.git_keep/objects/0c/fbf08886fca9a91cb753ec8734c84fcbe52c9f new file mode 100644 index 000000000..da246e1fe Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/0c/fbf08886fca9a91cb753ec8734c84fcbe52c9f differ diff --git a/test/integration/bisect/expected/.git_keep/objects/11/0046b8d92b877def6cda61639cf8f37bc2829c b/test/integration/bisect/expected/.git_keep/objects/11/0046b8d92b877def6cda61639cf8f37bc2829c new file mode 100644 index 000000000..ad217ecf4 Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/11/0046b8d92b877def6cda61639cf8f37bc2829c differ diff --git a/test/integration/bisect/expected/.git_keep/objects/12/e46e3c37d1a43a26b909a346ecd2d97677c641 b/test/integration/bisect/expected/.git_keep/objects/12/e46e3c37d1a43a26b909a346ecd2d97677c641 new file mode 100644 index 000000000..700547c7a Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/12/e46e3c37d1a43a26b909a346ecd2d97677c641 differ diff --git a/test/integration/bisect/expected/.git_keep/objects/1b/01733c2b372c7b5544c7f2293c3b7341824112 b/test/integration/bisect/expected/.git_keep/objects/1b/01733c2b372c7b5544c7f2293c3b7341824112 new file mode 100644 index 000000000..11276c468 Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/1b/01733c2b372c7b5544c7f2293c3b7341824112 differ diff --git a/test/integration/bisect/expected/.git_keep/objects/1e/8b314962144c26d5e0e50fd29d2ca327864913 b/test/integration/bisect/expected/.git_keep/objects/1e/8b314962144c26d5e0e50fd29d2ca327864913 new file mode 100644 index 000000000..f1722ae00 Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/1e/8b314962144c26d5e0e50fd29d2ca327864913 differ diff --git a/test/integration/bisect/expected/.git_keep/objects/20/9e3ef4b6247ce746048d5711befda46206d235 b/test/integration/bisect/expected/.git_keep/objects/20/9e3ef4b6247ce746048d5711befda46206d235 new file mode 100644 index 000000000..12e1f82f1 Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/20/9e3ef4b6247ce746048d5711befda46206d235 differ diff --git a/test/integration/bisect/expected/.git_keep/objects/26/7465454f74736bbe5b493c7f69dd3d024e26e5 b/test/integration/bisect/expected/.git_keep/objects/26/7465454f74736bbe5b493c7f69dd3d024e26e5 new file mode 100644 index 000000000..edae3689b Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/26/7465454f74736bbe5b493c7f69dd3d024e26e5 differ diff --git a/test/integration/bisect/expected/.git_keep/objects/32/e7b0308424a817ed5aa5bba94b06b72a1b8ce5 b/test/integration/bisect/expected/.git_keep/objects/32/e7b0308424a817ed5aa5bba94b06b72a1b8ce5 new file mode 100644 index 000000000..f220ca5b8 Binary files /dev/null and b/test/integration/bisect/expected/.git_keep/objects/32/e7b0308424a817ed5aa5bba94b06b72a1b8ce5 differ diff --git a/test/integration/bisect/expected/.git_keep/objects/39/983ea412adebe6c5a3d4451a7673cf0962c472 b/test/integration/bisect/expected/.git_keep/objects/39/983ea412adebe6c5a3d4451a7673cf0962c472 new file mode 100644 index 000000000..63c4d53b2 --- /dev/null +++ b/test/integration/bisect/expected/.git_keep/objects/39/983ea412adebe6c5a3d4451a7673cf0962c472 @@ -0,0 +1,2 @@ +xM +1 a=E4$ "j Co[Pպb}=OXsvB.b'fF6[AFI* ¾SU &}a^$ +KDј,Hdc!O*If{y5`{#QR Igƒ` }}a^ file + git add . + git commit -m "commit $i" +done diff --git a/test/integration/bisect/test.json b/test/integration/bisect/test.json new file mode 100644 index 000000000..2bf2b418b --- /dev/null +++ b/test/integration/bisect/test.json @@ -0,0 +1,4 @@ +{ + "description": "Basic git bisect usage", + "speed": 5 +} diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_ANCESTORS_OK b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_ANCESTORS_OK new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_EXPECTED_REV b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_EXPECTED_REV new file mode 100644 index 000000000..df35eff7a --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_EXPECTED_REV @@ -0,0 +1 @@ +10e171beacb963e4f8a4dc1d80fd291c135902bb diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_LOG b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_LOG new file mode 100644 index 000000000..6a5f8b695 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_LOG @@ -0,0 +1,12 @@ +# bad: [0f373801691c466240bd131d28b2168712b045ba] commit 18 +# good: [38242e5215bc35b3e418c1d6d63fd0291001e10b] commit 7 +git bisect start 'other~2' 'other~13' +# bad: [d27a997859219951ecc95c351174c70ea0cf9d37] commit 12 +git bisect bad d27a997859219951ecc95c351174c70ea0cf9d37 +# good: [3fbb9e626d4b7d30e58346b3eefaa342b15ab776] commit 9 +git bisect good 3fbb9e626d4b7d30e58346b3eefaa342b15ab776 +# bad: [d84e0684b1038552d5c8d86e67398d634f77ad3b] commit 11 +git bisect bad d84e0684b1038552d5c8d86e67398d634f77ad3b +# bad: [10e171beacb963e4f8a4dc1d80fd291c135902bb] commit 10 +git bisect bad 10e171beacb963e4f8a4dc1d80fd291c135902bb +# first bad commit: [10e171beacb963e4f8a4dc1d80fd291c135902bb] commit 10 diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_NAMES b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_NAMES new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_NAMES @@ -0,0 +1 @@ + diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_START b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_START new file mode 100644 index 000000000..1f7391f92 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_START @@ -0,0 +1 @@ +master diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_TERMS b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_TERMS new file mode 100644 index 000000000..25dd30b14 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/BISECT_TERMS @@ -0,0 +1,2 @@ +bad +good diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/COMMIT_EDITMSG b/test/integration/bisectFromOtherBranch/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..e6cf6d392 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +commit 20 diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/FETCH_HEAD b/test/integration/bisectFromOtherBranch/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/HEAD b/test/integration/bisectFromOtherBranch/expected/.git_keep/HEAD new file mode 100644 index 000000000..cea9d05ed --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/test diff --git a/test/integration/stashFiles/expected/.git_keep/config b/test/integration/bisectFromOtherBranch/expected/.git_keep/config similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/config rename to test/integration/bisectFromOtherBranch/expected/.git_keep/config diff --git a/test/integration/stashFiles/expected/.git_keep/description b/test/integration/bisectFromOtherBranch/expected/.git_keep/description similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/description rename to test/integration/bisectFromOtherBranch/expected/.git_keep/description diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/index b/test/integration/bisectFromOtherBranch/expected/.git_keep/index new file mode 100644 index 000000000..c469e6ea5 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/index differ diff --git a/test/integration/stashFiles/expected/.git_keep/info/exclude b/test/integration/bisectFromOtherBranch/expected/.git_keep/info/exclude similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/info/exclude rename to test/integration/bisectFromOtherBranch/expected/.git_keep/info/exclude diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/HEAD b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..31a81bf7a --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/HEAD @@ -0,0 +1,28 @@ +0000000000000000000000000000000000000000 36903784186b1b8b4a150dc656eccd49f94e114e CI 1643177881 +1100 commit (initial): first commit +36903784186b1b8b4a150dc656eccd49f94e114e 36903784186b1b8b4a150dc656eccd49f94e114e CI 1643177881 +1100 checkout: moving from master to other +36903784186b1b8b4a150dc656eccd49f94e114e 1ab342c03b4226cca1c751dba71fa2df0e7d82ee CI 1643177881 +1100 commit: commit 1 +1ab342c03b4226cca1c751dba71fa2df0e7d82ee 15d4c5b8608fe472fd224333e60d44ea826cc80e CI 1643177881 +1100 commit: commit 2 +15d4c5b8608fe472fd224333e60d44ea826cc80e b614152a335aabd8b5daa2c6abccc9425eb14177 CI 1643177881 +1100 commit: commit 3 +b614152a335aabd8b5daa2c6abccc9425eb14177 1a35564b85e24c96e647b477aaf8d35dcf0de84d CI 1643177881 +1100 commit: commit 4 +1a35564b85e24c96e647b477aaf8d35dcf0de84d 11e4fd4011c9ed3800bb33b85580dd1d09f6aefd CI 1643177881 +1100 commit: commit 5 +11e4fd4011c9ed3800bb33b85580dd1d09f6aefd 3068d0d730547aaa5b86dbfe638db83133ae1421 CI 1643177881 +1100 commit: commit 6 +3068d0d730547aaa5b86dbfe638db83133ae1421 38242e5215bc35b3e418c1d6d63fd0291001e10b CI 1643177881 +1100 commit: commit 7 +38242e5215bc35b3e418c1d6d63fd0291001e10b ff231021500c7beb87de0d6d5edc29b6f9c000b1 CI 1643177881 +1100 commit: commit 8 +ff231021500c7beb87de0d6d5edc29b6f9c000b1 3fbb9e626d4b7d30e58346b3eefaa342b15ab776 CI 1643177881 +1100 commit: commit 9 +3fbb9e626d4b7d30e58346b3eefaa342b15ab776 10e171beacb963e4f8a4dc1d80fd291c135902bb CI 1643177881 +1100 commit: commit 10 +10e171beacb963e4f8a4dc1d80fd291c135902bb d84e0684b1038552d5c8d86e67398d634f77ad3b CI 1643177882 +1100 commit: commit 11 +d84e0684b1038552d5c8d86e67398d634f77ad3b d27a997859219951ecc95c351174c70ea0cf9d37 CI 1643177882 +1100 commit: commit 12 +d27a997859219951ecc95c351174c70ea0cf9d37 3fd9ad29c185eac6106cfa005a3aa594e21b9e06 CI 1643177882 +1100 commit: commit 13 +3fd9ad29c185eac6106cfa005a3aa594e21b9e06 b2970eba9fd8dba8655094dc38fb4ee50b9bf23e CI 1643177882 +1100 commit: commit 14 +b2970eba9fd8dba8655094dc38fb4ee50b9bf23e 6fa769bf11bd9dc4ff4e85f4951950b0bc34326f CI 1643177882 +1100 commit: commit 15 +6fa769bf11bd9dc4ff4e85f4951950b0bc34326f 9c836a5c3f513818d300b410f8cb3a2e3bbd42a1 CI 1643177882 +1100 commit: commit 16 +9c836a5c3f513818d300b410f8cb3a2e3bbd42a1 5493d27d38b9902cf28b1035c644bf470df76060 CI 1643177882 +1100 commit: commit 17 +5493d27d38b9902cf28b1035c644bf470df76060 0f373801691c466240bd131d28b2168712b045ba CI 1643177882 +1100 commit: commit 18 +0f373801691c466240bd131d28b2168712b045ba 333ff293caf2d3216edf22e3f1df64d43a7e1311 CI 1643177882 +1100 commit: commit 19 +333ff293caf2d3216edf22e3f1df64d43a7e1311 e559f83c6e8dd11680de70b487725e37ff2e283f CI 1643177882 +1100 commit: commit 20 +e559f83c6e8dd11680de70b487725e37ff2e283f 36903784186b1b8b4a150dc656eccd49f94e114e CI 1643177882 +1100 checkout: moving from other to master +36903784186b1b8b4a150dc656eccd49f94e114e d27a997859219951ecc95c351174c70ea0cf9d37 CI 1643177882 +1100 checkout: moving from master to d27a997859219951ecc95c351174c70ea0cf9d37 +d27a997859219951ecc95c351174c70ea0cf9d37 3fbb9e626d4b7d30e58346b3eefaa342b15ab776 CI 1643177885 +1100 checkout: moving from d27a997859219951ecc95c351174c70ea0cf9d37 to 3fbb9e626d4b7d30e58346b3eefaa342b15ab776 +3fbb9e626d4b7d30e58346b3eefaa342b15ab776 d84e0684b1038552d5c8d86e67398d634f77ad3b CI 1643177886 +1100 checkout: moving from 3fbb9e626d4b7d30e58346b3eefaa342b15ab776 to d84e0684b1038552d5c8d86e67398d634f77ad3b +d84e0684b1038552d5c8d86e67398d634f77ad3b 10e171beacb963e4f8a4dc1d80fd291c135902bb CI 1643177888 +1100 checkout: moving from d84e0684b1038552d5c8d86e67398d634f77ad3b to 10e171beacb963e4f8a4dc1d80fd291c135902bb +10e171beacb963e4f8a4dc1d80fd291c135902bb 36903784186b1b8b4a150dc656eccd49f94e114e CI 1643177893 +1100 checkout: moving from 10e171beacb963e4f8a4dc1d80fd291c135902bb to test diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/master b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..4aff8c25b --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 36903784186b1b8b4a150dc656eccd49f94e114e CI 1643177881 +1100 commit (initial): first commit diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/other b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/other new file mode 100644 index 000000000..db03baa43 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/other @@ -0,0 +1,21 @@ +0000000000000000000000000000000000000000 36903784186b1b8b4a150dc656eccd49f94e114e CI 1643177881 +1100 branch: Created from HEAD +36903784186b1b8b4a150dc656eccd49f94e114e 1ab342c03b4226cca1c751dba71fa2df0e7d82ee CI 1643177881 +1100 commit: commit 1 +1ab342c03b4226cca1c751dba71fa2df0e7d82ee 15d4c5b8608fe472fd224333e60d44ea826cc80e CI 1643177881 +1100 commit: commit 2 +15d4c5b8608fe472fd224333e60d44ea826cc80e b614152a335aabd8b5daa2c6abccc9425eb14177 CI 1643177881 +1100 commit: commit 3 +b614152a335aabd8b5daa2c6abccc9425eb14177 1a35564b85e24c96e647b477aaf8d35dcf0de84d CI 1643177881 +1100 commit: commit 4 +1a35564b85e24c96e647b477aaf8d35dcf0de84d 11e4fd4011c9ed3800bb33b85580dd1d09f6aefd CI 1643177881 +1100 commit: commit 5 +11e4fd4011c9ed3800bb33b85580dd1d09f6aefd 3068d0d730547aaa5b86dbfe638db83133ae1421 CI 1643177881 +1100 commit: commit 6 +3068d0d730547aaa5b86dbfe638db83133ae1421 38242e5215bc35b3e418c1d6d63fd0291001e10b CI 1643177881 +1100 commit: commit 7 +38242e5215bc35b3e418c1d6d63fd0291001e10b ff231021500c7beb87de0d6d5edc29b6f9c000b1 CI 1643177881 +1100 commit: commit 8 +ff231021500c7beb87de0d6d5edc29b6f9c000b1 3fbb9e626d4b7d30e58346b3eefaa342b15ab776 CI 1643177881 +1100 commit: commit 9 +3fbb9e626d4b7d30e58346b3eefaa342b15ab776 10e171beacb963e4f8a4dc1d80fd291c135902bb CI 1643177881 +1100 commit: commit 10 +10e171beacb963e4f8a4dc1d80fd291c135902bb d84e0684b1038552d5c8d86e67398d634f77ad3b CI 1643177882 +1100 commit: commit 11 +d84e0684b1038552d5c8d86e67398d634f77ad3b d27a997859219951ecc95c351174c70ea0cf9d37 CI 1643177882 +1100 commit: commit 12 +d27a997859219951ecc95c351174c70ea0cf9d37 3fd9ad29c185eac6106cfa005a3aa594e21b9e06 CI 1643177882 +1100 commit: commit 13 +3fd9ad29c185eac6106cfa005a3aa594e21b9e06 b2970eba9fd8dba8655094dc38fb4ee50b9bf23e CI 1643177882 +1100 commit: commit 14 +b2970eba9fd8dba8655094dc38fb4ee50b9bf23e 6fa769bf11bd9dc4ff4e85f4951950b0bc34326f CI 1643177882 +1100 commit: commit 15 +6fa769bf11bd9dc4ff4e85f4951950b0bc34326f 9c836a5c3f513818d300b410f8cb3a2e3bbd42a1 CI 1643177882 +1100 commit: commit 16 +9c836a5c3f513818d300b410f8cb3a2e3bbd42a1 5493d27d38b9902cf28b1035c644bf470df76060 CI 1643177882 +1100 commit: commit 17 +5493d27d38b9902cf28b1035c644bf470df76060 0f373801691c466240bd131d28b2168712b045ba CI 1643177882 +1100 commit: commit 18 +0f373801691c466240bd131d28b2168712b045ba 333ff293caf2d3216edf22e3f1df64d43a7e1311 CI 1643177882 +1100 commit: commit 19 +333ff293caf2d3216edf22e3f1df64d43a7e1311 e559f83c6e8dd11680de70b487725e37ff2e283f CI 1643177882 +1100 commit: commit 20 diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/test b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/test new file mode 100644 index 000000000..7ae7f188f --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/logs/refs/heads/test @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 36903784186b1b8b4a150dc656eccd49f94e114e CI 1643177893 +1100 branch: Created from master diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba new file mode 100644 index 000000000..d3c45d51e Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/03/ecdaa424af1fdaeab1bd1852319652b9518f11 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/03/ecdaa424af1fdaeab1bd1852319652b9518f11 new file mode 100644 index 000000000..355004b9e Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/03/ecdaa424af1fdaeab1bd1852319652b9518f11 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/04/a577be2858b8024716876aefe6b665a98e1e4f b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/04/a577be2858b8024716876aefe6b665a98e1e4f new file mode 100644 index 000000000..da9396d18 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/04/a577be2858b8024716876aefe6b665a98e1e4f differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/05/57fc43da38567eae00831e9b385fd2cad22643 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/05/57fc43da38567eae00831e9b385fd2cad22643 new file mode 100644 index 000000000..0ff3d683a Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/05/57fc43da38567eae00831e9b385fd2cad22643 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0c/fbf08886fca9a91cb753ec8734c84fcbe52c9f b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0c/fbf08886fca9a91cb753ec8734c84fcbe52c9f new file mode 100644 index 000000000..da246e1fe Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0c/fbf08886fca9a91cb753ec8734c84fcbe52c9f differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0d/1bbe8d012c8c070167a006a4898b525cfbd930 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0d/1bbe8d012c8c070167a006a4898b525cfbd930 new file mode 100644 index 000000000..be2aa4386 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0d/1bbe8d012c8c070167a006a4898b525cfbd930 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0f/373801691c466240bd131d28b2168712b045ba b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0f/373801691c466240bd131d28b2168712b045ba new file mode 100644 index 000000000..97cb55d14 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/0f/373801691c466240bd131d28b2168712b045ba @@ -0,0 +1,3 @@ +xK +0@]d&IEzcKzx(i쵂ffΤ"K("F$Sda#&{} p6}1!ň:7qMz,32*yü4/=% +֐!h8!Sz9 \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/10/e171beacb963e4f8a4dc1d80fd291c135902bb b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/10/e171beacb963e4f8a4dc1d80fd291c135902bb new file mode 100644 index 000000000..72e97d561 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/10/e171beacb963e4f8a4dc1d80fd291c135902bb differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/11/e4fd4011c9ed3800bb33b85580dd1d09f6aefd b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/11/e4fd4011c9ed3800bb33b85580dd1d09f6aefd new file mode 100644 index 000000000..76c433f1e Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/11/e4fd4011c9ed3800bb33b85580dd1d09f6aefd differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/15/d4c5b8608fe472fd224333e60d44ea826cc80e b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/15/d4c5b8608fe472fd224333e60d44ea826cc80e new file mode 100644 index 000000000..1bbb2bb68 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/15/d4c5b8608fe472fd224333e60d44ea826cc80e differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1a/35564b85e24c96e647b477aaf8d35dcf0de84d b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1a/35564b85e24c96e647b477aaf8d35dcf0de84d new file mode 100644 index 000000000..90434de85 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1a/35564b85e24c96e647b477aaf8d35dcf0de84d differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1a/b342c03b4226cca1c751dba71fa2df0e7d82ee b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1a/b342c03b4226cca1c751dba71fa2df0e7d82ee new file mode 100644 index 000000000..89ad61ab4 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1a/b342c03b4226cca1c751dba71fa2df0e7d82ee differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1e/8b314962144c26d5e0e50fd29d2ca327864913 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1e/8b314962144c26d5e0e50fd29d2ca327864913 new file mode 100644 index 000000000..f1722ae00 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/1e/8b314962144c26d5e0e50fd29d2ca327864913 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/20/9e3ef4b6247ce746048d5711befda46206d235 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/20/9e3ef4b6247ce746048d5711befda46206d235 new file mode 100644 index 000000000..12e1f82f1 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/20/9e3ef4b6247ce746048d5711befda46206d235 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/26/661287266e53bda69d8daa3aac1f714650f13c b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/26/661287266e53bda69d8daa3aac1f714650f13c new file mode 100644 index 000000000..77e40c5af Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/26/661287266e53bda69d8daa3aac1f714650f13c differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/30/68d0d730547aaa5b86dbfe638db83133ae1421 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/30/68d0d730547aaa5b86dbfe638db83133ae1421 new file mode 100644 index 000000000..8d4ae0999 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/30/68d0d730547aaa5b86dbfe638db83133ae1421 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/31/df6c951dc82b75b11bc48836e780134a16e6ad b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/31/df6c951dc82b75b11bc48836e780134a16e6ad new file mode 100644 index 000000000..204aace94 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/31/df6c951dc82b75b11bc48836e780134a16e6ad differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/33/3ff293caf2d3216edf22e3f1df64d43a7e1311 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/33/3ff293caf2d3216edf22e3f1df64d43a7e1311 new file mode 100644 index 000000000..36368d1fc Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/33/3ff293caf2d3216edf22e3f1df64d43a7e1311 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/36/903784186b1b8b4a150dc656eccd49f94e114e b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/36/903784186b1b8b4a150dc656eccd49f94e114e new file mode 100644 index 000000000..abb03d100 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/36/903784186b1b8b4a150dc656eccd49f94e114e differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/38/242e5215bc35b3e418c1d6d63fd0291001e10b b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/38/242e5215bc35b3e418c1d6d63fd0291001e10b new file mode 100644 index 000000000..5ca009d7c --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/38/242e5215bc35b3e418c1d6d63fd0291001e10b @@ -0,0 +1,2 @@ +xM +@ @asL'U(X[.<ǷxmG""r:Y&ʄDb:y0eoy}ک-0GR޻S?}-: \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3a/05ed1ca9671bc362c7197eb09bddff040c9110 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3a/05ed1ca9671bc362c7197eb09bddff040c9110 new file mode 100644 index 000000000..1ede294e1 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3a/05ed1ca9671bc362c7197eb09bddff040c9110 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3b/e94d6b1b1b3b63db9e412b2e788d08979bc176 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3b/e94d6b1b1b3b63db9e412b2e788d08979bc176 new file mode 100644 index 000000000..00f0e0176 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3b/e94d6b1b1b3b63db9e412b2e788d08979bc176 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3c/032078a4a21c5c51d3c93d91717c1dabbb8cd0 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3c/032078a4a21c5c51d3c93d91717c1dabbb8cd0 new file mode 100644 index 000000000..ecdb2d612 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3c/032078a4a21c5c51d3c93d91717c1dabbb8cd0 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3f/bb9e626d4b7d30e58346b3eefaa342b15ab776 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3f/bb9e626d4b7d30e58346b3eefaa342b15ab776 new file mode 100644 index 000000000..344405069 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3f/bb9e626d4b7d30e58346b3eefaa342b15ab776 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3f/d9ad29c185eac6106cfa005a3aa594e21b9e06 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3f/d9ad29c185eac6106cfa005a3aa594e21b9e06 new file mode 100644 index 000000000..f03b029ab Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/3f/d9ad29c185eac6106cfa005a3aa594e21b9e06 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/43/99b07bd31d1f58a0ff10b5434d5ff4f36e38fb b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/43/99b07bd31d1f58a0ff10b5434d5ff4f36e38fb new file mode 100644 index 000000000..23915f2bc Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/43/99b07bd31d1f58a0ff10b5434d5ff4f36e38fb differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/45/a4fb75db864000d01701c0f7a51864bd4daabf b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/45/a4fb75db864000d01701c0f7a51864bd4daabf new file mode 100644 index 000000000..fdf2bc81d Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/45/a4fb75db864000d01701c0f7a51864bd4daabf differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/48/082f72f087ce7e6fa75b9c41d7387daecd447b b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/48/082f72f087ce7e6fa75b9c41d7387daecd447b new file mode 100644 index 000000000..23ff2840f Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/48/082f72f087ce7e6fa75b9c41d7387daecd447b differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/4a/9e01012c736e5e0998b7184d9a54e8c610ed02 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/4a/9e01012c736e5e0998b7184d9a54e8c610ed02 new file mode 100644 index 000000000..3b4ccc564 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/4a/9e01012c736e5e0998b7184d9a54e8c610ed02 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/54/93d27d38b9902cf28b1035c644bf470df76060 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/54/93d27d38b9902cf28b1035c644bf470df76060 new file mode 100644 index 000000000..166e5ee14 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/54/93d27d38b9902cf28b1035c644bf470df76060 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/60/d3b2f4a4cd5f1637eba020358bfe5ecb5edcf2 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/60/d3b2f4a4cd5f1637eba020358bfe5ecb5edcf2 new file mode 100644 index 000000000..d94cbe67d Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/60/d3b2f4a4cd5f1637eba020358bfe5ecb5edcf2 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/6f/a769bf11bd9dc4ff4e85f4951950b0bc34326f b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/6f/a769bf11bd9dc4ff4e85f4951950b0bc34326f new file mode 100644 index 000000000..7f9ad8243 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/6f/a769bf11bd9dc4ff4e85f4951950b0bc34326f @@ -0,0 +1,3 @@ +xA +1 @Q=E$M@Dh C,<[9ƦjjS 8X< +plּkXq@%sT%S xSI54/o<߮}},b1NS"r`O `;w \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/74/cd0e856d938eb6b665284c5485c00f87e20dc5 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/74/cd0e856d938eb6b665284c5485c00f87e20dc5 new file mode 100644 index 000000000..988810145 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/74/cd0e856d938eb6b665284c5485c00f87e20dc5 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7c/fd51ebd06287effcfdab241235305cb6439d40 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7c/fd51ebd06287effcfdab241235305cb6439d40 new file mode 100644 index 000000000..8a6a651ca Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7c/fd51ebd06287effcfdab241235305cb6439d40 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7e/d6ff82de6bcc2a78243fc9c54d3ef5ac14da69 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7e/d6ff82de6bcc2a78243fc9c54d3ef5ac14da69 new file mode 100644 index 000000000..637482d7a Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7e/d6ff82de6bcc2a78243fc9c54d3ef5ac14da69 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7f/8f011eb73d6043d2e6db9d2c101195ae2801f2 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7f/8f011eb73d6043d2e6db9d2c101195ae2801f2 new file mode 100644 index 000000000..60454ab43 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/7f/8f011eb73d6043d2e6db9d2c101195ae2801f2 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/83/51c19397f4fcd5238d10034fa7fa384f14d580 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/83/51c19397f4fcd5238d10034fa7fa384f14d580 new file mode 100644 index 000000000..12805e960 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/83/51c19397f4fcd5238d10034fa7fa384f14d580 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/88/8633a131a49f1b8981d70c13d666defed6ba15 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/88/8633a131a49f1b8981d70c13d666defed6ba15 new file mode 100644 index 000000000..1c62650e2 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/88/8633a131a49f1b8981d70c13d666defed6ba15 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/8b/0156bd25a1ecd82ef7c4c53e9d6d312bb6d403 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/8b/0156bd25a1ecd82ef7c4c53e9d6d312bb6d403 new file mode 100644 index 000000000..f78367c1d Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/8b/0156bd25a1ecd82ef7c4c53e9d6d312bb6d403 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/90/dfebd90b0a89766c39928f22901b8c02b51fda b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/90/dfebd90b0a89766c39928f22901b8c02b51fda new file mode 100644 index 000000000..851fbe7da --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/90/dfebd90b0a89766c39928f22901b8c02b51fda @@ -0,0 +1 @@ +x+)JMU03c040031QHIeH˒b(v;wT>G7]ݭ<ơ'=B \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/98/d9bcb75a685dfbfd60f611c309410152935b3d b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/98/d9bcb75a685dfbfd60f611c309410152935b3d new file mode 100644 index 000000000..f6e750c5d Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/98/d9bcb75a685dfbfd60f611c309410152935b3d differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/9c/836a5c3f513818d300b410f8cb3a2e3bbd42a1 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/9c/836a5c3f513818d300b410f8cb3a2e3bbd42a1 new file mode 100644 index 000000000..22a928539 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/9c/836a5c3f513818d300b410f8cb3a2e3bbd42a1 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/9d/33ec0915534bf6401be9412203697791e40a04 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/9d/33ec0915534bf6401be9412203697791e40a04 new file mode 100644 index 000000000..c6c240e0c --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/9d/33ec0915534bf6401be9412203697791e40a04 @@ -0,0 +1 @@ +x+)JMU03c040031QHIeʕ2ۃߴ?Tg>T>G7]ݭ<ơ'=G \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b1/bd38b62a0800a4f6a80c34e21c5acffae52c7e b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b1/bd38b62a0800a4f6a80c34e21c5acffae52c7e new file mode 100644 index 000000000..268d0f459 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b1/bd38b62a0800a4f6a80c34e21c5acffae52c7e differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b2/970eba9fd8dba8655094dc38fb4ee50b9bf23e b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b2/970eba9fd8dba8655094dc38fb4ee50b9bf23e new file mode 100644 index 000000000..e9a1d720b Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b2/970eba9fd8dba8655094dc38fb4ee50b9bf23e differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b4/de3947675361a7770d29b8982c407b0ec6b2a0 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b4/de3947675361a7770d29b8982c407b0ec6b2a0 new file mode 100644 index 000000000..6d65f13b4 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b4/de3947675361a7770d29b8982c407b0ec6b2a0 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b6/14152a335aabd8b5daa2c6abccc9425eb14177 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b6/14152a335aabd8b5daa2c6abccc9425eb14177 new file mode 100644 index 000000000..7e8fb3f28 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b6/14152a335aabd8b5daa2c6abccc9425eb14177 @@ -0,0 +1,2 @@ +xK +1] ҝDYyA8㛅p[T=?cєIE,Lp@*8!.jٕ!6j%"Pg *}rvOSNFo CqNt5/o:` \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b6/a7d89c68e0ca66e96a9a51892cc33db66fb8a3 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b6/a7d89c68e0ca66e96a9a51892cc33db66fb8a3 new file mode 100644 index 000000000..02dc68891 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b6/a7d89c68e0ca66e96a9a51892cc33db66fb8a3 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b8/626c4cff2849624fb67f87cd0ad72b163671ad b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b8/626c4cff2849624fb67f87cd0ad72b163671ad new file mode 100644 index 000000000..0761b6105 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/b8/626c4cff2849624fb67f87cd0ad72b163671ad differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/cd/c58c0f95a2313ded9185e102bc35253f6a1bed b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/cd/c58c0f95a2313ded9185e102bc35253f6a1bed new file mode 100644 index 000000000..13147f24b Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/cd/c58c0f95a2313ded9185e102bc35253f6a1bed differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d0/0491fd7e5bb6fa28c517a0bb32b8b506539d4d b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d0/0491fd7e5bb6fa28c517a0bb32b8b506539d4d new file mode 100644 index 000000000..8dab6a9ea Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d0/0491fd7e5bb6fa28c517a0bb32b8b506539d4d differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d2/7a997859219951ecc95c351174c70ea0cf9d37 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d2/7a997859219951ecc95c351174c70ea0cf9d37 new file mode 100644 index 000000000..1efdcdbe0 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d2/7a997859219951ecc95c351174c70ea0cf9d37 @@ -0,0 +1,3 @@ +xM +0F]$)1cK|Wp +~@,=Jf Eupơj_rpvb՞yu`rڒ}! !ډ8XWcLlJ~XV&\֮c$2pFZuDuSF}W: \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d3/6e09d97bf2c1527118bde353ad64b157f8b269 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d3/6e09d97bf2c1527118bde353ad64b157f8b269 new file mode 100644 index 000000000..efc598268 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d3/6e09d97bf2c1527118bde353ad64b157f8b269 @@ -0,0 +1 @@ +x+)JMU03c040031QHIe2o]_G6ڱ-x/T>G7]ݭ<ơ'= 5 \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d4/e982570808a24722649e852a92bda5cf54c9dd b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d4/e982570808a24722649e852a92bda5cf54c9dd new file mode 100644 index 000000000..262f6ef1f Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d4/e982570808a24722649e852a92bda5cf54c9dd differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d6/b24041cf04154f8f902651969675021f4d93a5 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d6/b24041cf04154f8f902651969675021f4d93a5 new file mode 100644 index 000000000..1ece9675c Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d6/b24041cf04154f8f902651969675021f4d93a5 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d8/4e0684b1038552d5c8d86e67398d634f77ad3b b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d8/4e0684b1038552d5c8d86e67398d634f77ad3b new file mode 100644 index 000000000..4a21451f3 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/d8/4e0684b1038552d5c8d86e67398d634f77ad3b differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/da/8dacb2a073ebc2adeddceb3cc2b39b6c95c858 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/da/8dacb2a073ebc2adeddceb3cc2b39b6c95c858 new file mode 100644 index 000000000..336fa7996 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/da/8dacb2a073ebc2adeddceb3cc2b39b6c95c858 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/db/76c03074879025735b647b825786a7b3fcfe7c b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/db/76c03074879025735b647b825786a7b3fcfe7c new file mode 100644 index 000000000..b19f46c3d Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/db/76c03074879025735b647b825786a7b3fcfe7c differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/e5/59f83c6e8dd11680de70b487725e37ff2e283f b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/e5/59f83c6e8dd11680de70b487725e37ff2e283f new file mode 100644 index 000000000..44afe2d13 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/e5/59f83c6e8dd11680de70b487725e37ff2e283f @@ -0,0 +1,3 @@ +xA +0E]$3&)]c2Df\ʺ,8CUC +!&>3xlTm3E*FFlX-X"#:y4ye{ꩬaJ9w}QM>w_?R: \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 000000000..711223894 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/ec/635144f60048986bc560c5576355344005e6e7 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/ec/635144f60048986bc560c5576355344005e6e7 new file mode 100644 index 000000000..0207e4b4b Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/ec/635144f60048986bc560c5576355344005e6e7 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/f5/99e28b8ab0d8c9c57a486c89c4a5132dcbd3b2 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/f5/99e28b8ab0d8c9c57a486c89c4a5132dcbd3b2 new file mode 100644 index 000000000..674e37f3f Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/f5/99e28b8ab0d8c9c57a486c89c4a5132dcbd3b2 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/ff/231021500c7beb87de0d6d5edc29b6f9c000b1 b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/ff/231021500c7beb87de0d6d5edc29b6f9c000b1 new file mode 100644 index 000000000..b02c5baf8 Binary files /dev/null and b/test/integration/bisectFromOtherBranch/expected/.git_keep/objects/ff/231021500c7beb87de0d6d5edc29b6f9c000b1 differ diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/packed-refs b/test/integration/bisectFromOtherBranch/expected/.git_keep/packed-refs new file mode 100644 index 000000000..250f18738 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/packed-refs @@ -0,0 +1 @@ +# pack-refs with: peeled fully-peeled sorted diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/bad b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/bad new file mode 100644 index 000000000..df35eff7a --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/bad @@ -0,0 +1 @@ +10e171beacb963e4f8a4dc1d80fd291c135902bb diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/good-38242e5215bc35b3e418c1d6d63fd0291001e10b b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/good-38242e5215bc35b3e418c1d6d63fd0291001e10b new file mode 100644 index 000000000..365c8ad45 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/good-38242e5215bc35b3e418c1d6d63fd0291001e10b @@ -0,0 +1 @@ +38242e5215bc35b3e418c1d6d63fd0291001e10b diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/good-3fbb9e626d4b7d30e58346b3eefaa342b15ab776 b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/good-3fbb9e626d4b7d30e58346b3eefaa342b15ab776 new file mode 100644 index 000000000..bfb46ec3b --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/bisect/good-3fbb9e626d4b7d30e58346b3eefaa342b15ab776 @@ -0,0 +1 @@ +3fbb9e626d4b7d30e58346b3eefaa342b15ab776 diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/master b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..bcec15ab4 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +36903784186b1b8b4a150dc656eccd49f94e114e diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/other b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/other new file mode 100644 index 000000000..d6f4abc86 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/other @@ -0,0 +1 @@ +e559f83c6e8dd11680de70b487725e37ff2e283f diff --git a/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/test b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/test new file mode 100644 index 000000000..bcec15ab4 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/expected/.git_keep/refs/heads/test @@ -0,0 +1 @@ +36903784186b1b8b4a150dc656eccd49f94e114e diff --git a/test/integration/bisectFromOtherBranch/expected/myfile b/test/integration/bisectFromOtherBranch/expected/myfile new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/bisectFromOtherBranch/recording.json b/test/integration/bisectFromOtherBranch/recording.json new file mode 100644 index 000000000..c38fa7758 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":661,"Mod":0,"Key":259,"Ch":0},{"Timestamp":925,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1238,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1398,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1535,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1677,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1823,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1941,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2261,"Mod":0,"Key":256,"Ch":98},{"Timestamp":2910,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3564,"Mod":0,"Key":256,"Ch":98},{"Timestamp":3949,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4214,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4869,"Mod":0,"Key":256,"Ch":98},{"Timestamp":5229,"Mod":0,"Key":257,"Ch":0},{"Timestamp":5582,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6590,"Mod":0,"Key":256,"Ch":98},{"Timestamp":7325,"Mod":0,"Key":13,"Ch":13},{"Timestamp":9003,"Mod":0,"Key":27,"Ch":0},{"Timestamp":9341,"Mod":0,"Key":260,"Ch":0},{"Timestamp":10141,"Mod":0,"Key":256,"Ch":110},{"Timestamp":10413,"Mod":0,"Key":256,"Ch":116},{"Timestamp":10452,"Mod":0,"Key":256,"Ch":101},{"Timestamp":10629,"Mod":0,"Key":256,"Ch":115},{"Timestamp":10660,"Mod":0,"Key":256,"Ch":116},{"Timestamp":10854,"Mod":0,"Key":13,"Ch":13},{"Timestamp":11637,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/bisectFromOtherBranch/setup.sh b/test/integration/bisectFromOtherBranch/setup.sh new file mode 100644 index 000000000..ef9eb932c --- /dev/null +++ b/test/integration/bisectFromOtherBranch/setup.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +touch myfile +git add myfile +git commit -m "first commit" + +git checkout -b other + +for i in {1..20} +do + echo "$i" > file + git add . + git commit -m "commit $i" +done + +git checkout master + +git bisect start other~2 other~13 diff --git a/test/integration/bisectFromOtherBranch/test.json b/test/integration/bisectFromOtherBranch/test.json new file mode 100644 index 000000000..a5503ac83 --- /dev/null +++ b/test/integration/bisectFromOtherBranch/test.json @@ -0,0 +1,4 @@ +{ + "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", + "speed": 20 +} diff --git a/test/integration/branchAutocomplete/setup.sh b/test/integration/branchAutocomplete/setup.sh index cde74f610..4c53f58a2 100644 --- a/test/integration/branchAutocomplete/setup.sh +++ b/test/integration/branchAutocomplete/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/branchDelete/setup.sh b/test/integration/branchDelete/setup.sh index da9b9bddb..d67fa9291 100644 --- a/test/integration/branchDelete/setup.sh +++ b/test/integration/branchDelete/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/branchRebase/expected/.git_keep/COMMIT_EDITMSG b/test/integration/branchRebase/expected/.git_keep/COMMIT_EDITMSG index 8ef87ec12..42bf03720 100644 --- a/test/integration/branchRebase/expected/.git_keep/COMMIT_EDITMSG +++ b/test/integration/branchRebase/expected/.git_keep/COMMIT_EDITMSG @@ -3,15 +3,16 @@ first commit on master # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # -# interactive rebase in progress; onto e23041d +# interactive rebase in progress; onto e957aaf # Last command done (1 command done): -# pick 5bc64a0 first commit on master +# pick 69f11ae first commit on master # Next commands to do (3 remaining commands): -# drop 8c464cc second commit on master -# drop 9ec3d8f third commit on master -# You are currently rebasing branch 'master' on 'e23041d'. +# drop 8d3bd1c second commit on master +# drop 3e1706c third commit on master +# You are currently rebasing branch 'master' on 'e957aaf'. # # Changes to be committed: +# modified: directory/file # modified: directory/file2 # modified: file1 # diff --git a/test/integration/branchRebase/expected/.git_keep/ORIG_HEAD b/test/integration/branchRebase/expected/.git_keep/ORIG_HEAD index 4c0b2873b..28cab2e49 100644 --- a/test/integration/branchRebase/expected/.git_keep/ORIG_HEAD +++ b/test/integration/branchRebase/expected/.git_keep/ORIG_HEAD @@ -1 +1 @@ -5bc64a01e7b980e97f714fba45aa18f0eef726fb +69f11ae88c8712fe38ffd0fe9ff9df05371500a6 diff --git a/test/integration/branchRebase/expected/.git_keep/index b/test/integration/branchRebase/expected/.git_keep/index index 7611a1faf..12d743587 100644 Binary files a/test/integration/branchRebase/expected/.git_keep/index and b/test/integration/branchRebase/expected/.git_keep/index differ diff --git a/test/integration/branchRebase/expected/.git_keep/logs/HEAD b/test/integration/branchRebase/expected/.git_keep/logs/HEAD index 7db77f413..e4a5b4d7a 100644 --- a/test/integration/branchRebase/expected/.git_keep/logs/HEAD +++ b/test/integration/branchRebase/expected/.git_keep/logs/HEAD @@ -1,22 +1,22 @@ -0000000000000000000000000000000000000000 ad12d265183742f1aa5ee2fe7c09dc3c16482d7d CI 1617686669 +1000 commit (initial): first commit -ad12d265183742f1aa5ee2fe7c09dc3c16482d7d ad12d265183742f1aa5ee2fe7c09dc3c16482d7d CI 1617686669 +1000 checkout: moving from master to develop -ad12d265183742f1aa5ee2fe7c09dc3c16482d7d f3dedf7d7a26a03a4e996a1d8e9bf2754609717f CI 1617686669 +1000 commit: first commit on develop -f3dedf7d7a26a03a4e996a1d8e9bf2754609717f ad12d265183742f1aa5ee2fe7c09dc3c16482d7d CI 1617686669 +1000 checkout: moving from develop to master -ad12d265183742f1aa5ee2fe7c09dc3c16482d7d 5bc64a01e7b980e97f714fba45aa18f0eef726fb CI 1617686669 +1000 commit: first commit on master -5bc64a01e7b980e97f714fba45aa18f0eef726fb f3dedf7d7a26a03a4e996a1d8e9bf2754609717f CI 1617686669 +1000 checkout: moving from master to develop -f3dedf7d7a26a03a4e996a1d8e9bf2754609717f f9ad2a0d946867abbd3228fc132afef480287c27 CI 1617686669 +1000 commit: second commit on develop -f9ad2a0d946867abbd3228fc132afef480287c27 5bc64a01e7b980e97f714fba45aa18f0eef726fb CI 1617686669 +1000 checkout: moving from develop to master -5bc64a01e7b980e97f714fba45aa18f0eef726fb 8c464cc13c9ad8d00604ec9b646e2b2187706287 CI 1617686669 +1000 commit: second commit on master -8c464cc13c9ad8d00604ec9b646e2b2187706287 f9ad2a0d946867abbd3228fc132afef480287c27 CI 1617686669 +1000 checkout: moving from master to develop -f9ad2a0d946867abbd3228fc132afef480287c27 7f8238e605705a3b485f653df7779e43a41f4e76 CI 1617686669 +1000 commit: third commit on develop -7f8238e605705a3b485f653df7779e43a41f4e76 8c464cc13c9ad8d00604ec9b646e2b2187706287 CI 1617686669 +1000 checkout: moving from develop to master -8c464cc13c9ad8d00604ec9b646e2b2187706287 9ec3d8f323be49b6cc9036db9299fb0ae6764440 CI 1617686669 +1000 commit: third commit on master -9ec3d8f323be49b6cc9036db9299fb0ae6764440 7f8238e605705a3b485f653df7779e43a41f4e76 CI 1617686669 +1000 checkout: moving from master to develop -7f8238e605705a3b485f653df7779e43a41f4e76 e23041d5ff1f243faf319cc5e48e6d57384e4f37 CI 1617686669 +1000 commit: fourth commit on develop -e23041d5ff1f243faf319cc5e48e6d57384e4f37 9ec3d8f323be49b6cc9036db9299fb0ae6764440 CI 1617686669 +1000 checkout: moving from develop to master -9ec3d8f323be49b6cc9036db9299fb0ae6764440 4d34b8913eb0991ca63f8f7877ab8408e1511b3d CI 1617686669 +1000 commit: fourth commit on master -4d34b8913eb0991ca63f8f7877ab8408e1511b3d e23041d5ff1f243faf319cc5e48e6d57384e4f37 CI 1617686671 +1000 rebase -i (start): checkout develop -e23041d5ff1f243faf319cc5e48e6d57384e4f37 4d34b8913eb0991ca63f8f7877ab8408e1511b3d CI 1617686672 +1000 rebase -i (abort): updating HEAD -4d34b8913eb0991ca63f8f7877ab8408e1511b3d e23041d5ff1f243faf319cc5e48e6d57384e4f37 CI 1617686674 +1000 rebase -i (start): checkout develop -e23041d5ff1f243faf319cc5e48e6d57384e4f37 cea4333c6fff70e6248efee2b3a414a4e11a7956 CI 1617686684 +1000 rebase -i (continue): first commit on master -cea4333c6fff70e6248efee2b3a414a4e11a7956 cea4333c6fff70e6248efee2b3a414a4e11a7956 CI 1617686684 +1000 rebase -i (finish): returning to refs/heads/master +0000000000000000000000000000000000000000 3627f93f3cc779dc2f99484fb8ffa49953e43b2f CI 1643188754 +1100 commit (initial): first commit +3627f93f3cc779dc2f99484fb8ffa49953e43b2f 3627f93f3cc779dc2f99484fb8ffa49953e43b2f CI 1643188754 +1100 checkout: moving from master to develop +3627f93f3cc779dc2f99484fb8ffa49953e43b2f 9a6521a3788b4d9e679b1709130ff8dc3f73ab18 CI 1643188754 +1100 commit: first commit on develop +9a6521a3788b4d9e679b1709130ff8dc3f73ab18 3627f93f3cc779dc2f99484fb8ffa49953e43b2f CI 1643188754 +1100 checkout: moving from develop to master +3627f93f3cc779dc2f99484fb8ffa49953e43b2f 69f11ae88c8712fe38ffd0fe9ff9df05371500a6 CI 1643188754 +1100 commit: first commit on master +69f11ae88c8712fe38ffd0fe9ff9df05371500a6 9a6521a3788b4d9e679b1709130ff8dc3f73ab18 CI 1643188754 +1100 checkout: moving from master to develop +9a6521a3788b4d9e679b1709130ff8dc3f73ab18 a8381c9130b03aef530b60b5a4546b93dc59ae12 CI 1643188754 +1100 commit: second commit on develop +a8381c9130b03aef530b60b5a4546b93dc59ae12 69f11ae88c8712fe38ffd0fe9ff9df05371500a6 CI 1643188754 +1100 checkout: moving from develop to master +69f11ae88c8712fe38ffd0fe9ff9df05371500a6 8d3bd1cbd5560c759c78a948bc0d24acb9cfae73 CI 1643188754 +1100 commit: second commit on master +8d3bd1cbd5560c759c78a948bc0d24acb9cfae73 a8381c9130b03aef530b60b5a4546b93dc59ae12 CI 1643188754 +1100 checkout: moving from master to develop +a8381c9130b03aef530b60b5a4546b93dc59ae12 7095508e3cd0fd40572f8e711170db38ef2342d7 CI 1643188754 +1100 commit: third commit on develop +7095508e3cd0fd40572f8e711170db38ef2342d7 8d3bd1cbd5560c759c78a948bc0d24acb9cfae73 CI 1643188754 +1100 checkout: moving from develop to master +8d3bd1cbd5560c759c78a948bc0d24acb9cfae73 3e1706cdf670f5641be0715178471abfc9ed1748 CI 1643188754 +1100 commit: third commit on master +3e1706cdf670f5641be0715178471abfc9ed1748 7095508e3cd0fd40572f8e711170db38ef2342d7 CI 1643188754 +1100 checkout: moving from master to develop +7095508e3cd0fd40572f8e711170db38ef2342d7 e957aaf2eef0c03a9052b472d4862d9ee684c3e5 CI 1643188754 +1100 commit: fourth commit on develop +e957aaf2eef0c03a9052b472d4862d9ee684c3e5 3e1706cdf670f5641be0715178471abfc9ed1748 CI 1643188754 +1100 checkout: moving from develop to master +3e1706cdf670f5641be0715178471abfc9ed1748 f5067da83b48f8588edce682fd2715a575f34373 CI 1643188754 +1100 commit: fourth commit on master +f5067da83b48f8588edce682fd2715a575f34373 e957aaf2eef0c03a9052b472d4862d9ee684c3e5 CI 1643188756 +1100 rebase -i (start): checkout develop +e957aaf2eef0c03a9052b472d4862d9ee684c3e5 f5067da83b48f8588edce682fd2715a575f34373 CI 1643188757 +1100 rebase -i (abort): updating HEAD +f5067da83b48f8588edce682fd2715a575f34373 e957aaf2eef0c03a9052b472d4862d9ee684c3e5 CI 1643188758 +1100 rebase -i (start): checkout develop +e957aaf2eef0c03a9052b472d4862d9ee684c3e5 42597904331c82f6d5c8c902755c8dfa5767ea95 CI 1643188766 +1100 rebase -i (continue): first commit on master +42597904331c82f6d5c8c902755c8dfa5767ea95 42597904331c82f6d5c8c902755c8dfa5767ea95 CI 1643188766 +1100 rebase -i (finish): returning to refs/heads/master diff --git a/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/develop b/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/develop index b13e37d19..06dc78ae9 100644 --- a/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/develop +++ b/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/develop @@ -1,5 +1,5 @@ -0000000000000000000000000000000000000000 ad12d265183742f1aa5ee2fe7c09dc3c16482d7d CI 1617686669 +1000 branch: Created from HEAD -ad12d265183742f1aa5ee2fe7c09dc3c16482d7d f3dedf7d7a26a03a4e996a1d8e9bf2754609717f CI 1617686669 +1000 commit: first commit on develop -f3dedf7d7a26a03a4e996a1d8e9bf2754609717f f9ad2a0d946867abbd3228fc132afef480287c27 CI 1617686669 +1000 commit: second commit on develop -f9ad2a0d946867abbd3228fc132afef480287c27 7f8238e605705a3b485f653df7779e43a41f4e76 CI 1617686669 +1000 commit: third commit on develop -7f8238e605705a3b485f653df7779e43a41f4e76 e23041d5ff1f243faf319cc5e48e6d57384e4f37 CI 1617686669 +1000 commit: fourth commit on develop +0000000000000000000000000000000000000000 3627f93f3cc779dc2f99484fb8ffa49953e43b2f CI 1643188754 +1100 branch: Created from HEAD +3627f93f3cc779dc2f99484fb8ffa49953e43b2f 9a6521a3788b4d9e679b1709130ff8dc3f73ab18 CI 1643188754 +1100 commit: first commit on develop +9a6521a3788b4d9e679b1709130ff8dc3f73ab18 a8381c9130b03aef530b60b5a4546b93dc59ae12 CI 1643188754 +1100 commit: second commit on develop +a8381c9130b03aef530b60b5a4546b93dc59ae12 7095508e3cd0fd40572f8e711170db38ef2342d7 CI 1643188754 +1100 commit: third commit on develop +7095508e3cd0fd40572f8e711170db38ef2342d7 e957aaf2eef0c03a9052b472d4862d9ee684c3e5 CI 1643188754 +1100 commit: fourth commit on develop diff --git a/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/master b/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/master index 9be1c0f29..1856f13e4 100644 --- a/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/master +++ b/test/integration/branchRebase/expected/.git_keep/logs/refs/heads/master @@ -1,6 +1,6 @@ -0000000000000000000000000000000000000000 ad12d265183742f1aa5ee2fe7c09dc3c16482d7d CI 1617686669 +1000 commit (initial): first commit -ad12d265183742f1aa5ee2fe7c09dc3c16482d7d 5bc64a01e7b980e97f714fba45aa18f0eef726fb CI 1617686669 +1000 commit: first commit on master -5bc64a01e7b980e97f714fba45aa18f0eef726fb 8c464cc13c9ad8d00604ec9b646e2b2187706287 CI 1617686669 +1000 commit: second commit on master -8c464cc13c9ad8d00604ec9b646e2b2187706287 9ec3d8f323be49b6cc9036db9299fb0ae6764440 CI 1617686669 +1000 commit: third commit on master -9ec3d8f323be49b6cc9036db9299fb0ae6764440 4d34b8913eb0991ca63f8f7877ab8408e1511b3d CI 1617686669 +1000 commit: fourth commit on master -4d34b8913eb0991ca63f8f7877ab8408e1511b3d cea4333c6fff70e6248efee2b3a414a4e11a7956 CI 1617686684 +1000 rebase -i (finish): refs/heads/master onto e23041d5ff1f243faf319cc5e48e6d57384e4f37 +0000000000000000000000000000000000000000 3627f93f3cc779dc2f99484fb8ffa49953e43b2f CI 1643188754 +1100 commit (initial): first commit +3627f93f3cc779dc2f99484fb8ffa49953e43b2f 69f11ae88c8712fe38ffd0fe9ff9df05371500a6 CI 1643188754 +1100 commit: first commit on master +69f11ae88c8712fe38ffd0fe9ff9df05371500a6 8d3bd1cbd5560c759c78a948bc0d24acb9cfae73 CI 1643188754 +1100 commit: second commit on master +8d3bd1cbd5560c759c78a948bc0d24acb9cfae73 3e1706cdf670f5641be0715178471abfc9ed1748 CI 1643188754 +1100 commit: third commit on master +3e1706cdf670f5641be0715178471abfc9ed1748 f5067da83b48f8588edce682fd2715a575f34373 CI 1643188754 +1100 commit: fourth commit on master +f5067da83b48f8588edce682fd2715a575f34373 42597904331c82f6d5c8c902755c8dfa5767ea95 CI 1643188766 +1100 rebase -i (finish): refs/heads/master onto e957aaf2eef0c03a9052b472d4862d9ee684c3e5 diff --git a/test/integration/branchRebase/expected/.git_keep/objects/2e/83133d8d6b88c588de66c3ff8405501b5215b4 b/test/integration/branchRebase/expected/.git_keep/objects/2e/83133d8d6b88c588de66c3ff8405501b5215b4 new file mode 100644 index 000000000..7cc212f4e Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/2e/83133d8d6b88c588de66c3ff8405501b5215b4 differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/36/27f93f3cc779dc2f99484fb8ffa49953e43b2f b/test/integration/branchRebase/expected/.git_keep/objects/36/27f93f3cc779dc2f99484fb8ffa49953e43b2f new file mode 100644 index 000000000..cd608da68 Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/36/27f93f3cc779dc2f99484fb8ffa49953e43b2f differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/3e/1706cdf670f5641be0715178471abfc9ed1748 b/test/integration/branchRebase/expected/.git_keep/objects/3e/1706cdf670f5641be0715178471abfc9ed1748 new file mode 100644 index 000000000..fcb375444 --- /dev/null +++ b/test/integration/branchRebase/expected/.git_keep/objects/3e/1706cdf670f5641be0715178471abfc9ed1748 @@ -0,0 +1,2 @@ +xA +0a9II2cLiR#x| ^VawL 6ZP HVg.Tl*&9Re@)%Xᄡ t! GkC8:T?s՗u/SОPu`.? \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/42/1b29bba240f23ea39e216bb0873cd4012624b5 b/test/integration/branchRebase/expected/.git_keep/objects/42/1b29bba240f23ea39e216bb0873cd4012624b5 new file mode 100644 index 000000000..6ad1bf9cd Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/42/1b29bba240f23ea39e216bb0873cd4012624b5 differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/42/597904331c82f6d5c8c902755c8dfa5767ea95 b/test/integration/branchRebase/expected/.git_keep/objects/42/597904331c82f6d5c8c902755c8dfa5767ea95 new file mode 100644 index 000000000..1c62a2241 Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/42/597904331c82f6d5c8c902755c8dfa5767ea95 differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/4d/34b8913eb0991ca63f8f7877ab8408e1511b3d b/test/integration/branchRebase/expected/.git_keep/objects/4d/34b8913eb0991ca63f8f7877ab8408e1511b3d deleted file mode 100644 index d790078a9..000000000 --- a/test/integration/branchRebase/expected/.git_keep/objects/4d/34b8913eb0991ca63f8f7877ab8408e1511b3d +++ /dev/null @@ -1,2 +0,0 @@ -xA -0E]diDzd2iJM[nR :{KV53 Yj%Zb֔$MҐ]z$(EsĠԓM\7'C?/H-w8zljomuއm\A \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/5b/c64a01e7b980e97f714fba45aa18f0eef726fb b/test/integration/branchRebase/expected/.git_keep/objects/5b/c64a01e7b980e97f714fba45aa18f0eef726fb deleted file mode 100644 index 3cfa68687..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/5b/c64a01e7b980e97f714fba45aa18f0eef726fb and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/5d/874a902548f753e50944827e572a7470aa9731 b/test/integration/branchRebase/expected/.git_keep/objects/5d/874a902548f753e50944827e572a7470aa9731 new file mode 100644 index 000000000..9633a4077 Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/5d/874a902548f753e50944827e572a7470aa9731 differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/69/f11ae88c8712fe38ffd0fe9ff9df05371500a6 b/test/integration/branchRebase/expected/.git_keep/objects/69/f11ae88c8712fe38ffd0fe9ff9df05371500a6 new file mode 100644 index 000000000..71f3f42ac Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/69/f11ae88c8712fe38ffd0fe9ff9df05371500a6 differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/70/95508e3cd0fd40572f8e711170db38ef2342d7 b/test/integration/branchRebase/expected/.git_keep/objects/70/95508e3cd0fd40572f8e711170db38ef2342d7 new file mode 100644 index 000000000..7d14e4e29 Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/70/95508e3cd0fd40572f8e711170db38ef2342d7 differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/72/df4fceb0be99deb091ece3f501ef80b39a876a b/test/integration/branchRebase/expected/.git_keep/objects/72/df4fceb0be99deb091ece3f501ef80b39a876a deleted file mode 100644 index 19670ff5a..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/72/df4fceb0be99deb091ece3f501ef80b39a876a and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/7f/8238e605705a3b485f653df7779e43a41f4e76 b/test/integration/branchRebase/expected/.git_keep/objects/7f/8238e605705a3b485f653df7779e43a41f4e76 deleted file mode 100644 index 616d411b3..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/7f/8238e605705a3b485f653df7779e43a41f4e76 and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/8c/464cc13c9ad8d00604ec9b646e2b2187706287 b/test/integration/branchRebase/expected/.git_keep/objects/8c/464cc13c9ad8d00604ec9b646e2b2187706287 deleted file mode 100644 index f968d937a..000000000 --- a/test/integration/branchRebase/expected/.git_keep/objects/8c/464cc13c9ad8d00604ec9b646e2b2187706287 +++ /dev/null @@ -1,2 +0,0 @@ -xA -0E]d3)]8A4-xTKy6m5RXH2(=g9EW1W$8AQ}J!Ystn8enѲj1 fHvj6Ku/:Cm@ \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/8d/3bd1cbd5560c759c78a948bc0d24acb9cfae73 b/test/integration/branchRebase/expected/.git_keep/objects/8d/3bd1cbd5560c759c78a948bc0d24acb9cfae73 new file mode 100644 index 000000000..5083d8258 --- /dev/null +++ b/test/integration/branchRebase/expected/.git_keep/objects/8d/3bd1cbd5560c759c78a948bc0d24acb9cfae73 @@ -0,0 +1,2 @@ +xM +0F]dDz$A4-x=r-٠#ifv piA*FYWAYae3_-MA" \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/9e/c3d8f323be49b6cc9036db9299fb0ae6764440 b/test/integration/branchRebase/expected/.git_keep/objects/9e/c3d8f323be49b6cc9036db9299fb0ae6764440 deleted file mode 100644 index 01e70b1cc..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/9e/c3d8f323be49b6cc9036db9299fb0ae6764440 and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/a8/381c9130b03aef530b60b5a4546b93dc59ae12 b/test/integration/branchRebase/expected/.git_keep/objects/a8/381c9130b03aef530b60b5a4546b93dc59ae12 new file mode 100644 index 000000000..8ed23d6aa --- /dev/null +++ b/test/integration/branchRebase/expected/.git_keep/objects/a8/381c9130b03aef530b60b5a4546b93dc59ae12 @@ -0,0 +1,2 @@ +x +0E]+f/H&DyLQMQ| pWs67ɺ̑4ɸD\5Q7i_gRkdwlkbƠ#&@)#귶0yISi[B,V;OuSWO)mˠ-P-/8|? \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/ad/12d265183742f1aa5ee2fe7c09dc3c16482d7d b/test/integration/branchRebase/expected/.git_keep/objects/ad/12d265183742f1aa5ee2fe7c09dc3c16482d7d deleted file mode 100644 index b62cda205..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/ad/12d265183742f1aa5ee2fe7c09dc3c16482d7d and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/b0/35336bbf5c79e3b4b8a0f997c32124f4f40800 b/test/integration/branchRebase/expected/.git_keep/objects/b0/35336bbf5c79e3b4b8a0f997c32124f4f40800 deleted file mode 100644 index a66fe422e..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/b0/35336bbf5c79e3b4b8a0f997c32124f4f40800 and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/b7/8c0e574778464b30f8b9979e8c8ec5693675f4 b/test/integration/branchRebase/expected/.git_keep/objects/b7/8c0e574778464b30f8b9979e8c8ec5693675f4 deleted file mode 100644 index 43d870c43..000000000 --- a/test/integration/branchRebase/expected/.git_keep/objects/b7/8c0e574778464b30f8b9979e8c8ec5693675f4 +++ /dev/null @@ -1,2 +0,0 @@ -x -0D=+ IAI6d%R{SYفeawgGî^ЁeX+$З͠9B{ތDǘKD%IoΫBm]s}UUxNBR(+'S^5GYo (! \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/cc/52f7d833c761b3b11a5fa1ae76ba9aba2edd6f b/test/integration/branchRebase/expected/.git_keep/objects/cc/52f7d833c761b3b11a5fa1ae76ba9aba2edd6f new file mode 100644 index 000000000..0e775a5f8 --- /dev/null +++ b/test/integration/branchRebase/expected/.git_keep/objects/cc/52f7d833c761b3b11a5fa1ae76ba9aba2edd6f @@ -0,0 +1,6 @@ +xQ +@ DS +,* +FڦBw#)MRdLPw5' d􂎤)aP:;%6 +zs[h/6|ZW}7 +ձ/K(V(+'g,7c01 \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/ce/a4333c6fff70e6248efee2b3a414a4e11a7956 b/test/integration/branchRebase/expected/.git_keep/objects/ce/a4333c6fff70e6248efee2b3a414a4e11a7956 deleted file mode 100644 index 098dc2c53..000000000 --- a/test/integration/branchRebase/expected/.git_keep/objects/ce/a4333c6fff70e6248efee2b3a414a4e11a7956 +++ /dev/null @@ -1,3 +0,0 @@ -x} -0=).H/=1t )m߀en k)K%©DDaG!J*= 1lq)ZΆYȑQ iOv6&j8ue{%riZ,Em??xُ_ -%]>:? \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/e2/3041d5ff1f243faf319cc5e48e6d57384e4f37 b/test/integration/branchRebase/expected/.git_keep/objects/e2/3041d5ff1f243faf319cc5e48e6d57384e4f37 deleted file mode 100644 index 85b628171..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/e2/3041d5ff1f243faf319cc5e48e6d57384e4f37 and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/e9/57aaf2eef0c03a9052b472d4862d9ee684c3e5 b/test/integration/branchRebase/expected/.git_keep/objects/e9/57aaf2eef0c03a9052b472d4862d9ee684c3e5 new file mode 100644 index 000000000..52090e26c Binary files /dev/null and b/test/integration/branchRebase/expected/.git_keep/objects/e9/57aaf2eef0c03a9052b472d4862d9ee684c3e5 differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/f3/dedf7d7a26a03a4e996a1d8e9bf2754609717f b/test/integration/branchRebase/expected/.git_keep/objects/f3/dedf7d7a26a03a4e996a1d8e9bf2754609717f deleted file mode 100644 index fac587698..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/f3/dedf7d7a26a03a4e996a1d8e9bf2754609717f and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/f4/5c1fa29d9ae6806790e81763d978db2d957be2 b/test/integration/branchRebase/expected/.git_keep/objects/f4/5c1fa29d9ae6806790e81763d978db2d957be2 deleted file mode 100644 index db4c3f161..000000000 Binary files a/test/integration/branchRebase/expected/.git_keep/objects/f4/5c1fa29d9ae6806790e81763d978db2d957be2 and /dev/null differ diff --git a/test/integration/branchRebase/expected/.git_keep/objects/f5/067da83b48f8588edce682fd2715a575f34373 b/test/integration/branchRebase/expected/.git_keep/objects/f5/067da83b48f8588edce682fd2715a575f34373 new file mode 100644 index 000000000..47681b580 --- /dev/null +++ b/test/integration/branchRebase/expected/.git_keep/objects/f5/067da83b48f8588edce682fd2715a575f34373 @@ -0,0 +1,4 @@ +xA +0E]$cL +"BW=FP4% x? O2`"t,GQtЈ|XSZbR{5C8b5Eqd +G[Ki4?K,̀ޓ5pFZjˠlû 1A \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/objects/f9/ad2a0d946867abbd3228fc132afef480287c27 b/test/integration/branchRebase/expected/.git_keep/objects/f9/ad2a0d946867abbd3228fc132afef480287c27 deleted file mode 100644 index 13214c210..000000000 --- a/test/integration/branchRebase/expected/.git_keep/objects/f9/ad2a0d946867abbd3228fc132afef480287c27 +++ /dev/null @@ -1,2 +0,0 @@ -xM -0F]$ ]fmJ-x[=ރoj|`9M"9t EBZGcvjM)5Y;BlJQS4*~[`4MĄGV;OuSWORA[[mU_.@a \ No newline at end of file diff --git a/test/integration/branchRebase/expected/.git_keep/refs/heads/develop b/test/integration/branchRebase/expected/.git_keep/refs/heads/develop index 9974cd2cd..d77f4721f 100644 --- a/test/integration/branchRebase/expected/.git_keep/refs/heads/develop +++ b/test/integration/branchRebase/expected/.git_keep/refs/heads/develop @@ -1 +1 @@ -e23041d5ff1f243faf319cc5e48e6d57384e4f37 +e957aaf2eef0c03a9052b472d4862d9ee684c3e5 diff --git a/test/integration/branchRebase/expected/.git_keep/refs/heads/master b/test/integration/branchRebase/expected/.git_keep/refs/heads/master index f0fe9cada..567f6507a 100644 --- a/test/integration/branchRebase/expected/.git_keep/refs/heads/master +++ b/test/integration/branchRebase/expected/.git_keep/refs/heads/master @@ -1 +1 @@ -cea4333c6fff70e6248efee2b3a414a4e11a7956 +42597904331c82f6d5c8c902755c8dfa5767ea95 diff --git a/test/integration/branchRebase/expected/directory/file b/test/integration/branchRebase/expected/directory/file index 180cf8328..df6b0d2bc 100644 --- a/test/integration/branchRebase/expected/directory/file +++ b/test/integration/branchRebase/expected/directory/file @@ -1 +1 @@ -test2 +test3 diff --git a/test/integration/branchRebase/expected/file1 b/test/integration/branchRebase/expected/file1 index dcd348507..5d874a902 100644 --- a/test/integration/branchRebase/expected/file1 +++ b/test/integration/branchRebase/expected/file1 @@ -60,4 +60,4 @@ once upon a time there was a cat ... ... ... -once upon a time there was another cat +once upon a time there was another dog diff --git a/test/integration/branchRebase/recording.json b/test/integration/branchRebase/recording.json index b88bc8ff1..c0da4c16a 100644 --- a/test/integration/branchRebase/recording.json +++ b/test/integration/branchRebase/recording.json @@ -1 +1 @@ -{"KeyEvents":[{"Timestamp":468,"Mod":0,"Key":259,"Ch":0},{"Timestamp":860,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1196,"Mod":0,"Key":256,"Ch":114},{"Timestamp":1869,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2984,"Mod":0,"Key":27,"Ch":0},{"Timestamp":4124,"Mod":0,"Key":256,"Ch":114},{"Timestamp":4764,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5756,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6308,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6572,"Mod":0,"Key":259,"Ch":0},{"Timestamp":7204,"Mod":0,"Key":256,"Ch":100},{"Timestamp":7468,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7668,"Mod":0,"Key":256,"Ch":100},{"Timestamp":8492,"Mod":0,"Key":258,"Ch":0},{"Timestamp":8604,"Mod":0,"Key":256,"Ch":100},{"Timestamp":8957,"Mod":0,"Key":260,"Ch":0},{"Timestamp":9132,"Mod":0,"Key":260,"Ch":0},{"Timestamp":9556,"Mod":0,"Key":256,"Ch":32},{"Timestamp":10067,"Mod":0,"Key":256,"Ch":32},{"Timestamp":10692,"Mod":0,"Key":256,"Ch":32},{"Timestamp":10989,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11164,"Mod":0,"Key":256,"Ch":32},{"Timestamp":11596,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11996,"Mod":0,"Key":256,"Ch":32},{"Timestamp":12493,"Mod":0,"Key":256,"Ch":32},{"Timestamp":12757,"Mod":0,"Key":257,"Ch":0},{"Timestamp":12900,"Mod":0,"Key":256,"Ch":32},{"Timestamp":13245,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13380,"Mod":0,"Key":256,"Ch":32},{"Timestamp":13788,"Mod":0,"Key":256,"Ch":32},{"Timestamp":14516,"Mod":0,"Key":13,"Ch":13},{"Timestamp":15604,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file +{"KeyEvents":[{"Timestamp":617,"Mod":0,"Key":259,"Ch":0},{"Timestamp":922,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1176,"Mod":0,"Key":256,"Ch":114},{"Timestamp":1512,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2797,"Mod":0,"Key":27,"Ch":0},{"Timestamp":3632,"Mod":0,"Key":256,"Ch":114},{"Timestamp":4032,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4760,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5105,"Mod":0,"Key":259,"Ch":0},{"Timestamp":5376,"Mod":0,"Key":259,"Ch":0},{"Timestamp":5721,"Mod":0,"Key":256,"Ch":100},{"Timestamp":5992,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6186,"Mod":0,"Key":256,"Ch":100},{"Timestamp":6401,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6592,"Mod":0,"Key":256,"Ch":100},{"Timestamp":6840,"Mod":0,"Key":260,"Ch":0},{"Timestamp":7105,"Mod":0,"Key":260,"Ch":0},{"Timestamp":7612,"Mod":0,"Key":256,"Ch":32},{"Timestamp":7922,"Mod":0,"Key":258,"Ch":0},{"Timestamp":8160,"Mod":0,"Key":256,"Ch":32},{"Timestamp":8680,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9080,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9464,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9808,"Mod":0,"Key":256,"Ch":32},{"Timestamp":10040,"Mod":0,"Key":257,"Ch":0},{"Timestamp":10372,"Mod":0,"Key":256,"Ch":32},{"Timestamp":11249,"Mod":0,"Key":13,"Ch":13},{"Timestamp":12088,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/branchRebase/setup.sh b/test/integration/branchRebase/setup.sh index 0fa1d98a1..a1f14b455 100644 --- a/test/integration/branchRebase/setup.sh +++ b/test/integration/branchRebase/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/branchReset/setup.sh b/test/integration/branchReset/setup.sh index 0fa1d98a1..a1f14b455 100644 --- a/test/integration/branchReset/setup.sh +++ b/test/integration/branchReset/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/branchSuggestions/setup.sh b/test/integration/branchSuggestions/setup.sh index da9b9bddb..d67fa9291 100644 --- a/test/integration/branchSuggestions/setup.sh +++ b/test/integration/branchSuggestions/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/cherryPicking/setup.sh b/test/integration/cherryPicking/setup.sh index d60a9a7e1..1a913412f 100644 --- a/test/integration/cherryPicking/setup.sh +++ b/test/integration/cherryPicking/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/commit/setup.sh b/test/integration/commit/setup.sh index 21fb3fefb..c6c6a9271 100644 --- a/test/integration/commit/setup.sh +++ b/test/integration/commit/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/commitsNewBranch/setup.sh b/test/integration/commitsNewBranch/setup.sh index 9b9e01321..4e35cf543 100644 --- a/test/integration/commitsNewBranch/setup.sh +++ b/test/integration/commitsNewBranch/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/commitsRevert/expected/.git_keep/COMMIT_EDITMSG b/test/integration/commitsRevert/expected/.git_keep/COMMIT_EDITMSG index 6c493ff74..a04a59a53 100644 --- a/test/integration/commitsRevert/expected/.git_keep/COMMIT_EDITMSG +++ b/test/integration/commitsRevert/expected/.git_keep/COMMIT_EDITMSG @@ -1 +1 @@ -file2 +revert commit diff --git a/test/integration/commitsRevert/expected/.git_keep/HEAD b/test/integration/commitsRevert/expected/.git_keep/HEAD index a9a1d2137..cb089cd89 100644 --- a/test/integration/commitsRevert/expected/.git_keep/HEAD +++ b/test/integration/commitsRevert/expected/.git_keep/HEAD @@ -1 +1 @@ -ref: refs/heads/newbranch +ref: refs/heads/master diff --git a/test/integration/commitsRevert/expected/.git_keep/index b/test/integration/commitsRevert/expected/.git_keep/index index 9ce0d669e..fabd517d8 100644 Binary files a/test/integration/commitsRevert/expected/.git_keep/index and b/test/integration/commitsRevert/expected/.git_keep/index differ diff --git a/test/integration/commitsRevert/expected/.git_keep/logs/HEAD b/test/integration/commitsRevert/expected/.git_keep/logs/HEAD index f477d1f97..d82a2c6a4 100644 --- a/test/integration/commitsRevert/expected/.git_keep/logs/HEAD +++ b/test/integration/commitsRevert/expected/.git_keep/logs/HEAD @@ -1,4 +1,5 @@ -0000000000000000000000000000000000000000 3e9204493881a93d230aa85233c4721b6e83268d CI 1617674261 +1000 commit (initial): file0 -3e9204493881a93d230aa85233c4721b6e83268d 592b94b2f4dc9b7831efbd76f711f6232448730e CI 1617674261 +1000 commit: file1 -592b94b2f4dc9b7831efbd76f711f6232448730e 0842967ae6f16d85995f718922256cea03d10b4e CI 1617674261 +1000 commit: file2 -0842967ae6f16d85995f718922256cea03d10b4e 592b94b2f4dc9b7831efbd76f711f6232448730e CI 1617674264 +1000 checkout: moving from master to newbranch +0000000000000000000000000000000000000000 9eaae8f342ca71c060b760870a715a6303905935 CI 1643148217 +1100 commit (initial): file0 +9eaae8f342ca71c060b760870a715a6303905935 1727eeb6864e52a8967a1a494099359dbdfcc235 CI 1643148217 +1100 commit: file1 +1727eeb6864e52a8967a1a494099359dbdfcc235 b781ffd3aa940dde39f73c9149b67e2b128de085 CI 1643148217 +1100 commit: file2 +b781ffd3aa940dde39f73c9149b67e2b128de085 ab15072795e72a2061bc40060494c3ca2138b297 CI 1643148219 +1100 revert: Revert "file1" +ab15072795e72a2061bc40060494c3ca2138b297 7e03c9a9538a907c936de5c9a2154707b9ee541c CI 1643148227 +1100 commit (amend): revert commit diff --git a/test/integration/commitsRevert/expected/.git_keep/logs/refs/heads/master b/test/integration/commitsRevert/expected/.git_keep/logs/refs/heads/master index 42c7cd4a3..d82a2c6a4 100644 --- a/test/integration/commitsRevert/expected/.git_keep/logs/refs/heads/master +++ b/test/integration/commitsRevert/expected/.git_keep/logs/refs/heads/master @@ -1,3 +1,5 @@ -0000000000000000000000000000000000000000 3e9204493881a93d230aa85233c4721b6e83268d CI 1617674261 +1000 commit (initial): file0 -3e9204493881a93d230aa85233c4721b6e83268d 592b94b2f4dc9b7831efbd76f711f6232448730e CI 1617674261 +1000 commit: file1 -592b94b2f4dc9b7831efbd76f711f6232448730e 0842967ae6f16d85995f718922256cea03d10b4e CI 1617674261 +1000 commit: file2 +0000000000000000000000000000000000000000 9eaae8f342ca71c060b760870a715a6303905935 CI 1643148217 +1100 commit (initial): file0 +9eaae8f342ca71c060b760870a715a6303905935 1727eeb6864e52a8967a1a494099359dbdfcc235 CI 1643148217 +1100 commit: file1 +1727eeb6864e52a8967a1a494099359dbdfcc235 b781ffd3aa940dde39f73c9149b67e2b128de085 CI 1643148217 +1100 commit: file2 +b781ffd3aa940dde39f73c9149b67e2b128de085 ab15072795e72a2061bc40060494c3ca2138b297 CI 1643148219 +1100 revert: Revert "file1" +ab15072795e72a2061bc40060494c3ca2138b297 7e03c9a9538a907c936de5c9a2154707b9ee541c CI 1643148227 +1100 commit (amend): revert commit diff --git a/test/integration/commitsRevert/expected/.git_keep/logs/refs/heads/newbranch b/test/integration/commitsRevert/expected/.git_keep/logs/refs/heads/newbranch deleted file mode 100644 index 16b1076b3..000000000 --- a/test/integration/commitsRevert/expected/.git_keep/logs/refs/heads/newbranch +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 592b94b2f4dc9b7831efbd76f711f6232448730e CI 1617674264 +1000 branch: Created from 592b94b2f4dc9b7831efbd76f711f6232448730e diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/08/42967ae6f16d85995f718922256cea03d10b4e b/test/integration/commitsRevert/expected/.git_keep/objects/08/42967ae6f16d85995f718922256cea03d10b4e deleted file mode 100644 index 932545521..000000000 --- a/test/integration/commitsRevert/expected/.git_keep/objects/08/42967ae6f16d85995f718922256cea03d10b4e +++ /dev/null @@ -1,2 +0,0 @@ -xK -0@] ]`DnǫۺBK?DlLUna֢mp@QX}X͞yw;$,)j*DK=䝘do^Enu[♧sT?u"h~R9 \ No newline at end of file diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/17/27eeb6864e52a8967a1a494099359dbdfcc235 b/test/integration/commitsRevert/expected/.git_keep/objects/17/27eeb6864e52a8967a1a494099359dbdfcc235 new file mode 100644 index 000000000..59a430624 Binary files /dev/null and b/test/integration/commitsRevert/expected/.git_keep/objects/17/27eeb6864e52a8967a1a494099359dbdfcc235 differ diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/3a/1ee58e5736049ad5b9266715d3642614816c1f b/test/integration/commitsRevert/expected/.git_keep/objects/3a/1ee58e5736049ad5b9266715d3642614816c1f new file mode 100644 index 000000000..2d68564f5 --- /dev/null +++ b/test/integration/commitsRevert/expected/.git_keep/objects/3a/1ee58e5736049ad5b9266715d3642614816c1f @@ -0,0 +1,2 @@ +x+)JMU03c040031QHI5`ֶww.hT[H +$x~5(;rբW-9 \ No newline at end of file diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/3e/9204493881a93d230aa85233c4721b6e83268d b/test/integration/commitsRevert/expected/.git_keep/objects/3e/9204493881a93d230aa85233c4721b6e83268d deleted file mode 100644 index 4084ff6cb..000000000 --- a/test/integration/commitsRevert/expected/.git_keep/objects/3e/9204493881a93d230aa85233c4721b6e83268d +++ /dev/null @@ -1,3 +0,0 @@ -xA -0Fa9cb@U4АR"x|{nպtK| -!$bharTi;i/mmnM; ]QIInʲd+ \ No newline at end of file diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/59/2b94b2f4dc9b7831efbd76f711f6232448730e b/test/integration/commitsRevert/expected/.git_keep/objects/59/2b94b2f4dc9b7831efbd76f711f6232448730e deleted file mode 100644 index 44166baee..000000000 --- a/test/integration/commitsRevert/expected/.git_keep/objects/59/2b94b2f4dc9b7831efbd76f711f6232448730e +++ /dev/null @@ -1,2 +0,0 @@ -x1 -0 @>B$GBCH\#t붮sP3_CbբVi$*RX"Gpn`P )DrDȖ 9W'Oʺ/vm}x`H"2+Yϩfr7͋8 \ No newline at end of file diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/7e/03c9a9538a907c936de5c9a2154707b9ee541c b/test/integration/commitsRevert/expected/.git_keep/objects/7e/03c9a9538a907c936de5c9a2154707b9ee541c new file mode 100644 index 000000000..fea4aa6be --- /dev/null +++ b/test/integration/commitsRevert/expected/.git_keep/objects/7e/03c9a9538a907c936de5c9a2154707b9ee541c @@ -0,0 +1,4 @@ +x}K +1]$O'AY1`!DmQxym6@IytDf:M҄Xl +ȡ-"4)c[Z1Y +PMHX%TV縮y;Χ3 * pDRt?5O_|3< \ No newline at end of file diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/9e/aae8f342ca71c060b760870a715a6303905935 b/test/integration/commitsRevert/expected/.git_keep/objects/9e/aae8f342ca71c060b760870a715a6303905935 new file mode 100644 index 000000000..dfcaf39be Binary files /dev/null and b/test/integration/commitsRevert/expected/.git_keep/objects/9e/aae8f342ca71c060b760870a715a6303905935 differ diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/ab/15072795e72a2061bc40060494c3ca2138b297 b/test/integration/commitsRevert/expected/.git_keep/objects/ab/15072795e72a2061bc40060494c3ca2138b297 new file mode 100644 index 000000000..28a9eaf41 Binary files /dev/null and b/test/integration/commitsRevert/expected/.git_keep/objects/ab/15072795e72a2061bc40060494c3ca2138b297 differ diff --git a/test/integration/commitsRevert/expected/.git_keep/objects/b7/81ffd3aa940dde39f73c9149b67e2b128de085 b/test/integration/commitsRevert/expected/.git_keep/objects/b7/81ffd3aa940dde39f73c9149b67e2b128de085 new file mode 100644 index 000000000..c32d4b681 --- /dev/null +++ b/test/integration/commitsRevert/expected/.git_keep/objects/b7/81ffd3aa940dde39f73c9149b67e2b128de085 @@ -0,0 +1,2 @@ +xK +0@] & "tcL [Jo /I8Z֊r@ " F"9Pp).9JAk;44?}_VGQ_Q'=\9 \ No newline at end of file diff --git a/test/integration/commitsRevert/expected/.git_keep/refs/heads/master b/test/integration/commitsRevert/expected/.git_keep/refs/heads/master index 8cebe1d71..7edf473fd 100644 --- a/test/integration/commitsRevert/expected/.git_keep/refs/heads/master +++ b/test/integration/commitsRevert/expected/.git_keep/refs/heads/master @@ -1 +1 @@ -0842967ae6f16d85995f718922256cea03d10b4e +7e03c9a9538a907c936de5c9a2154707b9ee541c diff --git a/test/integration/commitsRevert/expected/.git_keep/refs/heads/newbranch b/test/integration/commitsRevert/expected/.git_keep/refs/heads/newbranch deleted file mode 100644 index dd2e9bde8..000000000 --- a/test/integration/commitsRevert/expected/.git_keep/refs/heads/newbranch +++ /dev/null @@ -1 +0,0 @@ -592b94b2f4dc9b7831efbd76f711f6232448730e diff --git a/test/integration/rebaseReword/expected/file2 b/test/integration/commitsRevert/expected/file2 similarity index 100% rename from test/integration/rebaseReword/expected/file2 rename to test/integration/commitsRevert/expected/file2 diff --git a/test/integration/commitsRevert/recording.json b/test/integration/commitsRevert/recording.json index 829287a3a..80125e840 100644 --- a/test/integration/commitsRevert/recording.json +++ b/test/integration/commitsRevert/recording.json @@ -1 +1 @@ -{"KeyEvents":[{"Timestamp":471,"Mod":0,"Key":259,"Ch":0},{"Timestamp":694,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1015,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1703,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2503,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2622,"Mod":0,"Key":256,"Ch":101},{"Timestamp":2653,"Mod":0,"Key":256,"Ch":119},{"Timestamp":2783,"Mod":0,"Key":256,"Ch":98},{"Timestamp":2878,"Mod":0,"Key":256,"Ch":114},{"Timestamp":2950,"Mod":0,"Key":256,"Ch":97},{"Timestamp":2990,"Mod":0,"Key":256,"Ch":110},{"Timestamp":3110,"Mod":0,"Key":256,"Ch":99},{"Timestamp":3151,"Mod":0,"Key":256,"Ch":104},{"Timestamp":3398,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4102,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file +{"KeyEvents":[{"Timestamp":614,"Mod":0,"Key":259,"Ch":0},{"Timestamp":749,"Mod":0,"Key":259,"Ch":0},{"Timestamp":980,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1219,"Mod":0,"Key":256,"Ch":116},{"Timestamp":1854,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2469,"Mod":0,"Key":257,"Ch":0},{"Timestamp":2725,"Mod":0,"Key":257,"Ch":0},{"Timestamp":3892,"Mod":0,"Key":256,"Ch":114},{"Timestamp":5421,"Mod":2,"Key":21,"Ch":21},{"Timestamp":5854,"Mod":2,"Key":21,"Ch":21},{"Timestamp":6197,"Mod":2,"Key":21,"Ch":21},{"Timestamp":6469,"Mod":2,"Key":21,"Ch":21},{"Timestamp":6781,"Mod":2,"Key":21,"Ch":21},{"Timestamp":7078,"Mod":2,"Key":21,"Ch":21},{"Timestamp":7907,"Mod":0,"Key":256,"Ch":114},{"Timestamp":7989,"Mod":0,"Key":256,"Ch":101},{"Timestamp":8148,"Mod":0,"Key":256,"Ch":118},{"Timestamp":8285,"Mod":0,"Key":256,"Ch":101},{"Timestamp":8356,"Mod":0,"Key":256,"Ch":114},{"Timestamp":8493,"Mod":0,"Key":256,"Ch":116},{"Timestamp":8604,"Mod":0,"Key":256,"Ch":32},{"Timestamp":8700,"Mod":0,"Key":256,"Ch":102},{"Timestamp":9125,"Mod":0,"Key":127,"Ch":127},{"Timestamp":9189,"Mod":0,"Key":256,"Ch":99},{"Timestamp":9317,"Mod":0,"Key":256,"Ch":111},{"Timestamp":9509,"Mod":0,"Key":256,"Ch":109},{"Timestamp":9637,"Mod":0,"Key":256,"Ch":109},{"Timestamp":9685,"Mod":0,"Key":256,"Ch":105},{"Timestamp":9781,"Mod":0,"Key":256,"Ch":116},{"Timestamp":9918,"Mod":0,"Key":13,"Ch":13},{"Timestamp":11110,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/commitsRevert/setup.sh b/test/integration/commitsRevert/setup.sh index 9b9e01321..4e35cf543 100644 --- a/test/integration/commitsRevert/setup.sh +++ b/test/integration/commitsRevert/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/confirmQuit/config/config.yml b/test/integration/confirmQuit/config/config.yml new file mode 100644 index 000000000..7227c0b5d --- /dev/null +++ b/test/integration/confirmQuit/config/config.yml @@ -0,0 +1,2 @@ +disableStartupPopups: true +confirmOnQuit: true diff --git a/test/integration/confirmQuit/expected/.git_keep/COMMIT_EDITMSG b/test/integration/confirmQuit/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..3829ab872 --- /dev/null +++ b/test/integration/confirmQuit/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +myfile1 diff --git a/test/integration/confirmQuit/expected/.git_keep/FETCH_HEAD b/test/integration/confirmQuit/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/rebaseReword/expected/.git_keep/HEAD b/test/integration/confirmQuit/expected/.git_keep/HEAD similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/HEAD rename to test/integration/confirmQuit/expected/.git_keep/HEAD diff --git a/test/integration/confirmQuit/expected/.git_keep/config b/test/integration/confirmQuit/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/confirmQuit/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/confirmQuit/expected/.git_keep/description b/test/integration/confirmQuit/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/confirmQuit/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/confirmQuit/expected/.git_keep/index b/test/integration/confirmQuit/expected/.git_keep/index new file mode 100644 index 000000000..e70e72626 Binary files /dev/null and b/test/integration/confirmQuit/expected/.git_keep/index differ diff --git a/test/integration/confirmQuit/expected/.git_keep/info/exclude b/test/integration/confirmQuit/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/confirmQuit/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/confirmQuit/expected/.git_keep/logs/HEAD b/test/integration/confirmQuit/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..d1743395b --- /dev/null +++ b/test/integration/confirmQuit/expected/.git_keep/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 544efed4e669ec3bd64b44799175bffac95035f5 CI 1642239015 +1100 commit (initial): myfile1 diff --git a/test/integration/confirmQuit/expected/.git_keep/logs/refs/heads/master b/test/integration/confirmQuit/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..d1743395b --- /dev/null +++ b/test/integration/confirmQuit/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 544efed4e669ec3bd64b44799175bffac95035f5 CI 1642239015 +1100 commit (initial): myfile1 diff --git a/test/integration/confirmQuit/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/confirmQuit/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/confirmQuit/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/confirmQuit/expected/.git_keep/objects/54/4efed4e669ec3bd64b44799175bffac95035f5 b/test/integration/confirmQuit/expected/.git_keep/objects/54/4efed4e669ec3bd64b44799175bffac95035f5 new file mode 100644 index 000000000..0b96ac6d5 Binary files /dev/null and b/test/integration/confirmQuit/expected/.git_keep/objects/54/4efed4e669ec3bd64b44799175bffac95035f5 differ diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/confirmQuit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 rename to test/integration/confirmQuit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 diff --git a/test/integration/confirmQuit/expected/.git_keep/refs/heads/master b/test/integration/confirmQuit/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..c8e696e5a --- /dev/null +++ b/test/integration/confirmQuit/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +544efed4e669ec3bd64b44799175bffac95035f5 diff --git a/test/integration/commitsRevert/expected/file1 b/test/integration/confirmQuit/expected/myfile1 similarity index 100% rename from test/integration/commitsRevert/expected/file1 rename to test/integration/confirmQuit/expected/myfile1 diff --git a/test/integration/confirmQuit/recording.json b/test/integration/confirmQuit/recording.json new file mode 100644 index 000000000..fea04afb7 --- /dev/null +++ b/test/integration/confirmQuit/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":466,"Mod":0,"Key":256,"Ch":113},{"Timestamp":890,"Mod":0,"Key":13,"Ch":13}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/confirmQuit/setup.sh b/test/integration/confirmQuit/setup.sh new file mode 100644 index 000000000..ee8276237 --- /dev/null +++ b/test/integration/confirmQuit/setup.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" diff --git a/test/integration/confirmQuit/test.json b/test/integration/confirmQuit/test.json new file mode 100644 index 000000000..0386cab89 --- /dev/null +++ b/test/integration/confirmQuit/test.json @@ -0,0 +1,4 @@ +{ + "description": "quit with a confirm", + "speed": 20 +} diff --git a/test/integration/customCommands/setup.sh b/test/integration/customCommands/setup.sh index 54ec0d96e..fbd02e952 100644 --- a/test/integration/customCommands/setup.sh +++ b/test/integration/customCommands/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/customCommandsComplex/config/config.yml b/test/integration/customCommandsComplex/config/config.yml new file mode 100644 index 000000000..69072c2c7 --- /dev/null +++ b/test/integration/customCommandsComplex/config/config.yml @@ -0,0 +1,31 @@ +disableStartupPopups: true +customCommands: + - key: 'N' + description: 'Add file' + context: 'localBranches' + command: 'echo "{{index .PromptResponses 0}} {{index .PromptResponses 1}} {{index .PromptResponses 2}} {{ .SelectedLocalBranch.Name }}" > output.txt' + loadingText: 'Running custom command...' + prompts: + - type: 'menuFromCommand' + title: 'Title' + 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}}" + - type: 'menu' + title: 'yes or no' + options: + - name: 'no' + value: 'false' + - name: 'yes' + value: 'true' +gui: + theme: + activeBorderColor: + - green + - bold + SelectedRangeBgcolor: + - reverse diff --git a/test/integration/customCommandsComplex/expected/.git_keep/COMMIT_EDITMSG b/test/integration/customCommandsComplex/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..9daeafb98 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +test diff --git a/test/integration/customCommandsComplex/expected/.git_keep/FETCH_HEAD b/test/integration/customCommandsComplex/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/stashFiles/expected/.git_keep/HEAD b/test/integration/customCommandsComplex/expected/.git_keep/HEAD similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/HEAD rename to test/integration/customCommandsComplex/expected/.git_keep/HEAD diff --git a/test/integration/customCommandsComplex/expected/.git_keep/config b/test/integration/customCommandsComplex/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/customCommandsComplex/expected/.git_keep/description b/test/integration/customCommandsComplex/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/customCommandsComplex/expected/.git_keep/index b/test/integration/customCommandsComplex/expected/.git_keep/index new file mode 100644 index 000000000..005c7dd34 Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/index differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/info/exclude b/test/integration/customCommandsComplex/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/customCommandsComplex/expected/.git_keep/logs/HEAD b/test/integration/customCommandsComplex/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..605138dc1 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/logs/HEAD @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 ab38b1ca116f77648925d952e731f419db360cdb CI 1642201096 +1100 commit (initial): myfile1 +ab38b1ca116f77648925d952e731f419db360cdb 4fdfedfd9d406506be8b02f5b863dbc08d43cc9f CI 1642201096 +1100 commit: myfile2 +4fdfedfd9d406506be8b02f5b863dbc08d43cc9f 7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 CI 1642201096 +1100 commit: myfile3 +7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 f708d3e3819470a69f6c8562ff1e68eef02f8cac CI 1642201096 +1100 commit: myfile4 +f708d3e3819470a69f6c8562ff1e68eef02f8cac 5428838691c97ac192c8b8e1c3f573d8541a94b6 CI 1642201104 +1100 commit: test diff --git a/test/integration/customCommandsComplex/expected/.git_keep/logs/refs/heads/master b/test/integration/customCommandsComplex/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..605138dc1 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 ab38b1ca116f77648925d952e731f419db360cdb CI 1642201096 +1100 commit (initial): myfile1 +ab38b1ca116f77648925d952e731f419db360cdb 4fdfedfd9d406506be8b02f5b863dbc08d43cc9f CI 1642201096 +1100 commit: myfile2 +4fdfedfd9d406506be8b02f5b863dbc08d43cc9f 7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 CI 1642201096 +1100 commit: myfile3 +7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 f708d3e3819470a69f6c8562ff1e68eef02f8cac CI 1642201096 +1100 commit: myfile4 +f708d3e3819470a69f6c8562ff1e68eef02f8cac 5428838691c97ac192c8b8e1c3f573d8541a94b6 CI 1642201104 +1100 commit: test diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/05/3cf208ac3728c36c6ed86f2a03a1fb72a8e6bc b/test/integration/customCommandsComplex/expected/.git_keep/objects/05/3cf208ac3728c36c6ed86f2a03a1fb72a8e6bc new file mode 100644 index 000000000..161576b97 Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/05/3cf208ac3728c36c6ed86f2a03a1fb72a8e6bc differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/customCommandsComplex/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/customCommandsComplex/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 rename to test/integration/customCommandsComplex/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce b/test/integration/customCommandsComplex/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce new file mode 100644 index 000000000..0a734f981 Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 b/test/integration/customCommandsComplex/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 new file mode 100644 index 000000000..31ae3f5ba Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/4f/dfedfd9d406506be8b02f5b863dbc08d43cc9f b/test/integration/customCommandsComplex/expected/.git_keep/objects/4f/dfedfd9d406506be8b02f5b863dbc08d43cc9f new file mode 100644 index 000000000..5bcc5c659 Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/4f/dfedfd9d406506be8b02f5b863dbc08d43cc9f differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/54/28838691c97ac192c8b8e1c3f573d8541a94b6 b/test/integration/customCommandsComplex/expected/.git_keep/objects/54/28838691c97ac192c8b8e1c3f573d8541a94b6 new file mode 100644 index 000000000..bd598d7ab Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/54/28838691c97ac192c8b8e1c3f573d8541a94b6 differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/7d/b446a082f8c10183f1f27178698f07f3750b6b b/test/integration/customCommandsComplex/expected/.git_keep/objects/7d/b446a082f8c10183f1f27178698f07f3750b6b new file mode 100644 index 000000000..6de444c0f Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/7d/b446a082f8c10183f1f27178698f07f3750b6b differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/7d/d93a4be3d27d40fbe791d6d77e0d2fedc4d785 b/test/integration/customCommandsComplex/expected/.git_keep/objects/7d/d93a4be3d27d40fbe791d6d77e0d2fedc4d785 new file mode 100644 index 000000000..a96d76d40 Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/7d/d93a4be3d27d40fbe791d6d77e0d2fedc4d785 differ diff --git a/test/integration/stashFiles/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/customCommandsComplex/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 rename to test/integration/customCommandsComplex/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/customCommandsComplex/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/ab/38b1ca116f77648925d952e731f419db360cdb b/test/integration/customCommandsComplex/expected/.git_keep/objects/ab/38b1ca116f77648925d952e731f419db360cdb new file mode 100644 index 000000000..3de7c78a7 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/objects/ab/38b1ca116f77648925d952e731f419db360cdb @@ -0,0 +1,2 @@ +xA +0@ѮsL:#)1P!")#tyS5[˥*`5df 9T:KL⧽qzm[ @#a/p%Btg='MξeY.,, \ No newline at end of file diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 b/test/integration/customCommandsComplex/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 new file mode 100644 index 000000000..d39fa7d2f Binary files /dev/null and b/test/integration/customCommandsComplex/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 differ diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/customCommandsComplex/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b rename to test/integration/customCommandsComplex/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b diff --git a/test/integration/customCommandsComplex/expected/.git_keep/objects/f7/08d3e3819470a69f6c8562ff1e68eef02f8cac b/test/integration/customCommandsComplex/expected/.git_keep/objects/f7/08d3e3819470a69f6c8562ff1e68eef02f8cac new file mode 100644 index 000000000..010f8e879 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/objects/f7/08d3e3819470a69f6c8562ff1e68eef02f8cac @@ -0,0 +1,2 @@ +xA +0@Q9E$I"BW=F`R"~◵[Lt껪u1*Ҍa>FvC!nHfYcBaQA\U)$q&c88KY"s؞QrӾuy*39 \ No newline at end of file diff --git a/test/integration/customCommandsComplex/expected/.git_keep/refs/heads/master b/test/integration/customCommandsComplex/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..8d1ca4f04 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +5428838691c97ac192c8b8e1c3f573d8541a94b6 diff --git a/test/integration/rebaseReword/expected/file1 b/test/integration/customCommandsComplex/expected/myfile1 similarity index 100% rename from test/integration/rebaseReword/expected/file1 rename to test/integration/customCommandsComplex/expected/myfile1 diff --git a/test/integration/customCommandsComplex/expected/myfile2 b/test/integration/customCommandsComplex/expected/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/rebaseReword/expected/file3 b/test/integration/customCommandsComplex/expected/myfile3 similarity index 100% rename from test/integration/rebaseReword/expected/file3 rename to test/integration/customCommandsComplex/expected/myfile3 diff --git a/test/integration/customCommandsComplex/expected/myfile4 b/test/integration/customCommandsComplex/expected/myfile4 new file mode 100644 index 000000000..d234c5e05 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/myfile4 @@ -0,0 +1 @@ +test4 diff --git a/test/integration/customCommandsComplex/expected/output.txt b/test/integration/customCommandsComplex/expected/output.txt new file mode 100644 index 000000000..7db446a08 --- /dev/null +++ b/test/integration/customCommandsComplex/expected/output.txt @@ -0,0 +1 @@ +myfile2 Branch: #master haha true master diff --git a/test/integration/customCommandsComplex/recording.json b/test/integration/customCommandsComplex/recording.json new file mode 100644 index 000000000..8fdbd648c --- /dev/null +++ b/test/integration/customCommandsComplex/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":623,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1369,"Mod":0,"Key":256,"Ch":78},{"Timestamp":1904,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2033,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2328,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2848,"Mod":0,"Key":256,"Ch":32},{"Timestamp":3296,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3616,"Mod":0,"Key":127,"Ch":127},{"Timestamp":3824,"Mod":0,"Key":256,"Ch":104},{"Timestamp":3879,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3927,"Mod":0,"Key":256,"Ch":104},{"Timestamp":4000,"Mod":0,"Key":256,"Ch":97},{"Timestamp":4239,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4809,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5024,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5824,"Mod":0,"Key":260,"Ch":0},{"Timestamp":6079,"Mod":0,"Key":256,"Ch":32},{"Timestamp":6376,"Mod":0,"Key":256,"Ch":99},{"Timestamp":6591,"Mod":0,"Key":256,"Ch":116},{"Timestamp":6640,"Mod":0,"Key":256,"Ch":101},{"Timestamp":6816,"Mod":0,"Key":256,"Ch":115},{"Timestamp":6856,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7136,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7487,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":36}]} \ No newline at end of file diff --git a/test/integration/customCommandsComplex/setup.sh b/test/integration/customCommandsComplex/setup.sh new file mode 100644 index 000000000..0f364d18a --- /dev/null +++ b/test/integration/customCommandsComplex/setup.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" +echo test2 > myfile2 +git add . +git commit -am "myfile2" +echo test3 > myfile3 +git add . +git commit -am "myfile3" +echo test4 > myfile4 +git add . +git commit -am "myfile4" diff --git a/test/integration/customCommandsComplex/test.json b/test/integration/customCommandsComplex/test.json new file mode 100644 index 000000000..beac0e9ca --- /dev/null +++ b/test/integration/customCommandsComplex/test.json @@ -0,0 +1,4 @@ +{ + "description": "Invoke a custom command that creates a file, and then stage and commit that file. In this case we're using a more customised flow", + "speed": 5 +} diff --git a/test/integration/diffing/setup.sh b/test/integration/diffing/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/diffing/setup.sh +++ b/test/integration/diffing/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/diffing2/setup.sh b/test/integration/diffing2/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/diffing2/setup.sh +++ b/test/integration/diffing2/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/diffing3/setup.sh b/test/integration/diffing3/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/diffing3/setup.sh +++ b/test/integration/diffing3/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/discardFileChanges/setup.sh b/test/integration/discardFileChanges/setup.sh index df68833f9..82fa59475 100644 --- a/test/integration/discardFileChanges/setup.sh +++ b/test/integration/discardFileChanges/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/COMMIT_EDITMSG b/test/integration/discardOldFileChanges/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..7b27632fe --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +twoFiles diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/FETCH_HEAD b/test/integration/discardOldFileChanges/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/HEAD b/test/integration/discardOldFileChanges/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/ORIG_HEAD b/test/integration/discardOldFileChanges/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..db70e637d --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +7880a9728615a4d196df39600a0c8c71b40d96d6 diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/config b/test/integration/discardOldFileChanges/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/description b/test/integration/discardOldFileChanges/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/index b/test/integration/discardOldFileChanges/expected/.git_keep/index new file mode 100644 index 000000000..258123f26 Binary files /dev/null and b/test/integration/discardOldFileChanges/expected/.git_keep/index differ diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/info/exclude b/test/integration/discardOldFileChanges/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/logs/HEAD b/test/integration/discardOldFileChanges/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..cd7bc3c77 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/logs/HEAD @@ -0,0 +1,9 @@ +0000000000000000000000000000000000000000 b7a702b642978f2a9b1af9c1c67b22127af78c92 CI 1641697108 +1100 commit (initial): file0 +b7a702b642978f2a9b1af9c1c67b22127af78c92 7880a9728615a4d196df39600a0c8c71b40d96d6 CI 1641697108 +1100 commit: twoFiles +7880a9728615a4d196df39600a0c8c71b40d96d6 af6725ba23f43a286deff0747476d7874113df1e CI 1641697108 +1100 commit: file2 +af6725ba23f43a286deff0747476d7874113df1e af6725ba23f43a286deff0747476d7874113df1e CI 1641697111 +1100 rebase: updating HEAD +af6725ba23f43a286deff0747476d7874113df1e b7a702b642978f2a9b1af9c1c67b22127af78c92 CI 1641697111 +1100 rebase -i (start): checkout b7a702b642978f2a9b1af9c1c67b22127af78c92 +b7a702b642978f2a9b1af9c1c67b22127af78c92 7880a9728615a4d196df39600a0c8c71b40d96d6 CI 1641697111 +1100 rebase -i: fast-forward +7880a9728615a4d196df39600a0c8c71b40d96d6 d14505f281a54cda96fc5fb8cd4b4ee14bae6264 CI 1641697111 +1100 commit (amend): twoFiles +d14505f281a54cda96fc5fb8cd4b4ee14bae6264 225ad83faa797c1831a2bc956a21e2d472f21443 CI 1641697111 +1100 rebase -i (pick): file2 +225ad83faa797c1831a2bc956a21e2d472f21443 225ad83faa797c1831a2bc956a21e2d472f21443 CI 1641697111 +1100 rebase -i (finish): returning to refs/heads/master diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/logs/refs/heads/master b/test/integration/discardOldFileChanges/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..13413542d --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 b7a702b642978f2a9b1af9c1c67b22127af78c92 CI 1641697108 +1100 commit (initial): file0 +b7a702b642978f2a9b1af9c1c67b22127af78c92 7880a9728615a4d196df39600a0c8c71b40d96d6 CI 1641697108 +1100 commit: twoFiles +7880a9728615a4d196df39600a0c8c71b40d96d6 af6725ba23f43a286deff0747476d7874113df1e CI 1641697108 +1100 commit: file2 +af6725ba23f43a286deff0747476d7874113df1e 225ad83faa797c1831a2bc956a21e2d472f21443 CI 1641697111 +1100 rebase -i (finish): refs/heads/master onto b7a702b642978f2a9b1af9c1c67b22127af78c92 diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/objects/00/cbccfd35a05ef9373bba9d5633cf6e67f83dd5 b/test/integration/discardOldFileChanges/expected/.git_keep/objects/00/cbccfd35a05ef9373bba9d5633cf6e67f83dd5 new file mode 100644 index 000000000..fd478c4af Binary files /dev/null and b/test/integration/discardOldFileChanges/expected/.git_keep/objects/00/cbccfd35a05ef9373bba9d5633cf6e67f83dd5 differ diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/objects/0a/91dcf3772f7fd7409b3df04eb6ef177219303a b/test/integration/discardOldFileChanges/expected/.git_keep/objects/0a/91dcf3772f7fd7409b3df04eb6ef177219303a new file mode 100644 index 000000000..2794a5840 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/objects/0a/91dcf3772f7fd7409b3df04eb6ef177219303a @@ -0,0 +1 @@ +xj1DSW]iO+ \VD>s'C>?j҇n Wzd9Vc\3{Z3VeSD.>~-C >Eրi&*'&Ġ 폇%aZԨ*G)L"qvwl?V0̈^/w͙r~K \ No newline at end of file diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/objects/0c/db6daba7e25b6d6d10da326e0ab74401021370 b/test/integration/discardOldFileChanges/expected/.git_keep/objects/0c/db6daba7e25b6d6d10da326e0ab74401021370 new file mode 100644 index 000000000..798edd72c --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/objects/0c/db6daba7e25b6d6d10da326e0ab74401021370 @@ -0,0 +1,2 @@ +xA +0E]$DDzi2iK㛍{yZ\ .%xJNO|S{[{`Qx;1`7+ \ No newline at end of file diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/discardOldFileChanges/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 rename to test/integration/discardOldFileChanges/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/objects/d1/4505f281a54cda96fc5fb8cd4b4ee14bae6264 b/test/integration/discardOldFileChanges/expected/.git_keep/objects/d1/4505f281a54cda96fc5fb8cd4b4ee14bae6264 new file mode 100644 index 000000000..76e9da484 Binary files /dev/null and b/test/integration/discardOldFileChanges/expected/.git_keep/objects/d1/4505f281a54cda96fc5fb8cd4b4ee14bae6264 differ diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/discardOldFileChanges/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/discardOldFileChanges/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/discardOldFileChanges/expected/.git_keep/refs/heads/master b/test/integration/discardOldFileChanges/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..2ebac56f2 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +225ad83faa797c1831a2bc956a21e2d472f21443 diff --git a/test/integration/rebaseReword/expected/file0 b/test/integration/discardOldFileChanges/expected/file0 similarity index 100% rename from test/integration/rebaseReword/expected/file0 rename to test/integration/discardOldFileChanges/expected/file0 diff --git a/test/integration/discardOldFileChanges/expected/file1 b/test/integration/discardOldFileChanges/expected/file1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/file1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/discardOldFileChanges/expected/file2 b/test/integration/discardOldFileChanges/expected/file2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/file2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/discardOldFileChanges/expected/file3 b/test/integration/discardOldFileChanges/expected/file3 new file mode 100644 index 000000000..df6b0d2bc --- /dev/null +++ b/test/integration/discardOldFileChanges/expected/file3 @@ -0,0 +1 @@ +test3 diff --git a/test/integration/discardOldFileChanges/recording.json b/test/integration/discardOldFileChanges/recording.json new file mode 100644 index 000000000..0b9a99ec5 --- /dev/null +++ b/test/integration/discardOldFileChanges/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":635,"Mod":0,"Key":259,"Ch":0},{"Timestamp":899,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1227,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1571,"Mod":0,"Key":13,"Ch":13},{"Timestamp":1971,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2322,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2731,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3707,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/discardOldFileChanges/setup.sh b/test/integration/discardOldFileChanges/setup.sh new file mode 100644 index 000000000..5b592cabd --- /dev/null +++ b/test/integration/discardOldFileChanges/setup.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test0 > file0 +git add . +git commit -am file0 + +echo test1 > file1 +echo testZ > fileZ +git add . +git commit -am twoFiles + +echo test2 > file2 +git add . +git commit -am file2 + +echo test3 > file3 +git add . diff --git a/test/integration/discardOldFileChanges/test.json b/test/integration/discardOldFileChanges/test.json new file mode 100644 index 000000000..04a59a63c --- /dev/null +++ b/test/integration/discardOldFileChanges/test.json @@ -0,0 +1,4 @@ +{ + "description": "Discarding a single file from an old commit (does rebase in background to remove the file but retain the other one)", + "speed": 5 +} diff --git a/test/integration/filterPath/setup.sh b/test/integration/filterPath/setup.sh index e766b6bdb..460b35b25 100644 --- a/test/integration/filterPath/setup.sh +++ b/test/integration/filterPath/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/filterPath2/setup.sh b/test/integration/filterPath2/setup.sh index e766b6bdb..460b35b25 100644 --- a/test/integration/filterPath2/setup.sh +++ b/test/integration/filterPath2/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/filterPath3/setup.sh b/test/integration/filterPath3/setup.sh index e766b6bdb..460b35b25 100644 --- a/test/integration/filterPath3/setup.sh +++ b/test/integration/filterPath3/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/forcePush/setup.sh b/test/integration/forcePush/setup.sh index 79a9499d0..74192c316 100644 --- a/test/integration/forcePush/setup.sh +++ b/test/integration/forcePush/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/initialOpen/setup.sh b/test/integration/initialOpen/setup.sh index 84ad82d36..e7de1949f 100644 --- a/test/integration/initialOpen/setup.sh +++ b/test/integration/initialOpen/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/mergeConflictRevert/setup.sh b/test/integration/mergeConflictRevert/setup.sh index 4618aa37c..aa3eb68c7 100644 --- a/test/integration/mergeConflictRevert/setup.sh +++ b/test/integration/mergeConflictRevert/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/mergeConflictUndo/setup.sh b/test/integration/mergeConflictUndo/setup.sh index 0d9a57cf9..5182e5eae 100644 --- a/test/integration/mergeConflictUndo/setup.sh +++ b/test/integration/mergeConflictUndo/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/mergeConflicts/config/config.yml b/test/integration/mergeConflicts/config/config.yml new file mode 100644 index 000000000..718ebf70d --- /dev/null +++ b/test/integration/mergeConflicts/config/config.yml @@ -0,0 +1,5 @@ +disableStartupPopups: true +gui: + showFileTree: false +refresher: + refreshInterval: 1 diff --git a/test/integration/mergeConflicts/setup.sh b/test/integration/mergeConflicts/setup.sh index 0d9a57cf9..5182e5eae 100644 --- a/test/integration/mergeConflicts/setup.sh +++ b/test/integration/mergeConflicts/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/mergeConflictsFiltered/config/config.yml b/test/integration/mergeConflictsFiltered/config/config.yml new file mode 100644 index 000000000..718ebf70d --- /dev/null +++ b/test/integration/mergeConflictsFiltered/config/config.yml @@ -0,0 +1,5 @@ +disableStartupPopups: true +gui: + showFileTree: false +refresher: + refreshInterval: 1 diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/COMMIT_EDITMSG b/test/integration/mergeConflictsFiltered/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..80977d9d1 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1,40 @@ +Merge branch 'develop' into other_branch + +# Conflicts: +# directory/file +# directory/file2 +# file1 +# file3 +# file4 +# file5 +# +# It looks like you may be committing a merge. +# If this is not correct, please remove the file +# /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/mergeConflictsFiltered/actual/.git/MERGE_HEAD +# and try again. + + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# On branch other_branch +# All conflicts fixed but you are still merging. +# +# Changes to be committed: +# new file: cherrypicking1 +# new file: cherrypicking2 +# new file: cherrypicking3 +# new file: cherrypicking4 +# new file: cherrypicking5 +# new file: cherrypicking7 +# new file: cherrypicking8 +# new file: cherrypicking9 +# modified: directory/file +# modified: directory/file2 +# modified: file1 +# modified: file4 +# modified: file5 +# +# Untracked files: +# cherrypicking6 +# diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/FETCH_HEAD b/test/integration/mergeConflictsFiltered/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/HEAD b/test/integration/mergeConflictsFiltered/expected/.git_keep/HEAD new file mode 100644 index 000000000..904a2e296 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/other_branch diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/ORIG_HEAD b/test/integration/mergeConflictsFiltered/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..84de0034b --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/config b/test/integration/mergeConflictsFiltered/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/description b/test/integration/mergeConflictsFiltered/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/index b/test/integration/mergeConflictsFiltered/expected/.git_keep/index new file mode 100644 index 000000000..f455a20fa Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/index differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/info/exclude b/test/integration/mergeConflictsFiltered/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/HEAD b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..8d64a77dd --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/HEAD @@ -0,0 +1,33 @@ +0000000000000000000000000000000000000000 416ca3083a0651e1c8be3ad7e0dbe8547a62be8a CI 1643188552 +1100 commit (initial): first commit +416ca3083a0651e1c8be3ad7e0dbe8547a62be8a 416ca3083a0651e1c8be3ad7e0dbe8547a62be8a CI 1643188552 +1100 checkout: moving from master to feature/cherry-picking +416ca3083a0651e1c8be3ad7e0dbe8547a62be8a f47cc13dc21c72755ff2d96d0805837dbb028951 CI 1643188552 +1100 commit: first commit freshman year +f47cc13dc21c72755ff2d96d0805837dbb028951 34850e31f804a946d014b14443cf7387546877b0 CI 1643188552 +1100 commit: second commit subway eat fresh +34850e31f804a946d014b14443cf7387546877b0 66340a9343a65aec523bfc57bd5a870eb4e05959 CI 1643188552 +1100 commit: third commit fresh +66340a9343a65aec523bfc57bd5a870eb4e05959 f9c5f8e0789c2eb9a6e37ba82a5b34be739989ec CI 1643188552 +1100 commit: fourth commit cool +f9c5f8e0789c2eb9a6e37ba82a5b34be739989ec 4abe915d16bbc10f38b578390ff74c9ae62bf503 CI 1643188552 +1100 commit: fifth commit nice +4abe915d16bbc10f38b578390ff74c9ae62bf503 93d727583328b5ce8f717380d014b87bd7893222 CI 1643188552 +1100 commit: sixth commit haha +93d727583328b5ce8f717380d014b87bd7893222 951a2354eb5856644b0f1db74becc04c908beaa3 CI 1643188552 +1100 commit: seventh commit yeah +951a2354eb5856644b0f1db74becc04c908beaa3 1fb30058516518ac1579a8df132b0e4dade8e51d CI 1643188552 +1100 commit: eighth commit woo +1fb30058516518ac1579a8df132b0e4dade8e51d 1fb30058516518ac1579a8df132b0e4dade8e51d CI 1643188552 +1100 checkout: moving from feature/cherry-picking to develop +1fb30058516518ac1579a8df132b0e4dade8e51d 40844e90419651d425c0845ec6f7c64ff63ebf03 CI 1643188552 +1100 commit: first commit on develop +40844e90419651d425c0845ec6f7c64ff63ebf03 416ca3083a0651e1c8be3ad7e0dbe8547a62be8a CI 1643188552 +1100 checkout: moving from develop to master +416ca3083a0651e1c8be3ad7e0dbe8547a62be8a 165a3cfaf6a1d3d757fb7b1c509598a395108079 CI 1643188552 +1100 commit: first commit on master +165a3cfaf6a1d3d757fb7b1c509598a395108079 40844e90419651d425c0845ec6f7c64ff63ebf03 CI 1643188552 +1100 checkout: moving from master to develop +40844e90419651d425c0845ec6f7c64ff63ebf03 9c59217622ae74189d18f2992121f97dd28e966b CI 1643188552 +1100 commit: second commit on develop +9c59217622ae74189d18f2992121f97dd28e966b 165a3cfaf6a1d3d757fb7b1c509598a395108079 CI 1643188552 +1100 checkout: moving from develop to master +165a3cfaf6a1d3d757fb7b1c509598a395108079 454b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f CI 1643188552 +1100 commit: second commit on master +454b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f 9c59217622ae74189d18f2992121f97dd28e966b CI 1643188552 +1100 checkout: moving from master to develop +9c59217622ae74189d18f2992121f97dd28e966b ba211bf3464f9c0429483a83963c1c69e1f53d4e CI 1643188552 +1100 commit: third commit on develop +ba211bf3464f9c0429483a83963c1c69e1f53d4e 454b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f CI 1643188552 +1100 checkout: moving from develop to master +454b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f a83aac98467d005729bc9d80fe98abba47d41495 CI 1643188552 +1100 commit: third commit on master +a83aac98467d005729bc9d80fe98abba47d41495 ba211bf3464f9c0429483a83963c1c69e1f53d4e CI 1643188552 +1100 checkout: moving from master to develop +ba211bf3464f9c0429483a83963c1c69e1f53d4e 85aba0ff0b6e7c0a7f6180f9f6e7e8cdcb71ee3b CI 1643188552 +1100 commit: fourth commit on develop +85aba0ff0b6e7c0a7f6180f9f6e7e8cdcb71ee3b a83aac98467d005729bc9d80fe98abba47d41495 CI 1643188552 +1100 checkout: moving from develop to master +a83aac98467d005729bc9d80fe98abba47d41495 cd97d8e8ca03000f761f0e041cea0b6039923e70 CI 1643188552 +1100 commit: fourth commit on master +cd97d8e8ca03000f761f0e041cea0b6039923e70 cd97d8e8ca03000f761f0e041cea0b6039923e70 CI 1643188552 +1100 checkout: moving from master to base_branch +cd97d8e8ca03000f761f0e041cea0b6039923e70 ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab CI 1643188552 +1100 commit: file +ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab CI 1643188552 +1100 checkout: moving from base_branch to other_branch +ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab CI 1643188552 +1100 checkout: moving from other_branch to base_branch +ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab 06d48b81c12e9c1a3cc2704c0db337639a8cdf85 CI 1643188552 +1100 commit: file changed +06d48b81c12e9c1a3cc2704c0db337639a8cdf85 ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab CI 1643188552 +1100 checkout: moving from base_branch to other_branch +ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab 4c1db169d59c4345aba213cb79934f4e38222f02 CI 1643188579 +1100 commit (merge): Merge branch 'develop' into other_branch diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/base_branch b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/base_branch new file mode 100644 index 000000000..1d7992ef4 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/base_branch @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 cd97d8e8ca03000f761f0e041cea0b6039923e70 CI 1643188552 +1100 branch: Created from HEAD +cd97d8e8ca03000f761f0e041cea0b6039923e70 ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab CI 1643188552 +1100 commit: file +ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab 06d48b81c12e9c1a3cc2704c0db337639a8cdf85 CI 1643188552 +1100 commit: file changed diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/develop b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/develop new file mode 100644 index 000000000..52fde82d1 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/develop @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 1fb30058516518ac1579a8df132b0e4dade8e51d CI 1643188552 +1100 branch: Created from HEAD +1fb30058516518ac1579a8df132b0e4dade8e51d 40844e90419651d425c0845ec6f7c64ff63ebf03 CI 1643188552 +1100 commit: first commit on develop +40844e90419651d425c0845ec6f7c64ff63ebf03 9c59217622ae74189d18f2992121f97dd28e966b CI 1643188552 +1100 commit: second commit on develop +9c59217622ae74189d18f2992121f97dd28e966b ba211bf3464f9c0429483a83963c1c69e1f53d4e CI 1643188552 +1100 commit: third commit on develop +ba211bf3464f9c0429483a83963c1c69e1f53d4e 85aba0ff0b6e7c0a7f6180f9f6e7e8cdcb71ee3b CI 1643188552 +1100 commit: fourth commit on develop diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/feature/cherry-picking b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/feature/cherry-picking new file mode 100644 index 000000000..a77478ee9 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/feature/cherry-picking @@ -0,0 +1,9 @@ +0000000000000000000000000000000000000000 416ca3083a0651e1c8be3ad7e0dbe8547a62be8a CI 1643188552 +1100 branch: Created from HEAD +416ca3083a0651e1c8be3ad7e0dbe8547a62be8a f47cc13dc21c72755ff2d96d0805837dbb028951 CI 1643188552 +1100 commit: first commit freshman year +f47cc13dc21c72755ff2d96d0805837dbb028951 34850e31f804a946d014b14443cf7387546877b0 CI 1643188552 +1100 commit: second commit subway eat fresh +34850e31f804a946d014b14443cf7387546877b0 66340a9343a65aec523bfc57bd5a870eb4e05959 CI 1643188552 +1100 commit: third commit fresh +66340a9343a65aec523bfc57bd5a870eb4e05959 f9c5f8e0789c2eb9a6e37ba82a5b34be739989ec CI 1643188552 +1100 commit: fourth commit cool +f9c5f8e0789c2eb9a6e37ba82a5b34be739989ec 4abe915d16bbc10f38b578390ff74c9ae62bf503 CI 1643188552 +1100 commit: fifth commit nice +4abe915d16bbc10f38b578390ff74c9ae62bf503 93d727583328b5ce8f717380d014b87bd7893222 CI 1643188552 +1100 commit: sixth commit haha +93d727583328b5ce8f717380d014b87bd7893222 951a2354eb5856644b0f1db74becc04c908beaa3 CI 1643188552 +1100 commit: seventh commit yeah +951a2354eb5856644b0f1db74becc04c908beaa3 1fb30058516518ac1579a8df132b0e4dade8e51d CI 1643188552 +1100 commit: eighth commit woo diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/master b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..7f0b5ea9c --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 416ca3083a0651e1c8be3ad7e0dbe8547a62be8a CI 1643188552 +1100 commit (initial): first commit +416ca3083a0651e1c8be3ad7e0dbe8547a62be8a 165a3cfaf6a1d3d757fb7b1c509598a395108079 CI 1643188552 +1100 commit: first commit on master +165a3cfaf6a1d3d757fb7b1c509598a395108079 454b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f CI 1643188552 +1100 commit: second commit on master +454b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f a83aac98467d005729bc9d80fe98abba47d41495 CI 1643188552 +1100 commit: third commit on master +a83aac98467d005729bc9d80fe98abba47d41495 cd97d8e8ca03000f761f0e041cea0b6039923e70 CI 1643188552 +1100 commit: fourth commit on master diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/other_branch b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/other_branch new file mode 100644 index 000000000..2534238d7 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/logs/refs/heads/other_branch @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab CI 1643188552 +1100 branch: Created from HEAD +ab7ecb7891c5f56480fba1ed3a6e0fa8e989efab 4c1db169d59c4345aba213cb79934f4e38222f02 CI 1643188579 +1100 commit (merge): Merge branch 'develop' into other_branch diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/06/d48b81c12e9c1a3cc2704c0db337639a8cdf85 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/06/d48b81c12e9c1a3cc2704c0db337639a8cdf85 new file mode 100644 index 000000000..984eeda3b Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/06/d48b81c12e9c1a3cc2704c0db337639a8cdf85 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/09/cbe8c6717c06a61876b7b641a46a62bf3c585d b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/09/cbe8c6717c06a61876b7b641a46a62bf3c585d new file mode 100644 index 000000000..8d42c4c9e Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/09/cbe8c6717c06a61876b7b641a46a62bf3c585d differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/16/5a3cfaf6a1d3d757fb7b1c509598a395108079 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/16/5a3cfaf6a1d3d757fb7b1c509598a395108079 new file mode 100644 index 000000000..56bdc0b18 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/16/5a3cfaf6a1d3d757fb7b1c509598a395108079 @@ -0,0 +1,2 @@ +xM +0F]$g"BW=4`F{֥vD -$ώEbBlA.΄̨ YodyIȻ0pƻ|nO{ gcVSMUY/+? \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/17/3a40ed58e33060166ccbfb7d0ccc0387be5f09 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/17/3a40ed58e33060166ccbfb7d0ccc0387be5f09 new file mode 100644 index 000000000..25389c9d6 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/17/3a40ed58e33060166ccbfb7d0ccc0387be5f09 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/17/4a8c9444cfa700682d74059d9fa9be5749242c b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/17/4a8c9444cfa700682d74059d9fa9be5749242c new file mode 100644 index 000000000..fd879a5f3 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/17/4a8c9444cfa700682d74059d9fa9be5749242c differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/18/f469bc737f6c2a589205e2ddefceb32a7cc3a7 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/18/f469bc737f6c2a589205e2ddefceb32a7cc3a7 new file mode 100644 index 000000000..9b8af5fe7 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/18/f469bc737f6c2a589205e2ddefceb32a7cc3a7 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/1b/9ae5f5dff631baaa180a30afd9983f83dc27ca b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/1b/9ae5f5dff631baaa180a30afd9983f83dc27ca new file mode 100644 index 000000000..2b02dc3d1 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/1b/9ae5f5dff631baaa180a30afd9983f83dc27ca differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/1f/b30058516518ac1579a8df132b0e4dade8e51d b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/1f/b30058516518ac1579a8df132b0e4dade8e51d new file mode 100644 index 000000000..82cc46d1c Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/1f/b30058516518ac1579a8df132b0e4dade8e51d differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/20/85c8dd0a80e95ed959e4db2ab98f66b970ad77 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/20/85c8dd0a80e95ed959e4db2ab98f66b970ad77 new file mode 100644 index 000000000..1cafb95f9 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/20/85c8dd0a80e95ed959e4db2ab98f66b970ad77 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/20/8e708af0dc73a41a09e79a7198ce56aef3df05 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/20/8e708af0dc73a41a09e79a7198ce56aef3df05 new file mode 100644 index 000000000..01e88b75d Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/20/8e708af0dc73a41a09e79a7198ce56aef3df05 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/21/78af7503938665881174069be4d48fa483e4af b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/21/78af7503938665881174069be4d48fa483e4af new file mode 100644 index 000000000..27c11bb26 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/21/78af7503938665881174069be4d48fa483e4af differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/22/b0fd807dd5e428c2d818aef6a2311d7c11e885 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/22/b0fd807dd5e428c2d818aef6a2311d7c11e885 new file mode 100644 index 000000000..991774643 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/22/b0fd807dd5e428c2d818aef6a2311d7c11e885 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/24/6f7487e08e6330ccbec4053e701145d53f64d4 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/24/6f7487e08e6330ccbec4053e701145d53f64d4 new file mode 100644 index 000000000..864410e1e Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/24/6f7487e08e6330ccbec4053e701145d53f64d4 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/2e/cced19ece4424e0d3f26eb3ea2ccb6bfeafaa8 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/2e/cced19ece4424e0d3f26eb3ea2ccb6bfeafaa8 new file mode 100644 index 000000000..ed74cea0a Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/2e/cced19ece4424e0d3f26eb3ea2ccb6bfeafaa8 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/32/d15fd4451b6693a93d6420c8af6cfc99348e71 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/32/d15fd4451b6693a93d6420c8af6cfc99348e71 new file mode 100644 index 000000000..4a9f30d37 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/32/d15fd4451b6693a93d6420c8af6cfc99348e71 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/34/850e31f804a946d014b14443cf7387546877b0 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/34/850e31f804a946d014b14443cf7387546877b0 new file mode 100644 index 000000000..5d4a913e6 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/34/850e31f804a946d014b14443cf7387546877b0 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/34/c74161eef968fc951cf170a011fa8abfeddbcd b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/34/c74161eef968fc951cf170a011fa8abfeddbcd new file mode 100644 index 000000000..e8d63bced Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/34/c74161eef968fc951cf170a011fa8abfeddbcd differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/36/e0ef3e52c6e29e64980c71defbab6064d2da8c b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/36/e0ef3e52c6e29e64980c71defbab6064d2da8c new file mode 100644 index 000000000..394c5a294 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/36/e0ef3e52c6e29e64980c71defbab6064d2da8c differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/38/08a710b52a152bb73805fe274e0d877cf61800 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/38/08a710b52a152bb73805fe274e0d877cf61800 new file mode 100644 index 000000000..74bc806ad --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/38/08a710b52a152bb73805fe274e0d877cf61800 @@ -0,0 +1,2 @@ +xA +0E]ATzI34)ɴMK0 |>o\a83BQ/h Eǜ}aAsjAwތ1O*B|V'5C/Q&c۬5x70| \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/40/844e90419651d425c0845ec6f7c64ff63ebf03 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/40/844e90419651d425c0845ec6f7c64ff63ebf03 new file mode 100644 index 000000000..cca824fa4 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/40/844e90419651d425c0845ec6f7c64ff63ebf03 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/41/6ca3083a0651e1c8be3ad7e0dbe8547a62be8a b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/41/6ca3083a0651e1c8be3ad7e0dbe8547a62be8a new file mode 100644 index 000000000..4f8246e30 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/41/6ca3083a0651e1c8be3ad7e0dbe8547a62be8a differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/45/4b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/45/4b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f new file mode 100644 index 000000000..a334cf498 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/45/4b176d9e82bc3e0b5d5fd93fa5ad3a58b7db5f @@ -0,0 +1,2 @@ +xM +0]$t"BW=d`FǓZʳAًuD[蝍.yq{^%6bIqҁg.(~Ga:ᲽEjѠDVZWG&iPW(|t[}i>? \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4a/be915d16bbc10f38b578390ff74c9ae62bf503 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4a/be915d16bbc10f38b578390ff74c9ae62bf503 new file mode 100644 index 000000000..8854dbe4e Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4a/be915d16bbc10f38b578390ff74c9ae62bf503 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4c/1db169d59c4345aba213cb79934f4e38222f02 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4c/1db169d59c4345aba213cb79934f4e38222f02 new file mode 100644 index 000000000..5e63d0027 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4c/1db169d59c4345aba213cb79934f4e38222f02 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4f/80ec0c7b09eeeb580d0c19947477c02bc88c25 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4f/80ec0c7b09eeeb580d0c19947477c02bc88c25 new file mode 100644 index 000000000..e0670d284 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/4f/80ec0c7b09eeeb580d0c19947477c02bc88c25 @@ -0,0 +1 @@ +x 0CvL\jVОW=M#Li#x| ^=xպv0N@))&{#C ;xʅԞ<;X9"|AMY9BF}\.T\x7О9gA./k+`nZjc> \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/78/3666de4acbb22a9efc205197667f5136118c54 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/78/3666de4acbb22a9efc205197667f5136118c54 new file mode 100644 index 000000000..c41ae5c62 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/78/3666de4acbb22a9efc205197667f5136118c54 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/85/aba0ff0b6e7c0a7f6180f9f6e7e8cdcb71ee3b b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/85/aba0ff0b6e7c0a7f6180f9f6e7e8cdcb71ee3b new file mode 100644 index 000000000..bdc31c789 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/85/aba0ff0b6e7c0a7f6180f9f6e7e8cdcb71ee3b @@ -0,0 +1,3 @@ +xA + E/G'FBV9#)$1Szzn}ey50H &|.ԧ̆F6-6HTSdy 2 ;DţMua0>mȢ]g:Ϲ*? +Y2M}OA7 \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/88/9b0fdfe5f2ae3d7df3066f3bc1e181fa712c8d b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/88/9b0fdfe5f2ae3d7df3066f3bc1e181fa712c8d new file mode 100644 index 000000000..d63de558b Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/88/9b0fdfe5f2ae3d7df3066f3bc1e181fa712c8d differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/88/c39cdc29c995f8e1a63ccd48e7bbd6d96cb8b8 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/88/c39cdc29c995f8e1a63ccd48e7bbd6d96cb8b8 new file mode 100644 index 000000000..ea7fa5303 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/88/c39cdc29c995f8e1a63ccd48e7bbd6d96cb8b8 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/90/a84fd62f8033027fab3e567a81d5ed2a6a71cd b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/90/a84fd62f8033027fab3e567a81d5ed2a6a71cd new file mode 100644 index 000000000..cdd5f8a93 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/90/a84fd62f8033027fab3e567a81d5ed2a6a71cd differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/91/65a12a95d3b2b9b8a0374de787af169b2c339e b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/91/65a12a95d3b2b9b8a0374de787af169b2c339e new file mode 100644 index 000000000..cc06d0590 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/91/65a12a95d3b2b9b8a0374de787af169b2c339e differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/93/d727583328b5ce8f717380d014b87bd7893222 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/93/d727583328b5ce8f717380d014b87bd7893222 new file mode 100644 index 000000000..4eb870a9e --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/93/d727583328b5ce8f717380d014b87bd7893222 @@ -0,0 +1,3 @@ +xK +0@]$DzI:!F-x奵Gݩo"q;}" +h8"BaɓZyWI32b)HvGL*^[ş^ tK^ 08D8#jzLuSW+*\Y}= \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/95/1a2354eb5856644b0f1db74becc04c908beaa3 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/95/1a2354eb5856644b0f1db74becc04c908beaa3 new file mode 100644 index 000000000..27ac335e9 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/95/1a2354eb5856644b0f1db74becc04c908beaa3 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/95/9d7a10da71acf97b17300b40a3b4f30903e09c b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/95/9d7a10da71acf97b17300b40a3b4f30903e09c new file mode 100644 index 000000000..1befd67ac Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/95/9d7a10da71acf97b17300b40a3b4f30903e09c differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/9c/59217622ae74189d18f2992121f97dd28e966b b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/9c/59217622ae74189d18f2992121f97dd28e966b new file mode 100644 index 000000000..174b52052 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/9c/59217622ae74189d18f2992121f97dd28e966b differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/9d/e8260b738a34a74533df54f2e404276aa96242 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/9d/e8260b738a34a74533df54f2e404276aa96242 new file mode 100644 index 000000000..ffc277c86 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/9d/e8260b738a34a74533df54f2e404276aa96242 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/a8/3aac98467d005729bc9d80fe98abba47d41495 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/a8/3aac98467d005729bc9d80fe98abba47d41495 new file mode 100644 index 000000000..20ba8aaf2 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/a8/3aac98467d005729bc9d80fe98abba47d41495 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ab/7ecb7891c5f56480fba1ed3a6e0fa8e989efab b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ab/7ecb7891c5f56480fba1ed3a6e0fa8e989efab new file mode 100644 index 000000000..92e71009c --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ab/7ecb7891c5f56480fba1ed3a6e0fa8e989efab @@ -0,0 +1,2 @@ +xA + @Ѯ=BѨ3PJ!cԑ& =~sn?o˶sx釪u@P A+֡f'Ř9Ԕ.-S%" ,*#xf5Om'{~em}XG"S]͋>"94 \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/af/a76754c933269d7cd45630a7184a20849dbe9c b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/af/a76754c933269d7cd45630a7184a20849dbe9c new file mode 100644 index 000000000..7302ca34e Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/af/a76754c933269d7cd45630a7184a20849dbe9c differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/b4/121e2d6aa156227b6541431ddfb8594904b520 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/b4/121e2d6aa156227b6541431ddfb8594904b520 new file mode 100644 index 000000000..4aa46eb8c Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/b4/121e2d6aa156227b6541431ddfb8594904b520 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ba/211bf3464f9c0429483a83963c1c69e1f53d4e b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ba/211bf3464f9c0429483a83963c1c69e1f53d4e new file mode 100644 index 000000000..b97085682 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ba/211bf3464f9c0429483a83963c1c69e1f53d4e differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/c1/dd146476a4a37fff75b88612a718281ea83b58 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/c1/dd146476a4a37fff75b88612a718281ea83b58 new file mode 100644 index 000000000..008bb1a65 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/c1/dd146476a4a37fff75b88612a718281ea83b58 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/cd/97d8e8ca03000f761f0e041cea0b6039923e70 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/cd/97d8e8ca03000f761f0e041cea0b6039923e70 new file mode 100644 index 000000000..a14b07295 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/cd/97d8e8ca03000f761f0e041cea0b6039923e70 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/d0/60f7226715ca55b04e91fad2b8aca01badd993 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/d0/60f7226715ca55b04e91fad2b8aca01badd993 new file mode 100644 index 000000000..ab357ec23 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/d0/60f7226715ca55b04e91fad2b8aca01badd993 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/d8/a7c50dcab42b2b62e5c77cdcece620d3964bd4 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/d8/a7c50dcab42b2b62e5c77cdcece620d3964bd4 new file mode 100644 index 000000000..198bff1ec Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/d8/a7c50dcab42b2b62e5c77cdcece620d3964bd4 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/da/72a6dd6fbaaa4a2803a3c867437ab81a1a99a0 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/da/72a6dd6fbaaa4a2803a3c867437ab81a1a99a0 new file mode 100644 index 000000000..af687b620 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/da/72a6dd6fbaaa4a2803a3c867437ab81a1a99a0 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/dc/d348507ba1da8f6479b9d964daa302b2fb9d9c b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/dc/d348507ba1da8f6479b9d964daa302b2fb9d9c new file mode 100644 index 000000000..74c919681 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/dc/d348507ba1da8f6479b9d964daa302b2fb9d9c @@ -0,0 +1 @@ +x 0C?3ƵV=iO=,8tW@h*&R$j*yʑs-ܜ8v)u㧱VH" P \ No newline at end of file diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e3/ae5c6d8407e8307b9bc77923be78c901408f6e b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e3/ae5c6d8407e8307b9bc77923be78c901408f6e new file mode 100644 index 000000000..3a7ee91ea Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e3/ae5c6d8407e8307b9bc77923be78c901408f6e differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e4/48ae5bf6371d80ebee24a22b6df341797a6511 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e4/48ae5bf6371d80ebee24a22b6df341797a6511 new file mode 100644 index 000000000..714e20cb7 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e4/48ae5bf6371d80ebee24a22b6df341797a6511 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e4/666ba294866d5c16f9afebcacf8f4adfee7439 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e4/666ba294866d5c16f9afebcacf8f4adfee7439 new file mode 100644 index 000000000..83998943a Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/e4/666ba294866d5c16f9afebcacf8f4adfee7439 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ea/a48cb1e3d47e1b8b8df47bdc248e991207cc3d b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ea/a48cb1e3d47e1b8b8df47bdc248e991207cc3d new file mode 100644 index 000000000..b047d6827 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/ea/a48cb1e3d47e1b8b8df47bdc248e991207cc3d differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/eb/90e8d7b137a1d89480c9b22fd03199da77c9c7 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/eb/90e8d7b137a1d89480c9b22fd03199da77c9c7 new file mode 100644 index 000000000..4992422c7 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/eb/90e8d7b137a1d89480c9b22fd03199da77c9c7 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f1/46c7f7b874778c1ad0cf9aebe45ec2427c7de2 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f1/46c7f7b874778c1ad0cf9aebe45ec2427c7de2 new file mode 100644 index 000000000..3688d63f9 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f1/46c7f7b874778c1ad0cf9aebe45ec2427c7de2 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f3/f762af4429ae89fa0dae3d0a5b500ca11630c4 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f3/f762af4429ae89fa0dae3d0a5b500ca11630c4 new file mode 100644 index 000000000..e9f9f0881 Binary files /dev/null and b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f3/f762af4429ae89fa0dae3d0a5b500ca11630c4 differ diff --git a/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f4/7cc13dc21c72755ff2d96d0805837dbb028951 b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f4/7cc13dc21c72755ff2d96d0805837dbb028951 new file mode 100644 index 000000000..9be5e4363 --- /dev/null +++ b/test/integration/mergeConflictsFiltered/expected/.git_keep/objects/f4/7cc13dc21c72755ff2d96d0805837dbb028951 @@ -0,0 +1,2 @@ +xA +1=s$dcaOlJwxk*h]jvr7䠊P싋TsRDyHýSmb`M^f!%uW,Ɖt[㆝.D甆іZTǟ)4> $1 + done +} + +mkdir directory +echo "test1" > directory/file +echo "test1" > directory/file2 + + +echo "Here is a story that has been told throuhg the ages" >> file1 + +git add file1 +git add directory +git commit -m "first commit" + +git checkout -b feature/cherry-picking + +echo "this is file number 1 that I'm going to cherry-pick" > cherrypicking1 +echo "this is file number 2 that I'm going to cherry-pick" > cherrypicking2 + +git add . + +git commit -am "first commit freshman year" + +echo "this is file number 3 that I'm going to cherry-pick" > cherrypicking3 + +git add . + +git commit -am "second commit subway eat fresh" + +echo "this is file number 4 that I'm going to cherry-pick" > cherrypicking4 + +git add . + +git commit -am "third commit fresh" + +echo "this is file number 5 that I'm going to cherry-pick" > cherrypicking5 + +git add . + +git commit -am "fourth commit cool" + +echo "this is file number 6 that I'm going to cherry-pick" > cherrypicking6 + +git add . + +git commit -am "fifth commit nice" + +echo "this is file number 7 that I'm going to cherry-pick" > cherrypicking7 + +git add . + +git commit -am "sixth commit haha" + +echo "this is file number 8 that I'm going to cherry-pick" > cherrypicking8 + +git add . + +git commit -am "seventh commit yeah" + +echo "this is file number 9 that I'm going to cherry-pick" > cherrypicking9 + +git add . + +git commit -am "eighth commit woo" + + +git checkout -b develop +echo "once upon a time there was a dog" >> file1 +add_spacing file1 +echo "once upon a time there was another dog" >> file1 +git add file1 +echo "test2" > directory/file +echo "test2" > directory/file2 +git add directory +git commit -m "first commit on develop" + + +git checkout master +echo "once upon a time there was a cat" >> file1 +add_spacing file1 +echo "once upon a time there was another cat" >> file1 +git add file1 +echo "test3" > directory/file +echo "test3" > directory/file2 +git add directory +git commit -m "first commit on master" + + +git checkout develop +echo "once upon a time there was a mouse" >> file3 +git add file3 +git commit -m "second commit on develop" + + +git checkout master +echo "once upon a time there was a horse" >> file3 +git add file3 +git commit -m "second commit on master" + + +git checkout develop +echo "once upon a time there was a mouse" >> file4 +git add file4 +git commit -m "third commit on develop" + + +git checkout master +echo "once upon a time there was a horse" >> file4 +git add file4 +git commit -m "third commit on master" + + +git checkout develop +echo "once upon a time there was a mouse" >> file5 +git add file5 +git commit -m "fourth commit on develop" + + +git checkout master +echo "once upon a time there was a horse" >> file5 +git add file5 +git commit -m "fourth commit on master" + + +# this is for the autostash feature + +git checkout -b base_branch + +echo "original1\noriginal2\noriginal3" > file +git add file +git commit -m "file" + +git checkout -b other_branch + +git checkout base_branch + +echo "new1\noriginal2\noriginal3" > file +git add file +git commit -m "file changed" + +git checkout other_branch diff --git a/test/integration/mergeConflictsFiltered/test.json b/test/integration/mergeConflictsFiltered/test.json new file mode 100644 index 000000000..7d6d3b9ca --- /dev/null +++ b/test/integration/mergeConflictsFiltered/test.json @@ -0,0 +1,4 @@ +{ + "description": "Verify that when we get merge conflicts we filter out any non-conflicted files", + "speed": 20 +} diff --git a/test/integration/mergeConflictsResolvedExternally/config/config.yml b/test/integration/mergeConflictsResolvedExternally/config/config.yml new file mode 100644 index 000000000..33b63939a --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/config/config.yml @@ -0,0 +1,6 @@ +disableStartupPopups: true +customCommands: + - key: 'N' + description: 'Resolve conflict' + context: 'files' + command: 'echo "master2" > file' diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/COMMIT_EDITMSG b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..9c1b1e853 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1,20 @@ +Merge branch 'master' into other + +# Conflicts: +# file +# +# It looks like you may be committing a merge. +# If this is not correct, please remove the file +# /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/mergeConflictsResolvedExternally/actual/.git/MERGE_HEAD +# and try again. + + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# On branch other +# All conflicts fixed but you are still merging. +# +# Changes to be committed: +# modified: file +# diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/FETCH_HEAD b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/HEAD b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/HEAD new file mode 100644 index 000000000..0ca960536 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/other diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/ORIG_HEAD b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..30d51fe1d --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +769c8b8d89700f6f196b8331159150746a839662 diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/config b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/description b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/index b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/index new file mode 100644 index 000000000..61de7270a Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/index differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/info/exclude b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/HEAD b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..e316f4ff1 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/HEAD @@ -0,0 +1,7 @@ +0000000000000000000000000000000000000000 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI 1642403134 +1100 commit (initial): master1 +03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI 1642403134 +1100 checkout: moving from master to other +03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 769c8b8d89700f6f196b8331159150746a839662 CI 1642403134 +1100 commit: other1 +769c8b8d89700f6f196b8331159150746a839662 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI 1642403134 +1100 checkout: moving from other to master +03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 691ee9e9d9c654c81214f56c514ff725f46cb9e4 CI 1642403134 +1100 commit: master2 +691ee9e9d9c654c81214f56c514ff725f46cb9e4 769c8b8d89700f6f196b8331159150746a839662 CI 1642403134 +1100 checkout: moving from master to other +769c8b8d89700f6f196b8331159150746a839662 f2df244fb87b6ba1d2ab484d76c66baba168a867 CI 1642403138 +1100 commit (merge): Merge branch 'master' into other diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/refs/heads/master b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..2e39e4fcd --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI 1642403134 +1100 commit (initial): master1 +03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 691ee9e9d9c654c81214f56c514ff725f46cb9e4 CI 1642403134 +1100 commit: master2 diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/refs/heads/other b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/refs/heads/other new file mode 100644 index 000000000..a462e4b21 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/logs/refs/heads/other @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI 1642403134 +1100 branch: Created from HEAD +03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 769c8b8d89700f6f196b8331159150746a839662 CI 1642403134 +1100 commit: other1 +769c8b8d89700f6f196b8331159150746a839662 f2df244fb87b6ba1d2ab484d76c66baba168a867 CI 1642403138 +1100 commit (merge): Merge branch 'master' into other diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/03/959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/03/959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 new file mode 100644 index 000000000..e91b55a9f --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/03/959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 @@ -0,0 +1,4 @@ +xA +1 @Q=E$v!"j) +X#<=Bȹą}TYbQ8%Hϸ .vӯ멧 +#{8!Ydܙ'&xR-k \ No newline at end of file diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/08/84a47e04257f4c85435a8b10ff4f15fffa63fc b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/08/84a47e04257f4c85435a8b10ff4f15fffa63fc new file mode 100644 index 000000000..20a13c218 Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/08/84a47e04257f4c85435a8b10ff4f15fffa63fc differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/0b/e6e80a67f6276c5ede28dd6b8fa8873f1b23c5 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/0b/e6e80a67f6276c5ede28dd6b8fa8873f1b23c5 new file mode 100644 index 000000000..eb74d7a88 Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/0b/e6e80a67f6276c5ede28dd6b8fa8873f1b23c5 differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/2d/8021ed8803ed6142d31b331850ef46246391a7 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/2d/8021ed8803ed6142d31b331850ef46246391a7 new file mode 100644 index 000000000..660150da8 Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/2d/8021ed8803ed6142d31b331850ef46246391a7 differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/53/502c7023f80c046a1b00b45614d5ffef8977d9 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/53/502c7023f80c046a1b00b45614d5ffef8977d9 new file mode 100644 index 000000000..fdb7cf9f9 Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/53/502c7023f80c046a1b00b45614d5ffef8977d9 differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/69/1ee9e9d9c654c81214f56c514ff725f46cb9e4 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/69/1ee9e9d9c654c81214f56c514ff725f46cb9e4 new file mode 100644 index 000000000..ebc29f7ce Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/69/1ee9e9d9c654c81214f56c514ff725f46cb9e4 differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/6b/03bc7537ecf00b48a0ea57ce1edf388ed3f1ad b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/6b/03bc7537ecf00b48a0ea57ce1edf388ed3f1ad new file mode 100644 index 000000000..9ccb24501 Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/6b/03bc7537ecf00b48a0ea57ce1edf388ed3f1ad differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/76/9c8b8d89700f6f196b8331159150746a839662 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/76/9c8b8d89700f6f196b8331159150746a839662 new file mode 100644 index 000000000..f6c47ec28 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/76/9c8b8d89700f6f196b8331159150746a839662 @@ -0,0 +1,3 @@ +xA +0@Q9Ed1"BW=FL`L <=[j}BUjHGvp%0= ƫc@2I^\c( 1{VL }d?ck΋C/V=;Bb{F0G=Mv4? +8 \ No newline at end of file diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/bd/2b32f02abf86a2bb79a12ab09758e44b204b34 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/bd/2b32f02abf86a2bb79a12ab09758e44b204b34 new file mode 100644 index 000000000..abea13842 Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/bd/2b32f02abf86a2bb79a12ab09758e44b204b34 differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/c0/565d7cfcf1039c969105f2e1c86ca5eff64381 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/c0/565d7cfcf1039c969105f2e1c86ca5eff64381 new file mode 100644 index 000000000..7d88244be Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/c0/565d7cfcf1039c969105f2e1c86ca5eff64381 differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/f2/df244fb87b6ba1d2ab484d76c66baba168a867 b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/f2/df244fb87b6ba1d2ab484d76c66baba168a867 new file mode 100644 index 000000000..a8bc1219e Binary files /dev/null and b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/objects/f2/df244fb87b6ba1d2ab484d76c66baba168a867 differ diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/refs/heads/master b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..b735e793a --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +691ee9e9d9c654c81214f56c514ff725f46cb9e4 diff --git a/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/refs/heads/other b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/refs/heads/other new file mode 100644 index 000000000..52ac4afa8 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/.git_keep/refs/heads/other @@ -0,0 +1 @@ +f2df244fb87b6ba1d2ab484d76c66baba168a867 diff --git a/test/integration/mergeConflictsResolvedExternally/expected/file b/test/integration/mergeConflictsResolvedExternally/expected/file new file mode 100644 index 000000000..0884a47e0 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/expected/file @@ -0,0 +1 @@ +master2 diff --git a/test/integration/mergeConflictsResolvedExternally/recording.json b/test/integration/mergeConflictsResolvedExternally/recording.json new file mode 100644 index 000000000..21b60ce5a --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":438,"Mod":0,"Key":259,"Ch":0},{"Timestamp":663,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1206,"Mod":0,"Key":256,"Ch":77},{"Timestamp":1487,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2045,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2951,"Mod":0,"Key":256,"Ch":78},{"Timestamp":3606,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4343,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/mergeConflictsResolvedExternally/setup.sh b/test/integration/mergeConflictsResolvedExternally/setup.sh new file mode 100644 index 000000000..e1126d2c3 --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/setup.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -e + +cd $1 + +git init +git config user.email "CI@example.com" +git config user.name "CI" + +git checkout -b master + +echo "master1" > file +git add . +git commit -m "master1" + +git checkout -b other + +echo "other1" > file +git add . +git commit -m "other1" + +git checkout master + +echo "master2" > file +git add . +git commit -m "master2" + +git checkout other diff --git a/test/integration/mergeConflictsResolvedExternally/test.json b/test/integration/mergeConflictsResolvedExternally/test.json new file mode 100644 index 000000000..e51abcada --- /dev/null +++ b/test/integration/mergeConflictsResolvedExternally/test.json @@ -0,0 +1,4 @@ +{ + "description": "This test verifies that merge conflicts resolved externally are recognised by lazygit and the user is prompted to continue", + "speed": 10 +} diff --git a/test/integration/patchBuilding/setup.sh b/test/integration/patchBuilding/setup.sh index 2e117b4ad..3ba5d6017 100644 --- a/test/integration/patchBuilding/setup.sh +++ b/test/integration/patchBuilding/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/patchBuilding2/setup.sh b/test/integration/patchBuilding2/setup.sh index 2e117b4ad..3ba5d6017 100644 --- a/test/integration/patchBuilding2/setup.sh +++ b/test/integration/patchBuilding2/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/patchBuildingWithFiletree/setup.sh b/test/integration/patchBuildingWithFiletree/setup.sh index 87dc9f6f6..c552b9e56 100644 --- a/test/integration/patchBuildingWithFiletree/setup.sh +++ b/test/integration/patchBuildingWithFiletree/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/pull/expected/.git_keep/index b/test/integration/pull/expected/.git_keep/index index 85847f137..97b142556 100644 Binary files a/test/integration/pull/expected/.git_keep/index and b/test/integration/pull/expected/.git_keep/index differ diff --git a/test/integration/pull/setup.sh b/test/integration/pull/setup.sh index 2634d0278..ffe8a3b6d 100644 --- a/test/integration/pull/setup.sh +++ b/test/integration/pull/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/COMMIT_EDITMSG b/test/integration/pullAndSetUpstream/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..51be8ec3d --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +myfile4 diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/FETCH_HEAD b/test/integration/pullAndSetUpstream/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..dad3f2b15 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/FETCH_HEAD @@ -0,0 +1 @@ +766e681a51daa75233c1c4ae8845be2c893577d5 branch 'master' of ../actual_remote diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/HEAD b/test/integration/pullAndSetUpstream/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/ORIG_HEAD b/test/integration/pullAndSetUpstream/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..013d7edf1 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +c9fd61f40de25556977e063683d1de612f931ccb diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/config b/test/integration/pullAndSetUpstream/expected/.git_keep/config new file mode 100644 index 000000000..821803a3e --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/config @@ -0,0 +1,16 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI +[remote "origin"] + url = ../actual_remote + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/description b/test/integration/pullAndSetUpstream/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/index b/test/integration/pullAndSetUpstream/expected/.git_keep/index new file mode 100644 index 000000000..f658c6ea4 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/index differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/info/exclude b/test/integration/pullAndSetUpstream/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/logs/HEAD b/test/integration/pullAndSetUpstream/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..a0abb8bc8 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/logs/HEAD @@ -0,0 +1,7 @@ +0000000000000000000000000000000000000000 972fb9caab8b8536ae38687fec98304b76748b9d CI 1642217132 +1100 commit (initial): myfile1 +972fb9caab8b8536ae38687fec98304b76748b9d c9fd61f40de25556977e063683d1de612f931ccb CI 1642217132 +1100 commit: myfile2 +c9fd61f40de25556977e063683d1de612f931ccb 06d3929607b7519beb45ca67165a1f2b5c0e578b CI 1642217132 +1100 commit: myfile3 +06d3929607b7519beb45ca67165a1f2b5c0e578b 766e681a51daa75233c1c4ae8845be2c893577d5 CI 1642217132 +1100 commit: myfile4 +766e681a51daa75233c1c4ae8845be2c893577d5 c9fd61f40de25556977e063683d1de612f931ccb CI 1642217132 +1100 reset: moving to HEAD~2 +c9fd61f40de25556977e063683d1de612f931ccb 766e681a51daa75233c1c4ae8845be2c893577d5 CI 1642217139 +1100 rebase -i (start): checkout 766e681a51daa75233c1c4ae8845be2c893577d5 +766e681a51daa75233c1c4ae8845be2c893577d5 766e681a51daa75233c1c4ae8845be2c893577d5 CI 1642217139 +1100 rebase -i (finish): returning to refs/heads/master diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/logs/refs/heads/master b/test/integration/pullAndSetUpstream/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..d6b607c56 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,6 @@ +0000000000000000000000000000000000000000 972fb9caab8b8536ae38687fec98304b76748b9d CI 1642217132 +1100 commit (initial): myfile1 +972fb9caab8b8536ae38687fec98304b76748b9d c9fd61f40de25556977e063683d1de612f931ccb CI 1642217132 +1100 commit: myfile2 +c9fd61f40de25556977e063683d1de612f931ccb 06d3929607b7519beb45ca67165a1f2b5c0e578b CI 1642217132 +1100 commit: myfile3 +06d3929607b7519beb45ca67165a1f2b5c0e578b 766e681a51daa75233c1c4ae8845be2c893577d5 CI 1642217132 +1100 commit: myfile4 +766e681a51daa75233c1c4ae8845be2c893577d5 c9fd61f40de25556977e063683d1de612f931ccb CI 1642217132 +1100 reset: moving to HEAD~2 +c9fd61f40de25556977e063683d1de612f931ccb 766e681a51daa75233c1c4ae8845be2c893577d5 CI 1642217139 +1100 rebase -i (finish): refs/heads/master onto 766e681a51daa75233c1c4ae8845be2c893577d5 diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/logs/refs/remotes/origin/master b/test/integration/pullAndSetUpstream/expected/.git_keep/logs/refs/remotes/origin/master new file mode 100644 index 000000000..9c9943bda --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/logs/refs/remotes/origin/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 766e681a51daa75233c1c4ae8845be2c893577d5 CI 1642217132 +1100 fetch origin: storing head diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/06/d3929607b7519beb45ca67165a1f2b5c0e578b b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/06/d3929607b7519beb45ca67165a1f2b5c0e578b new file mode 100644 index 000000000..32e93478b Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/06/d3929607b7519beb45ca67165a1f2b5c0e578b differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce new file mode 100644 index 000000000..0a734f981 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 new file mode 100644 index 000000000..31ae3f5ba Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/76/6e681a51daa75233c1c4ae8845be2c893577d5 b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/76/6e681a51daa75233c1c4ae8845be2c893577d5 new file mode 100644 index 000000000..527c680a2 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/76/6e681a51daa75233c1c4ae8845be2c893577d5 differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/97/2fb9caab8b8536ae38687fec98304b76748b9d b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/97/2fb9caab8b8536ae38687fec98304b76748b9d new file mode 100644 index 000000000..ab6f93898 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/97/2fb9caab8b8536ae38687fec98304b76748b9d differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/c9/fd61f40de25556977e063683d1de612f931ccb b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/c9/fd61f40de25556977e063683d1de612f931ccb new file mode 100644 index 000000000..cb10ffe51 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/c9/fd61f40de25556977e063683d1de612f931ccb differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 new file mode 100644 index 000000000..d39fa7d2f Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/refs/heads/master b/test/integration/pullAndSetUpstream/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..e6edfeca1 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +766e681a51daa75233c1c4ae8845be2c893577d5 diff --git a/test/integration/pullAndSetUpstream/expected/.git_keep/refs/remotes/origin/master b/test/integration/pullAndSetUpstream/expected/.git_keep/refs/remotes/origin/master new file mode 100644 index 000000000..e6edfeca1 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/.git_keep/refs/remotes/origin/master @@ -0,0 +1 @@ +766e681a51daa75233c1c4ae8845be2c893577d5 diff --git a/test/integration/pullAndSetUpstream/expected/myfile1 b/test/integration/pullAndSetUpstream/expected/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/pullAndSetUpstream/expected/myfile2 b/test/integration/pullAndSetUpstream/expected/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/pullAndSetUpstream/expected/myfile3 b/test/integration/pullAndSetUpstream/expected/myfile3 new file mode 100644 index 000000000..df6b0d2bc --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/myfile3 @@ -0,0 +1 @@ +test3 diff --git a/test/integration/pullAndSetUpstream/expected/myfile4 b/test/integration/pullAndSetUpstream/expected/myfile4 new file mode 100644 index 000000000..d234c5e05 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected/myfile4 @@ -0,0 +1 @@ +test4 diff --git a/test/integration/pullAndSetUpstream/expected_remote/HEAD b/test/integration/pullAndSetUpstream/expected_remote/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected_remote/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/pullAndSetUpstream/expected_remote/config b/test/integration/pullAndSetUpstream/expected_remote/config new file mode 100644 index 000000000..6457661a3 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected_remote/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true +[remote "origin"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/pullAndSetUpstream/./actual diff --git a/test/integration/pullAndSetUpstream/expected_remote/description b/test/integration/pullAndSetUpstream/expected_remote/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected_remote/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/pullAndSetUpstream/expected_remote/info/exclude b/test/integration/pullAndSetUpstream/expected_remote/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected_remote/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/06/d3929607b7519beb45ca67165a1f2b5c0e578b b/test/integration/pullAndSetUpstream/expected_remote/objects/06/d3929607b7519beb45ca67165a1f2b5c0e578b new file mode 100644 index 000000000..32e93478b Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/06/d3929607b7519beb45ca67165a1f2b5c0e578b differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/pullAndSetUpstream/expected_remote/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/pullAndSetUpstream/expected_remote/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce b/test/integration/pullAndSetUpstream/expected_remote/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce new file mode 100644 index 000000000..0a734f981 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/2f/6174050380438f14b16658a356e762435ca591 b/test/integration/pullAndSetUpstream/expected_remote/objects/2f/6174050380438f14b16658a356e762435ca591 new file mode 100644 index 000000000..31ae3f5ba Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/2f/6174050380438f14b16658a356e762435ca591 differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/76/6e681a51daa75233c1c4ae8845be2c893577d5 b/test/integration/pullAndSetUpstream/expected_remote/objects/76/6e681a51daa75233c1c4ae8845be2c893577d5 new file mode 100644 index 000000000..527c680a2 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/76/6e681a51daa75233c1c4ae8845be2c893577d5 differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/97/2fb9caab8b8536ae38687fec98304b76748b9d b/test/integration/pullAndSetUpstream/expected_remote/objects/97/2fb9caab8b8536ae38687fec98304b76748b9d new file mode 100644 index 000000000..ab6f93898 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/97/2fb9caab8b8536ae38687fec98304b76748b9d differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/pullAndSetUpstream/expected_remote/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/pullAndSetUpstream/expected_remote/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/c9/fd61f40de25556977e063683d1de612f931ccb b/test/integration/pullAndSetUpstream/expected_remote/objects/c9/fd61f40de25556977e063683d1de612f931ccb new file mode 100644 index 000000000..cb10ffe51 Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/c9/fd61f40de25556977e063683d1de612f931ccb differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 b/test/integration/pullAndSetUpstream/expected_remote/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 new file mode 100644 index 000000000..d39fa7d2f Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/pullAndSetUpstream/expected_remote/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/pullAndSetUpstream/expected_remote/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/pullAndSetUpstream/expected_remote/packed-refs b/test/integration/pullAndSetUpstream/expected_remote/packed-refs new file mode 100644 index 000000000..66a0f8392 --- /dev/null +++ b/test/integration/pullAndSetUpstream/expected_remote/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +766e681a51daa75233c1c4ae8845be2c893577d5 refs/heads/master diff --git a/test/integration/pullAndSetUpstream/recording.json b/test/integration/pullAndSetUpstream/recording.json new file mode 100644 index 000000000..a2caf52dc --- /dev/null +++ b/test/integration/pullAndSetUpstream/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":423,"Mod":0,"Key":256,"Ch":112},{"Timestamp":1032,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1216,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1376,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1536,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1687,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1847,"Mod":0,"Key":127,"Ch":127},{"Timestamp":2016,"Mod":0,"Key":127,"Ch":127},{"Timestamp":2240,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3044,"Mod":0,"Key":27,"Ch":0},{"Timestamp":3768,"Mod":0,"Key":256,"Ch":112},{"Timestamp":4184,"Mod":0,"Key":256,"Ch":97},{"Timestamp":4232,"Mod":0,"Key":256,"Ch":115},{"Timestamp":4288,"Mod":0,"Key":256,"Ch":100},{"Timestamp":4728,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5488,"Mod":0,"Key":27,"Ch":0},{"Timestamp":5799,"Mod":0,"Key":256,"Ch":112},{"Timestamp":6273,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7207,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/pullAndSetUpstream/setup.sh b/test/integration/pullAndSetUpstream/setup.sh new file mode 100644 index 000000000..f0c7cf842 --- /dev/null +++ b/test/integration/pullAndSetUpstream/setup.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +set -e + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" +echo test2 > myfile2 +git add . +git commit -am "myfile2" +echo test3 > myfile3 +git add . +git commit -am "myfile3" +echo test4 > myfile4 +git add . +git commit -am "myfile4" + +cd .. +git clone --bare ./actual actual_remote + +cd actual + +# the test is to ensure that we actually can pull these two commits back from the origin +git reset --hard HEAD~2 +git remote add origin ../actual_remote +git fetch origin diff --git a/test/integration/pullAndSetUpstream/test.json b/test/integration/pullAndSetUpstream/test.json new file mode 100644 index 000000000..ca7e53085 --- /dev/null +++ b/test/integration/pullAndSetUpstream/test.json @@ -0,0 +1,4 @@ +{ + "description": "pull changes from the remote, setting upstream", + "speed": 10 +} diff --git a/test/integration/pullMerge/setup.sh b/test/integration/pullMerge/setup.sh index b9d9a933a..379a95362 100644 --- a/test/integration/pullMerge/setup.sh +++ b/test/integration/pullMerge/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pullMergeConflict/setup.sh b/test/integration/pullMergeConflict/setup.sh index a19999045..56967049f 100644 --- a/test/integration/pullMergeConflict/setup.sh +++ b/test/integration/pullMergeConflict/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pullRebase/setup.sh b/test/integration/pullRebase/setup.sh index aef24c8e8..b1eb37fcf 100644 --- a/test/integration/pullRebase/setup.sh +++ b/test/integration/pullRebase/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pullRebaseConflict/setup.sh b/test/integration/pullRebaseConflict/setup.sh index 63146bda2..e02be3c0c 100644 --- a/test/integration/pullRebaseConflict/setup.sh +++ b/test/integration/pullRebaseConflict/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pullRebaseInteractive/setup.sh b/test/integration/pullRebaseInteractive/setup.sh index 0795a297f..a0dce709f 100644 --- a/test/integration/pullRebaseInteractive/setup.sh +++ b/test/integration/pullRebaseInteractive/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pullRebaseInteractiveWithDrop/setup.sh b/test/integration/pullRebaseInteractiveWithDrop/setup.sh index 0795a297f..a0dce709f 100644 --- a/test/integration/pullRebaseInteractiveWithDrop/setup.sh +++ b/test/integration/pullRebaseInteractiveWithDrop/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/push/setup.sh b/test/integration/push/setup.sh index 7d137b349..075e9afd9 100644 --- a/test/integration/push/setup.sh +++ b/test/integration/push/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pushAndSetUpstream/setup.sh b/test/integration/pushAndSetUpstream/setup.sh index 2911a073c..6c4c85af6 100644 --- a/test/integration/pushAndSetUpstream/setup.sh +++ b/test/integration/pushAndSetUpstream/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pushAndSetUpstreamDefault/setup.sh b/test/integration/pushAndSetUpstreamDefault/setup.sh index d12f985f6..1d5f61ecf 100644 --- a/test/integration/pushAndSetUpstreamDefault/setup.sh +++ b/test/integration/pushAndSetUpstreamDefault/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pushFollowTags/setup.sh b/test/integration/pushFollowTags/setup.sh index 2da0cbf18..035c9189d 100644 --- a/test/integration/pushFollowTags/setup.sh +++ b/test/integration/pushFollowTags/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pushNoFollowTags/setup.sh b/test/integration/pushNoFollowTags/setup.sh index 304dcd9e9..b60c4eed4 100644 --- a/test/integration/pushNoFollowTags/setup.sh +++ b/test/integration/pushNoFollowTags/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pushTag/expected/.git_keep/FETCH_HEAD b/test/integration/pushTag/expected/.git_keep/FETCH_HEAD index fb3f085ba..db4145664 100644 --- a/test/integration/pushTag/expected/.git_keep/FETCH_HEAD +++ b/test/integration/pushTag/expected/.git_keep/FETCH_HEAD @@ -1 +1 @@ -9639a47a7955a338919b090835305898ea516c66 branch 'master' of ../actual_remote +5e8100f80934cb3f1530579225107a478afac4ee branch 'master' of ../actual_remote diff --git a/test/integration/pushTag/expected/.git_keep/index b/test/integration/pushTag/expected/.git_keep/index index b9d835e98..4ad50a104 100644 Binary files a/test/integration/pushTag/expected/.git_keep/index and b/test/integration/pushTag/expected/.git_keep/index differ diff --git a/test/integration/pushTag/expected/.git_keep/logs/HEAD b/test/integration/pushTag/expected/.git_keep/logs/HEAD index 406014feb..6b662a0c2 100644 --- a/test/integration/pushTag/expected/.git_keep/logs/HEAD +++ b/test/integration/pushTag/expected/.git_keep/logs/HEAD @@ -1,2 +1,2 @@ -0000000000000000000000000000000000000000 905961711d3f0898c003c7e4da636a4c683f7934 CI 1634944441 +1100 commit (initial): myfile1 -905961711d3f0898c003c7e4da636a4c683f7934 9639a47a7955a338919b090835305898ea516c66 CI 1634944441 +1100 commit: myfile2 +0000000000000000000000000000000000000000 f5e0cf8631fc56de2f374ef60e123a2b643381e5 CI 1641183988 +1100 commit (initial): myfile1 +f5e0cf8631fc56de2f374ef60e123a2b643381e5 5e8100f80934cb3f1530579225107a478afac4ee CI 1641183988 +1100 commit: myfile2 diff --git a/test/integration/pushTag/expected/.git_keep/logs/refs/heads/master b/test/integration/pushTag/expected/.git_keep/logs/refs/heads/master index 406014feb..6b662a0c2 100644 --- a/test/integration/pushTag/expected/.git_keep/logs/refs/heads/master +++ b/test/integration/pushTag/expected/.git_keep/logs/refs/heads/master @@ -1,2 +1,2 @@ -0000000000000000000000000000000000000000 905961711d3f0898c003c7e4da636a4c683f7934 CI 1634944441 +1100 commit (initial): myfile1 -905961711d3f0898c003c7e4da636a4c683f7934 9639a47a7955a338919b090835305898ea516c66 CI 1634944441 +1100 commit: myfile2 +0000000000000000000000000000000000000000 f5e0cf8631fc56de2f374ef60e123a2b643381e5 CI 1641183988 +1100 commit (initial): myfile1 +f5e0cf8631fc56de2f374ef60e123a2b643381e5 5e8100f80934cb3f1530579225107a478afac4ee CI 1641183988 +1100 commit: myfile2 diff --git a/test/integration/pushTag/expected/.git_keep/logs/refs/remotes/origin/master b/test/integration/pushTag/expected/.git_keep/logs/refs/remotes/origin/master index a2798f571..3509e2c88 100644 --- a/test/integration/pushTag/expected/.git_keep/logs/refs/remotes/origin/master +++ b/test/integration/pushTag/expected/.git_keep/logs/refs/remotes/origin/master @@ -1 +1 @@ -0000000000000000000000000000000000000000 9639a47a7955a338919b090835305898ea516c66 CI 1634944442 +1100 fetch origin: storing head +0000000000000000000000000000000000000000 5e8100f80934cb3f1530579225107a478afac4ee CI 1641183988 +1100 fetch origin: storing head diff --git a/test/integration/pushTag/expected/.git_keep/objects/5e/8100f80934cb3f1530579225107a478afac4ee b/test/integration/pushTag/expected/.git_keep/objects/5e/8100f80934cb3f1530579225107a478afac4ee new file mode 100644 index 000000000..bcbc6ef2b Binary files /dev/null and b/test/integration/pushTag/expected/.git_keep/objects/5e/8100f80934cb3f1530579225107a478afac4ee differ diff --git a/test/integration/pushTag/expected/.git_keep/objects/90/5961711d3f0898c003c7e4da636a4c683f7934 b/test/integration/pushTag/expected/.git_keep/objects/90/5961711d3f0898c003c7e4da636a4c683f7934 deleted file mode 100644 index f060761fd..000000000 Binary files a/test/integration/pushTag/expected/.git_keep/objects/90/5961711d3f0898c003c7e4da636a4c683f7934 and /dev/null differ diff --git a/test/integration/pushTag/expected/.git_keep/objects/96/39a47a7955a338919b090835305898ea516c66 b/test/integration/pushTag/expected/.git_keep/objects/96/39a47a7955a338919b090835305898ea516c66 deleted file mode 100644 index 243780783..000000000 Binary files a/test/integration/pushTag/expected/.git_keep/objects/96/39a47a7955a338919b090835305898ea516c66 and /dev/null differ diff --git a/test/integration/pushTag/expected/.git_keep/objects/f5/e0cf8631fc56de2f374ef60e123a2b643381e5 b/test/integration/pushTag/expected/.git_keep/objects/f5/e0cf8631fc56de2f374ef60e123a2b643381e5 new file mode 100644 index 000000000..647bf5fc7 --- /dev/null +++ b/test/integration/pushTag/expected/.git_keep/objects/f5/e0cf8631fc56de2f374ef60e123a2b643381e5 @@ -0,0 +1,2 @@ +xA +@ @Qs1MADHg2Xh)#~TH|jDT2&P8 veT4];L3ܦafTȄ((gQI?yoY746, \ No newline at end of file diff --git a/test/integration/pushTag/expected/.git_keep/refs/heads/master b/test/integration/pushTag/expected/.git_keep/refs/heads/master index 442efe33e..eb5effc80 100644 --- a/test/integration/pushTag/expected/.git_keep/refs/heads/master +++ b/test/integration/pushTag/expected/.git_keep/refs/heads/master @@ -1 +1 @@ -9639a47a7955a338919b090835305898ea516c66 +5e8100f80934cb3f1530579225107a478afac4ee diff --git a/test/integration/pushTag/expected/.git_keep/refs/remotes/origin/master b/test/integration/pushTag/expected/.git_keep/refs/remotes/origin/master index 442efe33e..eb5effc80 100644 --- a/test/integration/pushTag/expected/.git_keep/refs/remotes/origin/master +++ b/test/integration/pushTag/expected/.git_keep/refs/remotes/origin/master @@ -1 +1 @@ -9639a47a7955a338919b090835305898ea516c66 +5e8100f80934cb3f1530579225107a478afac4ee diff --git a/test/integration/pushTag/expected/.git_keep/refs/tags/v1.0 b/test/integration/pushTag/expected/.git_keep/refs/tags/v1.0 index 442efe33e..eb5effc80 100644 --- a/test/integration/pushTag/expected/.git_keep/refs/tags/v1.0 +++ b/test/integration/pushTag/expected/.git_keep/refs/tags/v1.0 @@ -1 +1 @@ -9639a47a7955a338919b090835305898ea516c66 +5e8100f80934cb3f1530579225107a478afac4ee diff --git a/test/integration/pushTag/expected_remote/objects/5e/8100f80934cb3f1530579225107a478afac4ee b/test/integration/pushTag/expected_remote/objects/5e/8100f80934cb3f1530579225107a478afac4ee new file mode 100644 index 000000000..bcbc6ef2b Binary files /dev/null and b/test/integration/pushTag/expected_remote/objects/5e/8100f80934cb3f1530579225107a478afac4ee differ diff --git a/test/integration/pushTag/expected_remote/objects/90/5961711d3f0898c003c7e4da636a4c683f7934 b/test/integration/pushTag/expected_remote/objects/90/5961711d3f0898c003c7e4da636a4c683f7934 deleted file mode 100644 index f060761fd..000000000 Binary files a/test/integration/pushTag/expected_remote/objects/90/5961711d3f0898c003c7e4da636a4c683f7934 and /dev/null differ diff --git a/test/integration/pushTag/expected_remote/objects/96/39a47a7955a338919b090835305898ea516c66 b/test/integration/pushTag/expected_remote/objects/96/39a47a7955a338919b090835305898ea516c66 deleted file mode 100644 index 243780783..000000000 Binary files a/test/integration/pushTag/expected_remote/objects/96/39a47a7955a338919b090835305898ea516c66 and /dev/null differ diff --git a/test/integration/pushTag/expected_remote/objects/f5/e0cf8631fc56de2f374ef60e123a2b643381e5 b/test/integration/pushTag/expected_remote/objects/f5/e0cf8631fc56de2f374ef60e123a2b643381e5 new file mode 100644 index 000000000..647bf5fc7 --- /dev/null +++ b/test/integration/pushTag/expected_remote/objects/f5/e0cf8631fc56de2f374ef60e123a2b643381e5 @@ -0,0 +1,2 @@ +xA +@ @Qs1MADHg2Xh)#~TH|jDT2&P8 veT4];L3ܦafTȄ((gQI?yoY746, \ No newline at end of file diff --git a/test/integration/pushTag/expected_remote/packed-refs b/test/integration/pushTag/expected_remote/packed-refs index 31565c279..b71ee5976 100644 --- a/test/integration/pushTag/expected_remote/packed-refs +++ b/test/integration/pushTag/expected_remote/packed-refs @@ -1,2 +1,2 @@ # pack-refs with: peeled fully-peeled sorted -9639a47a7955a338919b090835305898ea516c66 refs/heads/master +5e8100f80934cb3f1530579225107a478afac4ee refs/heads/master diff --git a/test/integration/pushTag/expected_remote/refs/tags/v1.0 b/test/integration/pushTag/expected_remote/refs/tags/v1.0 index 442efe33e..eb5effc80 100644 --- a/test/integration/pushTag/expected_remote/refs/tags/v1.0 +++ b/test/integration/pushTag/expected_remote/refs/tags/v1.0 @@ -1 +1 @@ -9639a47a7955a338919b090835305898ea516c66 +5e8100f80934cb3f1530579225107a478afac4ee diff --git a/test/integration/pushTag/recording.json b/test/integration/pushTag/recording.json index 7f2006f35..91490d1f6 100644 --- a/test/integration/pushTag/recording.json +++ b/test/integration/pushTag/recording.json @@ -1 +1 @@ -{"KeyEvents":[{"Timestamp":1037,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1596,"Mod":0,"Key":256,"Ch":93},{"Timestamp":1709,"Mod":0,"Key":256,"Ch":93},{"Timestamp":2117,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2411,"Mod":0,"Key":256,"Ch":118},{"Timestamp":2757,"Mod":0,"Key":256,"Ch":49},{"Timestamp":2932,"Mod":0,"Key":256,"Ch":46},{"Timestamp":3109,"Mod":0,"Key":256,"Ch":48},{"Timestamp":3373,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3988,"Mod":0,"Key":256,"Ch":80},{"Timestamp":4516,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5309,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file +{"KeyEvents":[{"Timestamp":736,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1398,"Mod":0,"Key":256,"Ch":93},{"Timestamp":1526,"Mod":0,"Key":256,"Ch":93},{"Timestamp":2271,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2759,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3342,"Mod":0,"Key":256,"Ch":118},{"Timestamp":3655,"Mod":0,"Key":256,"Ch":49},{"Timestamp":3792,"Mod":0,"Key":256,"Ch":46},{"Timestamp":3958,"Mod":0,"Key":256,"Ch":48},{"Timestamp":4271,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5062,"Mod":0,"Key":256,"Ch":80},{"Timestamp":5487,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6455,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/pushTag/setup.sh b/test/integration/pushTag/setup.sh index 5e09ed4ea..cf6270c03 100644 --- a/test/integration/pushTag/setup.sh +++ b/test/integration/pushTag/setup.sh @@ -2,6 +2,8 @@ set -e +set -e + cd $1 git init diff --git a/test/integration/pushWithCredentials/expected/.git_keep/COMMIT_EDITMSG b/test/integration/pushWithCredentials/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..51be8ec3d --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +myfile4 diff --git a/test/integration/pushWithCredentials/expected/.git_keep/FETCH_HEAD b/test/integration/pushWithCredentials/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..6832209cd --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/FETCH_HEAD @@ -0,0 +1 @@ +ff7015f162da19450f2eaf0fc24987104df30e15 branch 'master' of ../actual_remote diff --git a/test/integration/pushWithCredentials/expected/.git_keep/HEAD b/test/integration/pushWithCredentials/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/pushWithCredentials/expected/.git_keep/config b/test/integration/pushWithCredentials/expected/.git_keep/config new file mode 100644 index 000000000..821803a3e --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/config @@ -0,0 +1,16 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI +[remote "origin"] + url = ../actual_remote + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/test/integration/pushWithCredentials/expected/.git_keep/description b/test/integration/pushWithCredentials/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/pushWithCredentials/expected/.git_keep/index b/test/integration/pushWithCredentials/expected/.git_keep/index new file mode 100644 index 000000000..9e081cf47 Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/index differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/info/exclude b/test/integration/pushWithCredentials/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/pushWithCredentials/expected/.git_keep/logs/HEAD b/test/integration/pushWithCredentials/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..27c049707 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/logs/HEAD @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 ba8cb1da2a48c38706b15552877d79e8745c4bff CI 1641689920 +1100 commit (initial): myfile1 +ba8cb1da2a48c38706b15552877d79e8745c4bff ff7015f162da19450f2eaf0fc24987104df30e15 CI 1641689920 +1100 commit: myfile2 +ff7015f162da19450f2eaf0fc24987104df30e15 d9ea8db22c1655e9861309cc97139357d20e4e64 CI 1641689920 +1100 commit: myfile3 +d9ea8db22c1655e9861309cc97139357d20e4e64 75c50688e5a8e48a00d1a824124221bcc6aad640 CI 1641689920 +1100 commit: myfile4 diff --git a/test/integration/pushWithCredentials/expected/.git_keep/logs/refs/heads/master b/test/integration/pushWithCredentials/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..27c049707 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 ba8cb1da2a48c38706b15552877d79e8745c4bff CI 1641689920 +1100 commit (initial): myfile1 +ba8cb1da2a48c38706b15552877d79e8745c4bff ff7015f162da19450f2eaf0fc24987104df30e15 CI 1641689920 +1100 commit: myfile2 +ff7015f162da19450f2eaf0fc24987104df30e15 d9ea8db22c1655e9861309cc97139357d20e4e64 CI 1641689920 +1100 commit: myfile3 +d9ea8db22c1655e9861309cc97139357d20e4e64 75c50688e5a8e48a00d1a824124221bcc6aad640 CI 1641689920 +1100 commit: myfile4 diff --git a/test/integration/pushWithCredentials/expected/.git_keep/logs/refs/remotes/origin/master b/test/integration/pushWithCredentials/expected/.git_keep/logs/refs/remotes/origin/master new file mode 100644 index 000000000..463f9add6 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/logs/refs/remotes/origin/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 ff7015f162da19450f2eaf0fc24987104df30e15 CI 1641689920 +1100 fetch origin: storing head +ff7015f162da19450f2eaf0fc24987104df30e15 75c50688e5a8e48a00d1a824124221bcc6aad640 CI 1641689925 +1100 update by push diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/pushWithCredentials/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/pushWithCredentials/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce b/test/integration/pushWithCredentials/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce new file mode 100644 index 000000000..0a734f981 Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 b/test/integration/pushWithCredentials/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 new file mode 100644 index 000000000..31ae3f5ba Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/75/c50688e5a8e48a00d1a824124221bcc6aad640 b/test/integration/pushWithCredentials/expected/.git_keep/objects/75/c50688e5a8e48a00d1a824124221bcc6aad640 new file mode 100644 index 000000000..53b925279 Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/75/c50688e5a8e48a00d1a824124221bcc6aad640 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/pushWithCredentials/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/pushWithCredentials/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/ba/8cb1da2a48c38706b15552877d79e8745c4bff b/test/integration/pushWithCredentials/expected/.git_keep/objects/ba/8cb1da2a48c38706b15552877d79e8745c4bff new file mode 100644 index 000000000..e6612ab16 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/objects/ba/8cb1da2a48c38706b15552877d79e8745c4bff @@ -0,0 +1,2 @@ +xA +0@Q94#M&XR"~̖r*Jd¬3_pM]zWa>NO$Vf aG8!rgߺJ3, \ No newline at end of file diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 b/test/integration/pushWithCredentials/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 new file mode 100644 index 000000000..d39fa7d2f Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/d9/ea8db22c1655e9861309cc97139357d20e4e64 b/test/integration/pushWithCredentials/expected/.git_keep/objects/d9/ea8db22c1655e9861309cc97139357d20e4e64 new file mode 100644 index 000000000..16633475a Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/d9/ea8db22c1655e9861309cc97139357d20e4e64 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/pushWithCredentials/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/objects/ff/7015f162da19450f2eaf0fc24987104df30e15 b/test/integration/pushWithCredentials/expected/.git_keep/objects/ff/7015f162da19450f2eaf0fc24987104df30e15 new file mode 100644 index 000000000..c79143577 Binary files /dev/null and b/test/integration/pushWithCredentials/expected/.git_keep/objects/ff/7015f162da19450f2eaf0fc24987104df30e15 differ diff --git a/test/integration/pushWithCredentials/expected/.git_keep/refs/heads/master b/test/integration/pushWithCredentials/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..b3746d069 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +75c50688e5a8e48a00d1a824124221bcc6aad640 diff --git a/test/integration/pushWithCredentials/expected/.git_keep/refs/remotes/origin/master b/test/integration/pushWithCredentials/expected/.git_keep/refs/remotes/origin/master new file mode 100644 index 000000000..b3746d069 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/.git_keep/refs/remotes/origin/master @@ -0,0 +1 @@ +75c50688e5a8e48a00d1a824124221bcc6aad640 diff --git a/test/integration/pushWithCredentials/expected/myfile1 b/test/integration/pushWithCredentials/expected/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/pushWithCredentials/expected/myfile2 b/test/integration/pushWithCredentials/expected/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/pushWithCredentials/expected/myfile3 b/test/integration/pushWithCredentials/expected/myfile3 new file mode 100644 index 000000000..df6b0d2bc --- /dev/null +++ b/test/integration/pushWithCredentials/expected/myfile3 @@ -0,0 +1 @@ +test3 diff --git a/test/integration/pushWithCredentials/expected/myfile4 b/test/integration/pushWithCredentials/expected/myfile4 new file mode 100644 index 000000000..d234c5e05 --- /dev/null +++ b/test/integration/pushWithCredentials/expected/myfile4 @@ -0,0 +1 @@ +test4 diff --git a/test/integration/pushWithCredentials/expected_remote/HEAD b/test/integration/pushWithCredentials/expected_remote/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/pushWithCredentials/expected_remote/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/pushWithCredentials/expected_remote/config b/test/integration/pushWithCredentials/expected_remote/config new file mode 100644 index 000000000..c498610f7 --- /dev/null +++ b/test/integration/pushWithCredentials/expected_remote/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true +[remote "origin"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/pushWithCredentials/./actual diff --git a/test/integration/pushWithCredentials/expected_remote/description b/test/integration/pushWithCredentials/expected_remote/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/pushWithCredentials/expected_remote/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/pushWithCredentials/expected_remote/info/exclude b/test/integration/pushWithCredentials/expected_remote/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/pushWithCredentials/expected_remote/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/pushWithCredentials/expected_remote/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/pushWithCredentials/expected_remote/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/pushWithCredentials/expected_remote/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce b/test/integration/pushWithCredentials/expected_remote/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce new file mode 100644 index 000000000..0a734f981 Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/2f/6174050380438f14b16658a356e762435ca591 b/test/integration/pushWithCredentials/expected_remote/objects/2f/6174050380438f14b16658a356e762435ca591 new file mode 100644 index 000000000..31ae3f5ba Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/2f/6174050380438f14b16658a356e762435ca591 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/75/c50688e5a8e48a00d1a824124221bcc6aad640 b/test/integration/pushWithCredentials/expected_remote/objects/75/c50688e5a8e48a00d1a824124221bcc6aad640 new file mode 100644 index 000000000..53b925279 Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/75/c50688e5a8e48a00d1a824124221bcc6aad640 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/pushWithCredentials/expected_remote/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/pushWithCredentials/expected_remote/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/ba/8cb1da2a48c38706b15552877d79e8745c4bff b/test/integration/pushWithCredentials/expected_remote/objects/ba/8cb1da2a48c38706b15552877d79e8745c4bff new file mode 100644 index 000000000..e6612ab16 --- /dev/null +++ b/test/integration/pushWithCredentials/expected_remote/objects/ba/8cb1da2a48c38706b15552877d79e8745c4bff @@ -0,0 +1,2 @@ +xA +0@Q94#M&XR"~̖r*Jd¬3_pM]zWa>NO$Vf aG8!rgߺJ3, \ No newline at end of file diff --git a/test/integration/pushWithCredentials/expected_remote/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 b/test/integration/pushWithCredentials/expected_remote/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 new file mode 100644 index 000000000..d39fa7d2f Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/d9/ea8db22c1655e9861309cc97139357d20e4e64 b/test/integration/pushWithCredentials/expected_remote/objects/d9/ea8db22c1655e9861309cc97139357d20e4e64 new file mode 100644 index 000000000..16633475a Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/d9/ea8db22c1655e9861309cc97139357d20e4e64 differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/pushWithCredentials/expected_remote/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/pushWithCredentials/expected_remote/objects/ff/7015f162da19450f2eaf0fc24987104df30e15 b/test/integration/pushWithCredentials/expected_remote/objects/ff/7015f162da19450f2eaf0fc24987104df30e15 new file mode 100644 index 000000000..c79143577 Binary files /dev/null and b/test/integration/pushWithCredentials/expected_remote/objects/ff/7015f162da19450f2eaf0fc24987104df30e15 differ diff --git a/test/integration/pushWithCredentials/expected_remote/packed-refs b/test/integration/pushWithCredentials/expected_remote/packed-refs new file mode 100644 index 000000000..66cf8b589 --- /dev/null +++ b/test/integration/pushWithCredentials/expected_remote/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +ff7015f162da19450f2eaf0fc24987104df30e15 refs/heads/master diff --git a/test/integration/pushWithCredentials/expected_remote/refs/heads/master b/test/integration/pushWithCredentials/expected_remote/refs/heads/master new file mode 100644 index 000000000..b3746d069 --- /dev/null +++ b/test/integration/pushWithCredentials/expected_remote/refs/heads/master @@ -0,0 +1 @@ +75c50688e5a8e48a00d1a824124221bcc6aad640 diff --git a/test/integration/pushWithCredentials/recording.json b/test/integration/pushWithCredentials/recording.json new file mode 100644 index 000000000..961945fef --- /dev/null +++ b/test/integration/pushWithCredentials/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":522,"Mod":0,"Key":256,"Ch":80},{"Timestamp":2106,"Mod":0,"Key":256,"Ch":117},{"Timestamp":2210,"Mod":0,"Key":256,"Ch":115},{"Timestamp":2362,"Mod":0,"Key":256,"Ch":101},{"Timestamp":2386,"Mod":0,"Key":256,"Ch":114},{"Timestamp":2474,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2554,"Mod":0,"Key":256,"Ch":97},{"Timestamp":2643,"Mod":0,"Key":256,"Ch":109},{"Timestamp":2706,"Mod":0,"Key":256,"Ch":101},{"Timestamp":2850,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3722,"Mod":0,"Key":256,"Ch":112},{"Timestamp":3787,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3858,"Mod":0,"Key":256,"Ch":115},{"Timestamp":3987,"Mod":0,"Key":256,"Ch":115},{"Timestamp":4131,"Mod":0,"Key":256,"Ch":119},{"Timestamp":4250,"Mod":0,"Key":256,"Ch":111},{"Timestamp":4313,"Mod":0,"Key":256,"Ch":114},{"Timestamp":4433,"Mod":0,"Key":256,"Ch":100},{"Timestamp":4594,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8227,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/pushWithCredentials/setup.sh b/test/integration/pushWithCredentials/setup.sh new file mode 100644 index 000000000..d39a13d5a --- /dev/null +++ b/test/integration/pushWithCredentials/setup.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +set -e + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" +echo test2 > myfile2 +git add . +git commit -am "myfile2" + +cd .. +git clone --bare ./actual actual_remote + +cd actual + +echo test3 > myfile3 +git add . +git commit -am "myfile3" +echo test4 > myfile4 +git add . +git commit -am "myfile4" + +git remote add origin ../actual_remote +git fetch origin +git branch --set-upstream-to=origin/master master + +# 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! +cp ../../../hooks/pre-push .git/hooks/pre-push +chmod +x .git/hooks/pre-push diff --git a/test/integration/pushWithCredentials/test.json b/test/integration/pushWithCredentials/test.json new file mode 100644 index 000000000..2e3b4aedd --- /dev/null +++ b/test/integration/pushWithCredentials/test.json @@ -0,0 +1,4 @@ +{ + "description": "push changes to the remote, with a password prompt", + "speed": 10 +} diff --git a/test/integration/rebase/setup.sh b/test/integration/rebase/setup.sh index d11e86c0a..8806332fd 100644 --- a/test/integration/rebase/setup.sh +++ b/test/integration/rebase/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/rebase2/setup.sh b/test/integration/rebase2/setup.sh index 864bf5ca9..99c030a87 100644 --- a/test/integration/rebase2/setup.sh +++ b/test/integration/rebase2/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/rebase3/setup.sh b/test/integration/rebase3/setup.sh index 864bf5ca9..99c030a87 100644 --- a/test/integration/rebase3/setup.sh +++ b/test/integration/rebase3/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/rebaseFixupAndSquash/setup.sh b/test/integration/rebaseFixupAndSquash/setup.sh index 864bf5ca9..99c030a87 100644 --- a/test/integration/rebaseFixupAndSquash/setup.sh +++ b/test/integration/rebaseFixupAndSquash/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/rebaseFixups/setup.sh b/test/integration/rebaseFixups/setup.sh index 84c2fab8d..99fec6578 100644 --- a/test/integration/rebaseFixups/setup.sh +++ b/test/integration/rebaseFixups/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/rebaseReword/expected/.git_keep/index b/test/integration/rebaseReword/expected/.git_keep/index deleted file mode 100644 index e4963c7ce..000000000 Binary files a/test/integration/rebaseReword/expected/.git_keep/index and /dev/null differ diff --git a/test/integration/rebaseReword/expected/.git_keep/COMMIT_EDITMSG b/test/integration/rebaseRewordLastCommit/expected/.git_keep/COMMIT_EDITMSG similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/COMMIT_EDITMSG rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/COMMIT_EDITMSG diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/FETCH_HEAD b/test/integration/rebaseRewordLastCommit/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/HEAD b/test/integration/rebaseRewordLastCommit/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/rebaseRewordLastCommit/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/config b/test/integration/rebaseRewordLastCommit/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/rebaseRewordLastCommit/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/description b/test/integration/rebaseRewordLastCommit/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/rebaseRewordLastCommit/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/index b/test/integration/rebaseRewordLastCommit/expected/.git_keep/index new file mode 100644 index 000000000..8481c3b75 Binary files /dev/null and b/test/integration/rebaseRewordLastCommit/expected/.git_keep/index differ diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/info/exclude b/test/integration/rebaseRewordLastCommit/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/rebaseRewordLastCommit/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/rebaseReword/expected/.git_keep/logs/HEAD b/test/integration/rebaseRewordLastCommit/expected/.git_keep/logs/HEAD similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/logs/HEAD rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/logs/HEAD diff --git a/test/integration/rebaseReword/expected/.git_keep/logs/refs/heads/master b/test/integration/rebaseRewordLastCommit/expected/.git_keep/logs/refs/heads/master similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/logs/refs/heads/master rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/logs/refs/heads/master diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/stashFiles/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 diff --git a/test/integration/stashFiles/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/59/f4e88de812c15bf0fa7b224cdb361f7ede8931 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/59/f4e88de812c15bf0fa7b224cdb361f7ede8931 similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/59/f4e88de812c15bf0fa7b224cdb361f7ede8931 rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/59/f4e88de812c15bf0fa7b224cdb361f7ede8931 diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/74/abc9e0d0ec8dd0f5ea872a851364206008ea2b b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/74/abc9e0d0ec8dd0f5ea872a851364206008ea2b similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/74/abc9e0d0ec8dd0f5ea872a851364206008ea2b rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/74/abc9e0d0ec8dd0f5ea872a851364206008ea2b diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/97/066d3866b8e5ead0b68fc746a02222408f28a3 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/97/066d3866b8e5ead0b68fc746a02222408f28a3 similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/97/066d3866b8e5ead0b68fc746a02222408f28a3 rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/97/066d3866b8e5ead0b68fc746a02222408f28a3 diff --git a/test/integration/stashFiles/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/stashFiles/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/d4/83ea1d742e44d9191f3e31e926d7621c513042 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/d4/83ea1d742e44d9191f3e31e926d7621c513042 similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/d4/83ea1d742e44d9191f3e31e926d7621c513042 rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/d4/83ea1d742e44d9191f3e31e926d7621c513042 diff --git a/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/rebaseReword/expected/.git_keep/objects/e2/98537fd470f70bbb174d78f610fe49539cfe66 b/test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/e2/98537fd470f70bbb174d78f610fe49539cfe66 similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/objects/e2/98537fd470f70bbb174d78f610fe49539cfe66 rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/objects/e2/98537fd470f70bbb174d78f610fe49539cfe66 diff --git a/test/integration/rebaseReword/expected/.git_keep/refs/heads/master b/test/integration/rebaseRewordLastCommit/expected/.git_keep/refs/heads/master similarity index 100% rename from test/integration/rebaseReword/expected/.git_keep/refs/heads/master rename to test/integration/rebaseRewordLastCommit/expected/.git_keep/refs/heads/master diff --git a/test/integration/stashFiles/expected/file0 b/test/integration/rebaseRewordLastCommit/expected/file0 similarity index 100% rename from test/integration/stashFiles/expected/file0 rename to test/integration/rebaseRewordLastCommit/expected/file0 diff --git a/test/integration/rebaseRewordLastCommit/expected/file1 b/test/integration/rebaseRewordLastCommit/expected/file1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/rebaseRewordLastCommit/expected/file1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/rebaseRewordLastCommit/expected/file2 b/test/integration/rebaseRewordLastCommit/expected/file2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/rebaseRewordLastCommit/expected/file2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/rebaseRewordLastCommit/expected/file3 b/test/integration/rebaseRewordLastCommit/expected/file3 new file mode 100644 index 000000000..df6b0d2bc --- /dev/null +++ b/test/integration/rebaseRewordLastCommit/expected/file3 @@ -0,0 +1 @@ +test3 diff --git a/test/integration/rebaseReword/recording.json b/test/integration/rebaseRewordLastCommit/recording.json similarity index 100% rename from test/integration/rebaseReword/recording.json rename to test/integration/rebaseRewordLastCommit/recording.json diff --git a/test/integration/rebaseReword/setup.sh b/test/integration/rebaseRewordLastCommit/setup.sh similarity index 97% rename from test/integration/rebaseReword/setup.sh rename to test/integration/rebaseRewordLastCommit/setup.sh index 42957e626..cdd7ac5d0 100644 --- a/test/integration/rebaseReword/setup.sh +++ b/test/integration/rebaseRewordLastCommit/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/rebaseReword/test.json b/test/integration/rebaseRewordLastCommit/test.json similarity index 100% rename from test/integration/rebaseReword/test.json rename to test/integration/rebaseRewordLastCommit/test.json diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/COMMIT_EDITMSG b/test/integration/rebaseRewordOldCommit/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..996bc28b7 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +file1 2 diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/FETCH_HEAD b/test/integration/rebaseRewordOldCommit/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/HEAD b/test/integration/rebaseRewordOldCommit/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/ORIG_HEAD b/test/integration/rebaseRewordOldCommit/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..62f236fad --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +3c3125afd7a6475dcdd4c4a6b20cc920b31eb96f diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/config b/test/integration/rebaseRewordOldCommit/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/description b/test/integration/rebaseRewordOldCommit/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/index b/test/integration/rebaseRewordOldCommit/expected/.git_keep/index new file mode 100644 index 000000000..c1bf8572e Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/index differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/info/exclude b/test/integration/rebaseRewordOldCommit/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/logs/HEAD b/test/integration/rebaseRewordOldCommit/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..2fd699dc0 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/logs/HEAD @@ -0,0 +1,9 @@ +0000000000000000000000000000000000000000 4479c0a1c7e43a55a3a6909be88a810dbad3fc42 CI 1641696830 +1100 commit (initial): file0 +4479c0a1c7e43a55a3a6909be88a810dbad3fc42 3c3125afd7a6475dcdd4c4a6b20cc920b31eb96f CI 1641696830 +1100 commit: file1 +3c3125afd7a6475dcdd4c4a6b20cc920b31eb96f 7c5b8c907caad01842aa84e91b7d4724d57de4fd CI 1641696830 +1100 commit: file2 +7c5b8c907caad01842aa84e91b7d4724d57de4fd 7c5b8c907caad01842aa84e91b7d4724d57de4fd CI 1641696835 +1100 rebase: updating HEAD +7c5b8c907caad01842aa84e91b7d4724d57de4fd 4479c0a1c7e43a55a3a6909be88a810dbad3fc42 CI 1641696835 +1100 rebase -i (start): checkout 4479c0a1c7e43a55a3a6909be88a810dbad3fc42 +4479c0a1c7e43a55a3a6909be88a810dbad3fc42 3c3125afd7a6475dcdd4c4a6b20cc920b31eb96f CI 1641696835 +1100 rebase -i: fast-forward +3c3125afd7a6475dcdd4c4a6b20cc920b31eb96f 1bcb7e30b3a5a5ae64397dcfe6b74cc18fc55784 CI 1641696835 +1100 commit (amend): file1 2 +1bcb7e30b3a5a5ae64397dcfe6b74cc18fc55784 9d793e4fc04a0583eed7670d52fbb16b402f7499 CI 1641696835 +1100 rebase -i (pick): file2 +9d793e4fc04a0583eed7670d52fbb16b402f7499 9d793e4fc04a0583eed7670d52fbb16b402f7499 CI 1641696835 +1100 rebase -i (finish): returning to refs/heads/master diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/logs/refs/heads/master b/test/integration/rebaseRewordOldCommit/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..e62904977 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 4479c0a1c7e43a55a3a6909be88a810dbad3fc42 CI 1641696830 +1100 commit (initial): file0 +4479c0a1c7e43a55a3a6909be88a810dbad3fc42 3c3125afd7a6475dcdd4c4a6b20cc920b31eb96f CI 1641696830 +1100 commit: file1 +3c3125afd7a6475dcdd4c4a6b20cc920b31eb96f 7c5b8c907caad01842aa84e91b7d4724d57de4fd CI 1641696830 +1100 commit: file2 +7c5b8c907caad01842aa84e91b7d4724d57de4fd 9d793e4fc04a0583eed7670d52fbb16b402f7499 CI 1641696835 +1100 rebase -i (finish): refs/heads/master onto 4479c0a1c7e43a55a3a6909be88a810dbad3fc42 diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/1b/cb7e30b3a5a5ae64397dcfe6b74cc18fc55784 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/1b/cb7e30b3a5a5ae64397dcfe6b74cc18fc55784 new file mode 100644 index 000000000..189a77100 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/1b/cb7e30b3a5a5ae64397dcfe6b74cc18fc55784 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 new file mode 100644 index 000000000..79fcadf67 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da new file mode 100644 index 000000000..06c9cb73d Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/3c/3125afd7a6475dcdd4c4a6b20cc920b31eb96f b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/3c/3125afd7a6475dcdd4c4a6b20cc920b31eb96f new file mode 100644 index 000000000..91e0c959b Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/3c/3125afd7a6475dcdd4c4a6b20cc920b31eb96f differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/44/79c0a1c7e43a55a3a6909be88a810dbad3fc42 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/44/79c0a1c7e43a55a3a6909be88a810dbad3fc42 new file mode 100644 index 000000000..a62257409 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/44/79c0a1c7e43a55a3a6909be88a810dbad3fc42 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/64/7b9df53752363ecdc1d4c5cebe7d66e6132bfe b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/64/7b9df53752363ecdc1d4c5cebe7d66e6132bfe new file mode 100644 index 000000000..1a7ba1071 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/64/7b9df53752363ecdc1d4c5cebe7d66e6132bfe @@ -0,0 +1,4 @@ +xAj1 e˶J dUΠd3aƁ>_Åc5sA C +ZVC֒H1LO^1\4T_Y=fB0PSQæ^|qF + +#e:xl`׸-;_r_;ҿd\3>u&y76xGL} \ No newline at end of file diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/7c/5b8c907caad01842aa84e91b7d4724d57de4fd b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/7c/5b8c907caad01842aa84e91b7d4724d57de4fd new file mode 100644 index 000000000..c7c3a4183 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/7c/5b8c907caad01842aa84e91b7d4724d57de4fd differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/9d/793e4fc04a0583eed7670d52fbb16b402f7499 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/9d/793e4fc04a0583eed7670d52fbb16b402f7499 new file mode 100644 index 000000000..a7b197eb9 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/9d/793e4fc04a0583eed7670d52fbb16b402f7499 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c new file mode 100644 index 000000000..0e95eb06d Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/c0/793b482cdf9ca48686dbf56fc0a46e982003e1 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/c0/793b482cdf9ca48686dbf56fc0a46e982003e1 new file mode 100644 index 000000000..da9c5bc32 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/c0/793b482cdf9ca48686dbf56fc0a46e982003e1 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 new file mode 100644 index 000000000..2e9066287 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 @@ -0,0 +1,2 @@ +x+)JMU03c040031QHI5`ֶww.hT[H + yW5Ɨ(| ^-W(x9 \ No newline at end of file diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/d4/83ea1d742e44d9191f3e31e926d7621c513042 b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/d4/83ea1d742e44d9191f3e31e926d7621c513042 new file mode 100644 index 000000000..b0cd18a51 Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/d4/83ea1d742e44d9191f3e31e926d7621c513042 differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b new file mode 100644 index 000000000..9b771fc2f Binary files /dev/null and b/test/integration/rebaseRewordOldCommit/expected/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b differ diff --git a/test/integration/rebaseRewordOldCommit/expected/.git_keep/refs/heads/master b/test/integration/rebaseRewordOldCommit/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..d952177d9 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +9d793e4fc04a0583eed7670d52fbb16b402f7499 diff --git a/test/integration/rebaseRewordOldCommit/expected/file0 b/test/integration/rebaseRewordOldCommit/expected/file0 new file mode 100644 index 000000000..38143ad4a --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/file0 @@ -0,0 +1 @@ +test0 diff --git a/test/integration/rebaseRewordOldCommit/expected/file1 b/test/integration/rebaseRewordOldCommit/expected/file1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/file1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/rebaseRewordOldCommit/expected/file2 b/test/integration/rebaseRewordOldCommit/expected/file2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/file2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/rebaseRewordOldCommit/expected/file3 b/test/integration/rebaseRewordOldCommit/expected/file3 new file mode 100644 index 000000000..df6b0d2bc --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/expected/file3 @@ -0,0 +1 @@ +test3 diff --git a/test/integration/rebaseRewordOldCommit/recording.json b/test/integration/rebaseRewordOldCommit/recording.json new file mode 100644 index 000000000..0d27c4c7f --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":1013,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1332,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1811,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2643,"Mod":0,"Key":256,"Ch":114},{"Timestamp":3269,"Mod":0,"Key":256,"Ch":32},{"Timestamp":3795,"Mod":0,"Key":256,"Ch":50},{"Timestamp":4420,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5859,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/rebaseRewordOldCommit/setup.sh b/test/integration/rebaseRewordOldCommit/setup.sh new file mode 100644 index 000000000..cdd7ac5d0 --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/setup.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test0 > file0 +git add . +git commit -am file0 + +echo test1 > file1 +git add . +git commit -am file1 + +echo test2 > file2 +git add . +git commit -am file2 + +echo test3 > file3 +git add . diff --git a/test/integration/rebaseRewordOldCommit/test.json b/test/integration/rebaseRewordOldCommit/test.json new file mode 100644 index 000000000..b75601d7e --- /dev/null +++ b/test/integration/rebaseRewordOldCommit/test.json @@ -0,0 +1,4 @@ +{ + "description": "Rewording old commit", + "speed": 20 +} diff --git a/test/integration/rebaseSwapping/setup.sh b/test/integration/rebaseSwapping/setup.sh index 864bf5ca9..99c030a87 100644 --- a/test/integration/rebaseSwapping/setup.sh +++ b/test/integration/rebaseSwapping/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/reflogCheckout/setup.sh b/test/integration/reflogCheckout/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/reflogCheckout/setup.sh +++ b/test/integration/reflogCheckout/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/reflogCherryPick/setup.sh b/test/integration/reflogCherryPick/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/reflogCherryPick/setup.sh +++ b/test/integration/reflogCherryPick/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/reflogCommitFiles/setup.sh b/test/integration/reflogCommitFiles/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/reflogCommitFiles/setup.sh +++ b/test/integration/reflogCommitFiles/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/reflogHardReset/setup.sh b/test/integration/reflogHardReset/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/reflogHardReset/setup.sh +++ b/test/integration/reflogHardReset/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/COMMIT_EDITMSG b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..2ff0cc013 --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +second commit diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/FETCH_HEAD b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/HEAD b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/config b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/config new file mode 100644 index 000000000..596ebaeb3 --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/description b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/index b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/index new file mode 100644 index 000000000..d31935681 Binary files /dev/null and b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/index differ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/info/exclude b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/logs/HEAD b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..2bf564e5f --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 c2bf9b666a310383fd7095bc5bd993bba11b040e CI 1640438057 +0100 commit (initial): first commit +c2bf9b666a310383fd7095bc5bd993bba11b040e d0ce4cb10cd926f646a08889b077a6d7eddd3534 CI 1640438062 +0100 commit: second commit diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/logs/refs/heads/master b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..2bf564e5f --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 c2bf9b666a310383fd7095bc5bd993bba11b040e CI 1640438057 +0100 commit (initial): first commit +c2bf9b666a310383fd7095bc5bd993bba11b040e d0ce4cb10cd926f646a08889b077a6d7eddd3534 CI 1640438062 +0100 commit: second commit diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/6c/493ff740f9380390d5c9ddef4af18697ac9375 b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/6c/493ff740f9380390d5c9ddef4af18697ac9375 new file mode 100644 index 000000000..7d217a785 Binary files /dev/null and b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/6c/493ff740f9380390d5c9ddef4af18697ac9375 differ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/ae/ac8b060acee50f309eb1f6698a981c50bdf493 b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/ae/ac8b060acee50f309eb1f6698a981c50bdf493 new file mode 100644 index 000000000..9806bc4bd Binary files /dev/null and b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/ae/ac8b060acee50f309eb1f6698a981c50bdf493 differ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/c2/bf9b666a310383fd7095bc5bd993bba11b040e b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/c2/bf9b666a310383fd7095bc5bd993bba11b040e new file mode 100644 index 000000000..e92225048 Binary files /dev/null and b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/c2/bf9b666a310383fd7095bc5bd993bba11b040e differ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/c9/62a96f68e65b4dc8e0fea12db5f9006091efdf b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/c9/62a96f68e65b4dc8e0fea12db5f9006091efdf new file mode 100644 index 000000000..285462492 Binary files /dev/null and b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/c9/62a96f68e65b4dc8e0fea12db5f9006091efdf differ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/d0/ce4cb10cd926f646a08889b077a6d7eddd3534 b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/d0/ce4cb10cd926f646a08889b077a6d7eddd3534 new file mode 100644 index 000000000..76baa3646 Binary files /dev/null and b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/d0/ce4cb10cd926f646a08889b077a6d7eddd3534 differ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/e2/129701f1a4d54dc44f03c93bca0a2aec7c5449 b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/e2/129701f1a4d54dc44f03c93bca0a2aec7c5449 new file mode 100644 index 000000000..08245d001 Binary files /dev/null and b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/objects/e2/129701f1a4d54dc44f03c93bca0a2aec7c5449 differ diff --git a/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/refs/heads/master b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..13943fa1b --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +d0ce4cb10cd926f646a08889b077a6d7eddd3534 diff --git a/test/integration/rememberCommitMessageAfterFail/expected/file1 b/test/integration/rememberCommitMessageAfterFail/expected/file1 new file mode 100644 index 000000000..e2129701f --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/expected/file1 @@ -0,0 +1 @@ +file1 diff --git a/test/integration/stashFiles/expected/.git_keep/COMMIT_EDITMSG b/test/integration/rememberCommitMessageAfterFail/expected/file2 similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/COMMIT_EDITMSG rename to test/integration/rememberCommitMessageAfterFail/expected/file2 diff --git a/test/integration/rememberCommitMessageAfterFail/recording.json b/test/integration/rememberCommitMessageAfterFail/recording.json new file mode 100644 index 000000000..39d0fcfa0 --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":1360,"Mod":0,"Key":256,"Ch":106},{"Timestamp":2124,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4000,"Mod":0,"Key":256,"Ch":99},{"Timestamp":4595,"Mod":0,"Key":256,"Ch":102},{"Timestamp":4730,"Mod":0,"Key":256,"Ch":105},{"Timestamp":4831,"Mod":0,"Key":256,"Ch":114},{"Timestamp":5011,"Mod":0,"Key":256,"Ch":115},{"Timestamp":5122,"Mod":0,"Key":256,"Ch":116},{"Timestamp":5552,"Mod":0,"Key":256,"Ch":32},{"Timestamp":5778,"Mod":0,"Key":256,"Ch":99},{"Timestamp":5882,"Mod":0,"Key":256,"Ch":111},{"Timestamp":6046,"Mod":0,"Key":256,"Ch":109},{"Timestamp":6212,"Mod":0,"Key":256,"Ch":109},{"Timestamp":6449,"Mod":0,"Key":256,"Ch":105},{"Timestamp":6550,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7347,"Mod":0,"Key":13,"Ch":13},{"Timestamp":9314,"Mod":0,"Key":13,"Ch":13},{"Timestamp":10322,"Mod":0,"Key":256,"Ch":107},{"Timestamp":11012,"Mod":0,"Key":256,"Ch":100},{"Timestamp":11547,"Mod":0,"Key":13,"Ch":13},{"Timestamp":12960,"Mod":0,"Key":256,"Ch":99},{"Timestamp":13863,"Mod":0,"Key":13,"Ch":13},{"Timestamp":15574,"Mod":0,"Key":256,"Ch":32},{"Timestamp":16365,"Mod":0,"Key":256,"Ch":99},{"Timestamp":16977,"Mod":0,"Key":256,"Ch":115},{"Timestamp":17111,"Mod":0,"Key":256,"Ch":101},{"Timestamp":17308,"Mod":0,"Key":256,"Ch":99},{"Timestamp":17420,"Mod":0,"Key":256,"Ch":111},{"Timestamp":17442,"Mod":0,"Key":256,"Ch":110},{"Timestamp":17593,"Mod":0,"Key":256,"Ch":100},{"Timestamp":17814,"Mod":0,"Key":256,"Ch":32},{"Timestamp":18040,"Mod":0,"Key":256,"Ch":99},{"Timestamp":18127,"Mod":0,"Key":256,"Ch":111},{"Timestamp":18269,"Mod":0,"Key":256,"Ch":109},{"Timestamp":18409,"Mod":0,"Key":256,"Ch":109},{"Timestamp":18694,"Mod":0,"Key":256,"Ch":105},{"Timestamp":18803,"Mod":0,"Key":256,"Ch":116},{"Timestamp":19624,"Mod":0,"Key":13,"Ch":13},{"Timestamp":21204,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":212,"Height":55}]} \ No newline at end of file diff --git a/test/integration/rememberCommitMessageAfterFail/setup.sh b/test/integration/rememberCommitMessageAfterFail/setup.sh new file mode 100644 index 000000000..0ac33d4c9 --- /dev/null +++ b/test/integration/rememberCommitMessageAfterFail/setup.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -e + +cd $1 + +git init +git config user.email "CI@example.com" +git config user.name "CI" + +git checkout -b master + +echo "file1" > file1 +echo "file2" > file2 +echo "disruptive" > disruptive +cat > .git/hooks/pre-commit < 1642499804 +1100 commit (initial): file1 diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/logs/refs/heads/master b/test/integration/staginWithDiffContextChange/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..731f404f9 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1854ab416d299cda0227d62b9ab0765e5551ef57 CI 1642499804 +1100 commit (initial): file1 diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/15/8e9a9c1a9627ea0ef4de8b00a503ed0f80a09e b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/15/8e9a9c1a9627ea0ef4de8b00a503ed0f80a09e new file mode 100644 index 000000000..2c00719b0 Binary files /dev/null and b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/15/8e9a9c1a9627ea0ef4de8b00a503ed0f80a09e differ diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/18/54ab416d299cda0227d62b9ab0765e5551ef57 b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/18/54ab416d299cda0227d62b9ab0765e5551ef57 new file mode 100644 index 000000000..efdc77489 Binary files /dev/null and b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/18/54ab416d299cda0227d62b9ab0765e5551ef57 differ diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/2c/484c0a45f3726375600319f73978221a74b783 b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/2c/484c0a45f3726375600319f73978221a74b783 new file mode 100644 index 000000000..342ac1eb8 Binary files /dev/null and b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/2c/484c0a45f3726375600319f73978221a74b783 differ diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/7b/9c2149f16c706cea79b03234cb67fde7e9b68f b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/7b/9c2149f16c706cea79b03234cb67fde7e9b68f new file mode 100644 index 000000000..611ed37a4 Binary files /dev/null and b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/7b/9c2149f16c706cea79b03234cb67fde7e9b68f differ diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/8a/af931e5367e5af9d2e2c014800d22190352b14 b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/8a/af931e5367e5af9d2e2c014800d22190352b14 new file mode 100644 index 000000000..cb168bb1d Binary files /dev/null and b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/8a/af931e5367e5af9d2e2c014800d22190352b14 differ diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/fd/c28832bb15c80146150a24a018088c9df4f8cd b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/fd/c28832bb15c80146150a24a018088c9df4f8cd new file mode 100644 index 000000000..af8771ae4 Binary files /dev/null and b/test/integration/staginWithDiffContextChange/expected/.git_keep/objects/fd/c28832bb15c80146150a24a018088c9df4f8cd differ diff --git a/test/integration/staginWithDiffContextChange/expected/.git_keep/refs/heads/master b/test/integration/staginWithDiffContextChange/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..c5cccb127 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +1854ab416d299cda0227d62b9ab0765e5551ef57 diff --git a/test/integration/staginWithDiffContextChange/expected/one.txt b/test/integration/staginWithDiffContextChange/expected/one.txt new file mode 100644 index 000000000..c3323cfc9 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/expected/one.txt @@ -0,0 +1,67 @@ +package oscommands + +import ( + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// NewDummyOSCommand creates a new dummy OSCommand for testing +func NewDummyOSCommand() *OSCommand { + osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + + return osCmd +} + +type OSCommandDeps struct { + Common *common.Common + Platform *Platform + GetenvFn func(string) string + RemoveFileFn func(string) error + Cmd *CmdObjBuilder +} + +func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { + if deps.Cmd == nil { + panic("WHAT") + } + common := deps.Common + if common == nil { + common = utils.NewDummyCommon() + } + + platform := deps.Platform + if platform == nil { + platform = dummyPlatform + } + + return &OSCommand{ + Common: common, + Platform: platform, + getenvFn: deps.GetenvFn, + removeFileFn: deps.RemoveFileFn, + guiIO: NewNullGuiIO(utils.NewDummyLog()), + Cmd: deps.Cmd, + } +} + +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(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + osCommand.Cmd = NewDummyCmdObjBuilder(runner) + + return osCommand +} diff --git a/test/integration/staginWithDiffContextChange/files/one.txt b/test/integration/staginWithDiffContextChange/files/one.txt new file mode 100644 index 000000000..158e9a9c1 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/files/one.txt @@ -0,0 +1,63 @@ +package oscommands + +import ( + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// NewDummyOSCommand creates a new dummy OSCommand for testing +func NewDummyOSCommand() *OSCommand { + osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + + return osCmd +} + +type OSCommandDeps struct { + Common *common.Common + Platform *Platform + GetenvFn func(string) string + RemoveFileFn func(string) error + Cmd *CmdObjBuilder +} + +func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { + common := deps.Common + if common == nil { + common = utils.NewDummyCommon() + } + + platform := deps.Platform + if platform == nil { + platform = dummyPlatform + } + + return &OSCommand{ + Common: common, + Platform: platform, + getenvFn: deps.GetenvFn, + removeFileFn: deps.RemoveFileFn, + guiIO: NewNullGuiIO(utils.NewDummyLog()), + } +} + +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(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + osCommand.Cmd = NewDummyCmdObjBuilder(runner) + + return osCommand +} diff --git a/test/integration/staginWithDiffContextChange/files/one_new.txt b/test/integration/staginWithDiffContextChange/files/one_new.txt new file mode 100644 index 000000000..c3323cfc9 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/files/one_new.txt @@ -0,0 +1,67 @@ +package oscommands + +import ( + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// NewDummyOSCommand creates a new dummy OSCommand for testing +func NewDummyOSCommand() *OSCommand { + osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + + return osCmd +} + +type OSCommandDeps struct { + Common *common.Common + Platform *Platform + GetenvFn func(string) string + RemoveFileFn func(string) error + Cmd *CmdObjBuilder +} + +func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { + if deps.Cmd == nil { + panic("WHAT") + } + common := deps.Common + if common == nil { + common = utils.NewDummyCommon() + } + + platform := deps.Platform + if platform == nil { + platform = dummyPlatform + } + + return &OSCommand{ + Common: common, + Platform: platform, + getenvFn: deps.GetenvFn, + removeFileFn: deps.RemoveFileFn, + guiIO: NewNullGuiIO(utils.NewDummyLog()), + Cmd: deps.Cmd, + } +} + +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(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + osCommand.Cmd = NewDummyCmdObjBuilder(runner) + + return osCommand +} diff --git a/test/integration/staginWithDiffContextChange/recording.json b/test/integration/staginWithDiffContextChange/recording.json new file mode 100644 index 000000000..5bce04f41 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":1395,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3036,"Mod":0,"Key":256,"Ch":125},{"Timestamp":3283,"Mod":0,"Key":256,"Ch":125},{"Timestamp":3483,"Mod":0,"Key":256,"Ch":125},{"Timestamp":3996,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4629,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5004,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5067,"Mod":0,"Key":256,"Ch":118},{"Timestamp":5547,"Mod":0,"Key":257,"Ch":0},{"Timestamp":5867,"Mod":0,"Key":256,"Ch":118},{"Timestamp":6492,"Mod":0,"Key":256,"Ch":118},{"Timestamp":6628,"Mod":0,"Key":257,"Ch":0},{"Timestamp":6762,"Mod":0,"Key":257,"Ch":0},{"Timestamp":7779,"Mod":0,"Key":256,"Ch":125},{"Timestamp":8179,"Mod":0,"Key":256,"Ch":125},{"Timestamp":8723,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9531,"Mod":0,"Key":259,"Ch":0},{"Timestamp":10170,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10379,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10500,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10833,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10849,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10865,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10881,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10898,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10915,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10931,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10947,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10964,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11156,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11308,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11435,"Mod":0,"Key":256,"Ch":118},{"Timestamp":11571,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11700,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11828,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11963,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12083,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12603,"Mod":0,"Key":256,"Ch":32},{"Timestamp":13051,"Mod":0,"Key":9,"Ch":9},{"Timestamp":14820,"Mod":0,"Key":256,"Ch":118},{"Timestamp":15028,"Mod":0,"Key":258,"Ch":0},{"Timestamp":15139,"Mod":0,"Key":258,"Ch":0},{"Timestamp":15266,"Mod":0,"Key":258,"Ch":0},{"Timestamp":15387,"Mod":0,"Key":258,"Ch":0},{"Timestamp":15500,"Mod":0,"Key":258,"Ch":0},{"Timestamp":16266,"Mod":0,"Key":256,"Ch":125},{"Timestamp":16483,"Mod":0,"Key":256,"Ch":125},{"Timestamp":16747,"Mod":0,"Key":256,"Ch":125},{"Timestamp":17412,"Mod":0,"Key":256,"Ch":32},{"Timestamp":19124,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/staginWithDiffContextChange/setup.sh b/test/integration/staginWithDiffContextChange/setup.sh new file mode 100644 index 000000000..ac450f8d2 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/setup.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +cp ../files/one.txt one.txt +git add . +git commit -am file1 + +cp ../files/one_new.txt one.txt diff --git a/test/integration/staginWithDiffContextChange/test.json b/test/integration/staginWithDiffContextChange/test.json new file mode 100644 index 000000000..0351e0ec9 --- /dev/null +++ b/test/integration/staginWithDiffContextChange/test.json @@ -0,0 +1,4 @@ +{ + "description": "Staging a file and also changing the diff context", + "speed": 20 +} diff --git a/test/integration/staging/expected/.git_keep/COMMIT_EDITMSG b/test/integration/staging/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..9daeafb98 --- /dev/null +++ b/test/integration/staging/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +test diff --git a/test/integration/staging/expected/.git_keep/FETCH_HEAD b/test/integration/staging/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/staging/expected/.git_keep/HEAD b/test/integration/staging/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/staging/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/staging/expected/.git_keep/config b/test/integration/staging/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/staging/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/staging/expected/.git_keep/description b/test/integration/staging/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/staging/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/staging/expected/.git_keep/index b/test/integration/staging/expected/.git_keep/index new file mode 100644 index 000000000..3735bb8d9 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/index differ diff --git a/test/integration/staging/expected/.git_keep/info/exclude b/test/integration/staging/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/staging/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/staging/expected/.git_keep/logs/HEAD b/test/integration/staging/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..fea22e271 --- /dev/null +++ b/test/integration/staging/expected/.git_keep/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 6f7e9e66f080162af7ebab016d02550145cfda66 CI 1642499582 +1100 commit (initial): file1 +6f7e9e66f080162af7ebab016d02550145cfda66 0b6860367a6e7794985007cadf0aaf04c801e59e CI 1642499609 +1100 commit: test diff --git a/test/integration/staging/expected/.git_keep/logs/refs/heads/master b/test/integration/staging/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..fea22e271 --- /dev/null +++ b/test/integration/staging/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 6f7e9e66f080162af7ebab016d02550145cfda66 CI 1642499582 +1100 commit (initial): file1 +6f7e9e66f080162af7ebab016d02550145cfda66 0b6860367a6e7794985007cadf0aaf04c801e59e CI 1642499609 +1100 commit: test diff --git a/test/integration/staging/expected/.git_keep/objects/05/9586b468b89bf98e3b62126f455ab15bea4a5f b/test/integration/staging/expected/.git_keep/objects/05/9586b468b89bf98e3b62126f455ab15bea4a5f new file mode 100644 index 000000000..08eb1a0d1 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/objects/05/9586b468b89bf98e3b62126f455ab15bea4a5f differ diff --git a/test/integration/staging/expected/.git_keep/objects/0b/6860367a6e7794985007cadf0aaf04c801e59e b/test/integration/staging/expected/.git_keep/objects/0b/6860367a6e7794985007cadf0aaf04c801e59e new file mode 100644 index 000000000..330f8f8f7 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/objects/0b/6860367a6e7794985007cadf0aaf04c801e59e differ diff --git a/test/integration/staging/expected/.git_keep/objects/12/c4186053ecd4056526743060a8fe87429b7306 b/test/integration/staging/expected/.git_keep/objects/12/c4186053ecd4056526743060a8fe87429b7306 new file mode 100644 index 000000000..65f9a5559 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/objects/12/c4186053ecd4056526743060a8fe87429b7306 differ diff --git a/test/integration/staging/expected/.git_keep/objects/3e/95c983db9349a26b20fccbdaa933e805ff817e b/test/integration/staging/expected/.git_keep/objects/3e/95c983db9349a26b20fccbdaa933e805ff817e new file mode 100644 index 000000000..2da57d308 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/objects/3e/95c983db9349a26b20fccbdaa933e805ff817e differ diff --git a/test/integration/staging/expected/.git_keep/objects/40/ce5b93f72e04cb876afaaf91398c2821260b95 b/test/integration/staging/expected/.git_keep/objects/40/ce5b93f72e04cb876afaaf91398c2821260b95 new file mode 100644 index 000000000..8c6fa3ec6 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/objects/40/ce5b93f72e04cb876afaaf91398c2821260b95 differ diff --git a/test/integration/staging/expected/.git_keep/objects/63/5b45efaba0c2415658bc121de201ec43a47920 b/test/integration/staging/expected/.git_keep/objects/63/5b45efaba0c2415658bc121de201ec43a47920 new file mode 100644 index 000000000..860950f65 --- /dev/null +++ b/test/integration/staging/expected/.git_keep/objects/63/5b45efaba0c2415658bc121de201ec43a47920 @@ -0,0 +1,4 @@ +xMn0 ;)NtN:dYhK8Yt%y#>7Y|\OEotD=FƓ&mHDVR+q%)\KǛ#;dHaOB6 +Q» 8h*%KM}Q2"HGkϟ[`:DBUuJO=+4<3Zpo~lFU<WJy߯FMdqn&E۞8|m/)x_ 87עJU (?"#o -΢@ \ No newline at end of file diff --git a/test/integration/staging/expected/.git_keep/objects/dc/02541428fdc15b30bd2174fcbcd43d388eab82 b/test/integration/staging/expected/.git_keep/objects/dc/02541428fdc15b30bd2174fcbcd43d388eab82 new file mode 100644 index 000000000..232769986 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/objects/dc/02541428fdc15b30bd2174fcbcd43d388eab82 differ diff --git a/test/integration/staging/expected/.git_keep/objects/e8/aaa2f356eb341c693e239467fd200d0117b487 b/test/integration/staging/expected/.git_keep/objects/e8/aaa2f356eb341c693e239467fd200d0117b487 new file mode 100644 index 000000000..f444e5a1e --- /dev/null +++ b/test/integration/staging/expected/.git_keep/objects/e8/aaa2f356eb341c693e239467fd200d0117b487 @@ -0,0 +1 @@ +x==0 S/4ީ-*@vllі0gr0'ɓY )/.8I2Mpp_D;⇘F<= oaq6bV =1IV#:w+6#&8{/ _kv(WmJ]z)} ]dαk™Yx*81<ȩp ~TC;k>^%G g ffPˆKΰ%x1\hhgD Ljۈdϟ[`:H YkKWEKtQqmaCRn~?QnR kmtqνs+|"x_!fvؓVůe'ڟwA/Z4!oZZ- b \ No newline at end of file diff --git a/test/integration/staging/expected/.git_keep/objects/fb/e09a11933b44ea60b46bd0f3d44142cb6189a4 b/test/integration/staging/expected/.git_keep/objects/fb/e09a11933b44ea60b46bd0f3d44142cb6189a4 new file mode 100644 index 000000000..3d85419a6 Binary files /dev/null and b/test/integration/staging/expected/.git_keep/objects/fb/e09a11933b44ea60b46bd0f3d44142cb6189a4 differ diff --git a/test/integration/staging/expected/.git_keep/refs/heads/master b/test/integration/staging/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..9da93ce1c --- /dev/null +++ b/test/integration/staging/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +0b6860367a6e7794985007cadf0aaf04c801e59e diff --git a/test/integration/staging/expected/one.txt b/test/integration/staging/expected/one.txt new file mode 100644 index 000000000..3a54c9212 --- /dev/null +++ b/test/integration/staging/expected/one.txt @@ -0,0 +1,15 @@ +Out there, we've walked quite friendly up to Death, -- +Sat down and eaten with him, cool and bland, -- +Pardoned his spilling mess-tins in our hand. +We've sniffed the green thick smell of his breath, -- +Our eyes wept, but our courage did not writhe. +He's spat at us with bullets and he's coughed +Shrapnel. We sang when he sang aloft, +We whistled while he shaved us with his scythe. + +Oh, Death was never enemy of ours! +We laughed at him, we leagued with him, old chum. +No soldier's paid to kick against His powers. +We laughed, — knowing that greater men would come, +And greater wars: when each proud fighter brags +He wars on Death, for lives; not men, for flags diff --git a/test/integration/staging/expected/three.txt b/test/integration/staging/expected/three.txt new file mode 100644 index 000000000..3158e329a --- /dev/null +++ b/test/integration/staging/expected/three.txt @@ -0,0 +1,299 @@ +package gui + +import ( + "fmt" + "regexp" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/filetree" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// list panel functions + + if selectedLine == -1 { + return nil + } + + return gui.State.FileManager.GetItemAtIndex(selectedLine) + return gui.State.nodeManager.GetItemAtIndex(selectedLine) +} + +func (gui *Gui) getSelectednode() *models.node { + node := gui.getSelectednodeNode() + if node == nil { + return nil + } + return node.node +} + +func (gui *Gui) getSelectedPath() string { + node := gui.getSelectednodeNode() + if node == nil { + return "" + } + + return node.GetPath() +} + +func (gui *Gui) filesRenderToMain() error { + node := gui.getSelectedFileNode() +func (gui *Gui) nodesRenderToMain() error { + node := gui.getSelectednodeNode() + + if node == nil { + return gui.refreshMainViews(refreshMainOpts{ + main: &viewUpdateOpts{ + title: "", + task: NewRenderStringTask(gui.Tr.NoChangednodes), + }, + }) + } + + if node.node != nil && node.File.HasInlineMergeConflicts { + return gui.renderConflictsFromFilesPanel() + } + + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) + + refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ + title: gui.Tr.UnstagedChanges, + task: NewRunPtyTask(cmdObj.GetCmd()), + }} + + if node.GetHasUnstagedChanges() { + if node.GetHasStagedChanges() { + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView) + + refreshOpts.secondary = &viewUpdateOpts{ + title: gui.Tr.StagedChanges, + task: NewRunPtyTask(cmdObj.GetCmd()), + } + } + } else { + refreshOpts.main.title = gui.Tr.StagedChanges + } + + return gui.refreshMainViews(refreshOpts) +} + +func (gui *Gui) refreshFilesAndSubmodules() error { + gui.Mutexes.RefreshingFilesMutex.Lock() + gui.State.IsRefreshingFiles = true + defer func() { + gui.State.IsRefreshingFiles = false + gui.Mutexes.RefreshingFilesMutex.Unlock() + }() + + selectedPath := gui.getSelectedPath() + + if err := gui.refreshStateSubmoduleConfigs(); err != nil { + return err + } + if err := gui.refreshStateFiles(); err != nil { + return err + } + + gui.OnUIThread(func() error { + if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil { + gui.Log.Error(err) + } + + if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY { + // doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below + if err := gui.State.Contexts.Files.HandleRender(); err != nil { + return err + } + } + + if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (gui.g.CurrentView() == gui.Views.Main && ContextKey(gui.g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) { + newSelectedPath := gui.getSelectedPath() + alreadySelected := selectedPath != "" && newSelectedPath == selectedPath + if !alreadySelected { + gui.takeOverMergeConflictScrolling() + } + + gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) + return gui.filesRenderToMain() + } + + return nil + }) + + return nil +} + +// specific functions + +func (gui *Gui) stagedFiles() []*models.File { + files := gui.State.FileManager.GetAllFiles() + result := make([]*models.File, 0) + for _, file := range files { + if file.HasStagedChanges { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) trackedFiles() []*models.File { + files := gui.State.FileManager.GetAllFiles() + result := make([]*models.File, 0, len(files)) + for _, file := range files { + if file.Tracked { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) stageSelectedFile() error { + file := gui.getSelectedFile() + if file == nil { + return nil + } + + return gui.Git.WorkingTree.StageFile(file.Name) +} + +func (gui *Gui) handleEnterFile() error { + return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1}) +} + +func (gui *Gui) enterFile(opts OnFocusOpts) error { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + + if node.File == nil { + return gui.handleToggleDirCollapsed() + } + + file := node.File + + submoduleConfigs := gui.State.Submodules + if file.IsSubmodule(submoduleConfigs) { + submoduleConfig := file.SubmoduleConfig(submoduleConfigs) + return gui.enterSubmodule(submoduleConfig) + } + + if file.HasInlineMergeConflicts { + return gui.switchToMerge() + } + if file.HasMergeConflicts { + return gui.createErrorPanel(gui.Tr.FileStagingRequirements) + } + + return gui.pushContext(gui.State.Contexts.Staging, opts) +} + +func (gui *Gui) handleFilePress() error { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + + if node.IsLeaf() { + file := node.File + + if file.HasInlineMergeConflicts { + return gui.switchToMerge() + } + + if node.HasUnstagedChanges { + gui.logAction(gui.Tr.Actions.Stagenode) + if err := gui.Git.WorkingTree.Stagenode(node.Name); err != nil { + return gui.surfaceError(err) + } + } else { + gui.logAction(gui.Tr.Actions.Unstagenode) + if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil { + return gui.surfaceError(err) + } + } + } else { + if node.GetHasInlineMergeConflicts() { + return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts) + } + + if node.GetHasUnstagedChanges() { + gui.logAction(gui.Tr.Actions.Stagenode) + if err := gui.Git.WorkingTree.Stagenode(node.Path); err != nil { + return gui.surfaceError(err) + } + } else { + // pretty sure it doesn't matter that we're always passing true here + gui.logAction(gui.Tr.Actions.Unstagenode) + if err := gui.Git.WorkingTree.UnStagenode([]string{node.Path}, true); err != nil { + return gui.surfaceError(err) + } + } + } + + if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil { + return err + } + + return gui.State.Contexts.nodes.HandleFocus() +} + +func (gui *Gui) allnodesStaged() bool { + for _, node := range gui.State.nodeManager.GetAllnodes() { + if node.HasUnstagedChanges { + return false + } + } + return true +} + +func (gui *Gui) onFocusnode() error { + gui.takeOverMergeConflictScrolling() + return nil +} + +func (gui *Gui) handleStageAll() error { + var err error + if gui.allnodesStaged() { + gui.logAction(gui.Tr.Actions.UnstageAllnodes) + err = gui.Git.WorkingTree.UnstageAll() + } else { + gui.logAction(gui.Tr.Actions.StageAllnodes) + err = gui.Git.WorkingTree.StageAll() + } + if err != nil { + _ = gui.surfaceError(err) + } + + if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil { + return err + } + + return gui.State.Contexts.nodes.HandleFocus() +} + +func (gui *Gui) handleIgnorenode() error { + node := gui.getSelectednodeNode() + if node == nil { + return nil + } + + if node.GetPath() == ".gitignore" { + return gui.createErrorPanel("Cannot ignore .gitignore") + } + + unstagenodes := func() error { + return node.ForEachnode(func(node *models.node) error { + if node.HasStagedChanges { + if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil { + return err + } + } + + return nil + }) + } diff --git a/test/integration/staging/expected/two.txt b/test/integration/staging/expected/two.txt new file mode 100644 index 000000000..b4ebbd4f1 --- /dev/null +++ b/test/integration/staging/expected/two.txt @@ -0,0 +1,33 @@ +type createMenuOptions struct { + showCancel bool +} + +func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error { + if createMenuOptions.showCancel { + // this is mutative but I'm okay with that for now + items = app(items, &menuItem{ + d: []string{gui.Tr.LcCancel}, + onPress: func() error { + return nil + }, + }) + } + + gui.State.MenuItems = items + + stringArrays := make([][]string, len(items)) + for i, items := range items { + if items.opensMenu && item.displayStrings != nil { + return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user") + } + + if item.displayStrings == nil { + styledStr := item.displayString + if item.opensMenu { + styledStr = opensMenuStyle(styledStr) + } + stringArrays[i] = []string{styledStr} + } else { + stringArrays[i] = item.displayStrings + } + } diff --git a/test/integration/staging/files/one.txt b/test/integration/staging/files/one.txt new file mode 100644 index 000000000..e8aaa2f35 --- /dev/null +++ b/test/integration/staging/files/one.txt @@ -0,0 +1,15 @@ +Out there, we've walked quite friendly up to Death, -- +Sat down and eaten with him, cool and bland, -- +Pardoned his spilling mess-tins in our hand. +We've sniffed the green thick odour of his breath, -- +Our eyes wept, but our courage didn't writhe. +He's spat at us with bullets and he's coughed +Shrapnel. We chorused when he sang aloft, +We whistled while he shaved us with his scythe. + +Oh, Death was never enemy of ours! +We laughed at him, we leagued with him, old chum. +No soldier's paid to kick against His powers. +We laughed, — knowing that better men would come, +And greater wars: when each proud fighter brags +He wars on Death, for lives; not men, for flags diff --git a/test/integration/staging/files/one_new.txt b/test/integration/staging/files/one_new.txt new file mode 100644 index 000000000..3a54c9212 --- /dev/null +++ b/test/integration/staging/files/one_new.txt @@ -0,0 +1,15 @@ +Out there, we've walked quite friendly up to Death, -- +Sat down and eaten with him, cool and bland, -- +Pardoned his spilling mess-tins in our hand. +We've sniffed the green thick smell of his breath, -- +Our eyes wept, but our courage did not writhe. +He's spat at us with bullets and he's coughed +Shrapnel. We sang when he sang aloft, +We whistled while he shaved us with his scythe. + +Oh, Death was never enemy of ours! +We laughed at him, we leagued with him, old chum. +No soldier's paid to kick against His powers. +We laughed, — knowing that greater men would come, +And greater wars: when each proud fighter brags +He wars on Death, for lives; not men, for flags diff --git a/test/integration/staging/files/three.txt b/test/integration/staging/files/three.txt new file mode 100644 index 000000000..a04255341 --- /dev/null +++ b/test/integration/staging/files/three.txt @@ -0,0 +1,300 @@ +package gui + +import ( + "fmt" + "regexp" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/filetree" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// list panel functions + +func (gui *Gui) getSelectedFileNode() *filetree.FileNode { + selectedLine := gui.State.Panels.Files.SelectedLineIdx + if selectedLine == -1 { + return nil + } + + return gui.State.FileManager.GetItemAtIndex(selectedLine) +} + +func (gui *Gui) getSelectedFile() *models.File { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + return node.File +} + +func (gui *Gui) getSelectedPath() string { + node := gui.getSelectedFileNode() + if node == nil { + return "" + } + + return node.GetPath() +} + +func (gui *Gui) filesRenderToMain() error { + node := gui.getSelectedFileNode() + + if node == nil { + return gui.refreshMainViews(refreshMainOpts{ + main: &viewUpdateOpts{ + title: "", + task: NewRenderStringTask(gui.Tr.NoChangedFiles), + }, + }) + } + + if node.File != nil && node.File.HasInlineMergeConflicts { + return gui.renderConflictsFromFilesPanel() + } + + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) + + refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ + title: gui.Tr.UnstagedChanges, + task: NewRunPtyTask(cmdObj.GetCmd()), + }} + + if node.GetHasUnstagedChanges() { + if node.GetHasStagedChanges() { + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView) + + refreshOpts.secondary = &viewUpdateOpts{ + title: gui.Tr.StagedChanges, + task: NewRunPtyTask(cmdObj.GetCmd()), + } + } + } else { + refreshOpts.main.title = gui.Tr.StagedChanges + } + + return gui.refreshMainViews(refreshOpts) +} + +func (gui *Gui) refreshFilesAndSubmodules() error { + gui.Mutexes.RefreshingFilesMutex.Lock() + gui.State.IsRefreshingFiles = true + defer func() { + gui.State.IsRefreshingFiles = false + gui.Mutexes.RefreshingFilesMutex.Unlock() + }() + + selectedPath := gui.getSelectedPath() + + if err := gui.refreshStateSubmoduleConfigs(); err != nil { + return err + } + if err := gui.refreshStateFiles(); err != nil { + return err + } + + gui.OnUIThread(func() error { + if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil { + gui.Log.Error(err) + } + + if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY { + // doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below + if err := gui.State.Contexts.Files.HandleRender(); err != nil { + return err + } + } + + if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (gui.g.CurrentView() == gui.Views.Main && ContextKey(gui.g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) { + newSelectedPath := gui.getSelectedPath() + alreadySelected := selectedPath != "" && newSelectedPath == selectedPath + if !alreadySelected { + gui.takeOverMergeConflictScrolling() + } + + gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) + return gui.filesRenderToMain() + } + + return nil + }) + + return nil +} + +// specific functions + +func (gui *Gui) stagedFiles() []*models.File { + files := gui.State.FileManager.GetAllFiles() + result := make([]*models.File, 0) + for _, file := range files { + if file.HasStagedChanges { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) trackedFiles() []*models.File { + files := gui.State.FileManager.GetAllFiles() + result := make([]*models.File, 0, len(files)) + for _, file := range files { + if file.Tracked { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) stageSelectedFile() error { + file := gui.getSelectedFile() + if file == nil { + return nil + } + + return gui.Git.WorkingTree.StageFile(file.Name) +} + +func (gui *Gui) handleEnterFile() error { + return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1}) +} + +func (gui *Gui) enterFile(opts OnFocusOpts) error { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + + if node.File == nil { + return gui.handleToggleDirCollapsed() + } + + file := node.File + + submoduleConfigs := gui.State.Submodules + if file.IsSubmodule(submoduleConfigs) { + submoduleConfig := file.SubmoduleConfig(submoduleConfigs) + return gui.enterSubmodule(submoduleConfig) + } + + if file.HasInlineMergeConflicts { + return gui.switchToMerge() + } + if file.HasMergeConflicts { + return gui.createErrorPanel(gui.Tr.FileStagingRequirements) + } + + return gui.pushContext(gui.State.Contexts.Staging, opts) +} + +func (gui *Gui) handleFilePress() error { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + + if node.IsLeaf() { + file := node.File + + if file.HasInlineMergeConflicts { + return gui.switchToMerge() + } + + if file.HasUnstagedChanges { + gui.logAction(gui.Tr.Actions.StageFile) + if err := gui.Git.WorkingTree.StageFile(file.Name); err != nil { + return gui.surfaceError(err) + } + } else { + gui.logAction(gui.Tr.Actions.UnstageFile) + if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil { + return gui.surfaceError(err) + } + } + } else { + // 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 gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts) + } + + if node.GetHasUnstagedChanges() { + gui.logAction(gui.Tr.Actions.StageFile) + if err := gui.Git.WorkingTree.StageFile(node.Path); err != nil { + return gui.surfaceError(err) + } + } else { + // pretty sure it doesn't matter that we're always passing true here + gui.logAction(gui.Tr.Actions.UnstageFile) + if err := gui.Git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil { + return gui.surfaceError(err) + } + } + } + + if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil { + return err + } + + return gui.State.Contexts.Files.HandleFocus() +} + +func (gui *Gui) allFilesStaged() bool { + for _, file := range gui.State.FileManager.GetAllFiles() { + if file.HasUnstagedChanges { + return false + } + } + return true +} + +func (gui *Gui) onFocusFile() error { + gui.takeOverMergeConflictScrolling() + return nil +} + +func (gui *Gui) handleStageAll() error { + var err error + if gui.allFilesStaged() { + gui.logAction(gui.Tr.Actions.UnstageAllFiles) + err = gui.Git.WorkingTree.UnstageAll() + } else { + gui.logAction(gui.Tr.Actions.StageAllFiles) + err = gui.Git.WorkingTree.StageAll() + } + if err != nil { + _ = gui.surfaceError(err) + } + + if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil { + return err + } + + return gui.State.Contexts.Files.HandleFocus() +} + +func (gui *Gui) handleIgnoreFile() error { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + + if node.GetPath() == ".gitignore" { + return gui.createErrorPanel("Cannot ignore .gitignore") + } + + unstageFiles := func() error { + return node.ForEachFile(func(file *models.File) error { + if file.HasStagedChanges { + if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil { + return err + } + } + + return nil + }) + } diff --git a/test/integration/staging/files/three_new.txt b/test/integration/staging/files/three_new.txt new file mode 100644 index 000000000..500ce9c2b --- /dev/null +++ b/test/integration/staging/files/three_new.txt @@ -0,0 +1,298 @@ +package gui + +import ( + "fmt" + "regexp" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/nodetree" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// list panel functions + +func (gui *Gui) getSelectednodeNode() *nodetree.nodeNode { + selectedLine := gui.State.Panels.nodes.SelectedLineIdx + if selectedLine == -1 { + return nil + } + + return gui.State.nodeManager.GetItemAtIndex(selectedLine) +} + +func (gui *Gui) getSelectednode() *models.node { + node := gui.getSelectednodeNode() + if node == nil { + return nil + } + return node.node +} + +func (gui *Gui) getSelectedPath() string { + node := gui.getSelectednodeNode() + if node == nil { + return "" + } + + return node.GetPath() +} + +func (gui *Gui) nodesRenderToMain() error { + node := gui.getSelectednodeNode() + + if node == nil { + return gui.refreshMainViews(refreshMainOpts{ + main: &viewUpdateOpts{ + title: "", + task: NewRenderStringTask(gui.Tr.NoChangednodes), + }, + }) + } + + if node.node != nil && node.File.HasInlineMergeConflicts { + return gui.renderConflictsFromFilesPanel() + } + + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) + + refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ + title: gui.Tr.UnstagedChanges, + task: NewRunPtyTask(cmdObj.GetCmd()), + }} + + if node.GetHasUnstagedChanges() { + if node.GetHasStagedChanges() { + cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView) + + refreshOpts.secondary = &viewUpdateOpts{ + title: gui.Tr.StagedChanges, + task: NewRunPtyTask(cmdObj.GetCmd()), + } + } + } else { + refreshOpts.main.title = gui.Tr.StagedChanges + } + + return gui.refreshMainViews(refreshOpts) +} + +func (gui *Gui) refreshFilesAndSubmodules() error { + gui.Mutexes.RefreshingFilesMutex.Lock() + gui.State.IsRefreshingFiles = true + defer func() { + gui.State.IsRefreshingFiles = false + gui.Mutexes.RefreshingFilesMutex.Unlock() + }() + + selectedPath := gui.getSelectedPath() + + if err := gui.refreshStateSubmoduleConfigs(); err != nil { + return err + } + if err := gui.refreshStateFiles(); err != nil { + return err + } + + gui.OnUIThread(func() error { + if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil { + gui.Log.Error(err) + } + + if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY { + // doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below + if err := gui.State.Contexts.Files.HandleRender(); err != nil { + return err + } + } + + if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (gui.g.CurrentView() == gui.Views.Main && ContextKey(gui.g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) { + newSelectedPath := gui.getSelectedPath() + alreadySelected := selectedPath != "" && newSelectedPath == selectedPath + if !alreadySelected { + gui.takeOverMergeConflictScrolling() + } + + gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) + return gui.filesRenderToMain() + } + + return nil + }) + + return nil +} + +// specific functions + +func (gui *Gui) stagedFiles() []*models.File { + files := gui.State.FileManager.GetAllFiles() + result := make([]*models.File, 0) + for _, file := range files { + if file.HasStagedChanges { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) trackedFiles() []*models.File { + files := gui.State.FileManager.GetAllFiles() + result := make([]*models.File, 0, len(files)) + for _, file := range files { + if file.Tracked { + result = append(result, file) + } + } + return result +} + +func (gui *Gui) stageSelectedFile() error { + file := gui.getSelectedFile() + if file == nil { + return nil + } + + return gui.Git.WorkingTree.StageFile(file.Name) +} + +func (gui *Gui) handleEnterFile() error { + return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1}) +} + +func (gui *Gui) enterFile(opts OnFocusOpts) error { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + + if node.File == nil { + return gui.handleToggleDirCollapsed() + } + + file := node.File + + submoduleConfigs := gui.State.Submodules + if file.IsSubmodule(submoduleConfigs) { + submoduleConfig := file.SubmoduleConfig(submoduleConfigs) + return gui.enterSubmodule(submoduleConfig) + } + + if file.HasInlineMergeConflicts { + return gui.switchToMerge() + } + if file.HasMergeConflicts { + return gui.createErrorPanel(gui.Tr.FileStagingRequirements) + } + + return gui.pushContext(gui.State.Contexts.Staging, opts) +} + +func (gui *Gui) handleFilePress() error { + node := gui.getSelectedFileNode() + if node == nil { + return nil + } + + if node.IsLeaf() { + file := node.File + + if file.HasInlineMergeConflicts { + return gui.switchToMerge() + } + + if node.HasUnstagedChanges { + gui.logAction(gui.Tr.Actions.Stagenode) + if err := gui.Git.WorkingTree.Stagenode(node.Name); err != nil { + return gui.surfaceError(err) + } + } else { + gui.logAction(gui.Tr.Actions.Unstagenode) + if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil { + return gui.surfaceError(err) + } + } + } else { + if node.GetHasInlineMergeConflicts() { + return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts) + } + + if node.GetHasUnstagedChanges() { + gui.logAction(gui.Tr.Actions.Stagenode) + if err := gui.Git.WorkingTree.Stagenode(node.Path); err != nil { + return gui.surfaceError(err) + } + } else { + // pretty sure it doesn't matter that we're always passing true here + gui.logAction(gui.Tr.Actions.Unstagenode) + if err := gui.Git.WorkingTree.UnStagenode([]string{node.Path}, true); err != nil { + return gui.surfaceError(err) + } + } + } + + if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil { + return err + } + + return gui.State.Contexts.nodes.HandleFocus() +} + +func (gui *Gui) allnodesStaged() bool { + for _, node := range gui.State.nodeManager.GetAllnodes() { + if node.HasUnstagedChanges { + return false + } + } + return true +} + +func (gui *Gui) onFocusnode() error { + gui.takeOverMergeConflictScrolling() + return nil +} + +func (gui *Gui) handleStageAll() error { + var err error + if gui.allnodesStaged() { + gui.logAction(gui.Tr.Actions.UnstageAllnodes) + err = gui.Git.WorkingTree.UnstageAll() + } else { + gui.logAction(gui.Tr.Actions.StageAllnodes) + err = gui.Git.WorkingTree.StageAll() + } + if err != nil { + _ = gui.surfaceError(err) + } + + if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil { + return err + } + + return gui.State.Contexts.nodes.HandleFocus() +} + +func (gui *Gui) handleIgnorenode() error { + node := gui.getSelectednodeNode() + if node == nil { + return nil + } + + if node.GetPath() == ".gitignore" { + return gui.createErrorPanel("Cannot ignore .gitignore") + } + + unstagenodes := func() error { + return node.ForEachnode(func(node *models.node) error { + if node.HasStagedChanges { + if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil { + return err + } + } + + return nil + }) + } diff --git a/test/integration/staging/files/two.txt b/test/integration/staging/files/two.txt new file mode 100644 index 000000000..a48a7caa7 --- /dev/null +++ b/test/integration/staging/files/two.txt @@ -0,0 +1,33 @@ +type createMenuOptions struct { + showCancel bool +} + +func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error { + if createMenuOptions.showCancel { + // this is mutative but I'm okay with that for now + items = append(items, &menuItem{ + displayStrings: []string{gui.Tr.LcCancel}, + onPress: func() error { + return nil + }, + }) + } + + gui.State.MenuItems = items + + stringArrays := make([][]string, len(items)) + for i, item := range items { + if item.opensMenu && item.displayStrings != nil { + return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user") + } + + if item.displayStrings == nil { + styledStr := item.displayString + if item.opensMenu { + styledStr = opensMenuStyle(styledStr) + } + stringArrays[i] = []string{styledStr} + } else { + stringArrays[i] = item.displayStrings + } + } diff --git a/test/integration/staging/files/two_new.txt b/test/integration/staging/files/two_new.txt new file mode 100644 index 000000000..bbe2a2f2c --- /dev/null +++ b/test/integration/staging/files/two_new.txt @@ -0,0 +1,33 @@ +type createMenuOptions struct { + showCancel bool +} + +func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error { + if createMenuOptions.showCancel { + // this is mutative but I'm okay with that for now + items = app(items, &menuItem{ + d: []string{gui.Tr.LcCancel}, + onPress: func() error { + return nil + }, + }) + } + + gui.State.MenuItems = items + + stringArrays := make([][]string, len(items)) + for i, items := range items { + if items.opensMenu && item.displayStrings != nil { + return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user") + } + + if item.displayStrings == nil { + styledStr := item.displayString + if item.opensMenu { + styledStr = opensMenuStyle(styledStr) + } + stringArrays[0] = []str0ng{styledStr} + } else { + str0ngArrays[0] = item.displayStrings + } + } diff --git a/test/integration/staging/recording.json b/test/integration/staging/recording.json new file mode 100644 index 000000000..912fe35b0 --- /dev/null +++ b/test/integration/staging/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":671,"Mod":0,"Key":13,"Ch":13},{"Timestamp":1095,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1447,"Mod":0,"Key":256,"Ch":32},{"Timestamp":2608,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2743,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2973,"Mod":0,"Key":256,"Ch":118},{"Timestamp":3078,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3215,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3415,"Mod":0,"Key":256,"Ch":32},{"Timestamp":3920,"Mod":0,"Key":9,"Ch":9},{"Timestamp":4287,"Mod":0,"Key":257,"Ch":0},{"Timestamp":4431,"Mod":0,"Key":257,"Ch":0},{"Timestamp":4559,"Mod":0,"Key":257,"Ch":0},{"Timestamp":4848,"Mod":0,"Key":256,"Ch":32},{"Timestamp":5774,"Mod":0,"Key":9,"Ch":9},{"Timestamp":6031,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6294,"Mod":0,"Key":257,"Ch":0},{"Timestamp":6374,"Mod":0,"Key":256,"Ch":118},{"Timestamp":6463,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6591,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6711,"Mod":0,"Key":256,"Ch":32},{"Timestamp":7274,"Mod":0,"Key":27,"Ch":0},{"Timestamp":7591,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7968,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8735,"Mod":0,"Key":256,"Ch":97},{"Timestamp":9039,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9327,"Mod":0,"Key":258,"Ch":0},{"Timestamp":9478,"Mod":0,"Key":258,"Ch":0},{"Timestamp":9815,"Mod":0,"Key":256,"Ch":32},{"Timestamp":10439,"Mod":0,"Key":9,"Ch":9},{"Timestamp":11383,"Mod":0,"Key":256,"Ch":97},{"Timestamp":12095,"Mod":0,"Key":256,"Ch":97},{"Timestamp":12319,"Mod":0,"Key":257,"Ch":0},{"Timestamp":13039,"Mod":0,"Key":256,"Ch":32},{"Timestamp":14109,"Mod":0,"Key":27,"Ch":0},{"Timestamp":15119,"Mod":0,"Key":13,"Ch":13},{"Timestamp":15543,"Mod":0,"Key":256,"Ch":100},{"Timestamp":15855,"Mod":0,"Key":13,"Ch":13},{"Timestamp":16183,"Mod":0,"Key":256,"Ch":100},{"Timestamp":16415,"Mod":0,"Key":13,"Ch":13},{"Timestamp":16832,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17150,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17519,"Mod":0,"Key":256,"Ch":118},{"Timestamp":17654,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17784,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17903,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18015,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18150,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18272,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18567,"Mod":0,"Key":256,"Ch":100},{"Timestamp":18759,"Mod":0,"Key":13,"Ch":13},{"Timestamp":19254,"Mod":0,"Key":258,"Ch":0},{"Timestamp":19736,"Mod":0,"Key":259,"Ch":0},{"Timestamp":20358,"Mod":0,"Key":256,"Ch":100},{"Timestamp":20552,"Mod":0,"Key":13,"Ch":13},{"Timestamp":20871,"Mod":0,"Key":256,"Ch":100},{"Timestamp":20991,"Mod":0,"Key":13,"Ch":13},{"Timestamp":21433,"Mod":0,"Key":27,"Ch":0},{"Timestamp":21647,"Mod":0,"Key":258,"Ch":0},{"Timestamp":21943,"Mod":0,"Key":13,"Ch":13},{"Timestamp":22663,"Mod":0,"Key":256,"Ch":97},{"Timestamp":23207,"Mod":0,"Key":258,"Ch":0},{"Timestamp":23383,"Mod":0,"Key":258,"Ch":0},{"Timestamp":24039,"Mod":0,"Key":256,"Ch":100},{"Timestamp":24391,"Mod":0,"Key":13,"Ch":13},{"Timestamp":25141,"Mod":0,"Key":27,"Ch":0},{"Timestamp":25695,"Mod":0,"Key":256,"Ch":99},{"Timestamp":25959,"Mod":0,"Key":256,"Ch":116},{"Timestamp":26007,"Mod":0,"Key":256,"Ch":101},{"Timestamp":26191,"Mod":0,"Key":256,"Ch":115},{"Timestamp":26214,"Mod":0,"Key":256,"Ch":116},{"Timestamp":26464,"Mod":0,"Key":13,"Ch":13},{"Timestamp":27367,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/staging/setup.sh b/test/integration/staging/setup.sh new file mode 100644 index 000000000..5ede99e27 --- /dev/null +++ b/test/integration/staging/setup.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +cp ../files/one.txt one.txt +cp ../files/two.txt two.txt +cp ../files/three.txt three.txt +git add . +git commit -am file1 + +cp ../files/one_new.txt one.txt +cp ../files/two_new.txt two.txt +cp ../files/three_new.txt three.txt diff --git a/test/integration/staging/test.json b/test/integration/staging/test.json new file mode 100644 index 000000000..c0f750c29 --- /dev/null +++ b/test/integration/staging/test.json @@ -0,0 +1,4 @@ +{ + "description": "Staging a file line-by-line", + "speed": 30 +} diff --git a/test/integration/stagingTwo/expected/.git_keep/COMMIT_EDITMSG b/test/integration/stagingTwo/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..e2129701f --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +file1 diff --git a/test/integration/stagingTwo/expected/.git_keep/FETCH_HEAD b/test/integration/stagingTwo/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/stagingTwo/expected/.git_keep/HEAD b/test/integration/stagingTwo/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/stagingTwo/expected/.git_keep/config b/test/integration/stagingTwo/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/stagingTwo/expected/.git_keep/description b/test/integration/stagingTwo/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/stagingTwo/expected/.git_keep/index b/test/integration/stagingTwo/expected/.git_keep/index new file mode 100644 index 000000000..04a93cbaf Binary files /dev/null and b/test/integration/stagingTwo/expected/.git_keep/index differ diff --git a/test/integration/stagingTwo/expected/.git_keep/info/exclude b/test/integration/stagingTwo/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/stagingTwo/expected/.git_keep/logs/HEAD b/test/integration/stagingTwo/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..3e79e936b --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 f793cf3fd99464dbd3499093e95197229b771b11 CI 1642495374 +1100 commit (initial): file1 diff --git a/test/integration/stagingTwo/expected/.git_keep/logs/refs/heads/master b/test/integration/stagingTwo/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..3e79e936b --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 f793cf3fd99464dbd3499093e95197229b771b11 CI 1642495374 +1100 commit (initial): file1 diff --git a/test/integration/stagingTwo/expected/.git_keep/objects/15/8e9a9c1a9627ea0ef4de8b00a503ed0f80a09e b/test/integration/stagingTwo/expected/.git_keep/objects/15/8e9a9c1a9627ea0ef4de8b00a503ed0f80a09e new file mode 100644 index 000000000..2c00719b0 Binary files /dev/null and b/test/integration/stagingTwo/expected/.git_keep/objects/15/8e9a9c1a9627ea0ef4de8b00a503ed0f80a09e differ diff --git a/test/integration/stagingTwo/expected/.git_keep/objects/7b/9c2149f16c706cea79b03234cb67fde7e9b68f b/test/integration/stagingTwo/expected/.git_keep/objects/7b/9c2149f16c706cea79b03234cb67fde7e9b68f new file mode 100644 index 000000000..611ed37a4 Binary files /dev/null and b/test/integration/stagingTwo/expected/.git_keep/objects/7b/9c2149f16c706cea79b03234cb67fde7e9b68f differ diff --git a/test/integration/stagingTwo/expected/.git_keep/objects/f7/93cf3fd99464dbd3499093e95197229b771b11 b/test/integration/stagingTwo/expected/.git_keep/objects/f7/93cf3fd99464dbd3499093e95197229b771b11 new file mode 100644 index 000000000..85ed192ae Binary files /dev/null and b/test/integration/stagingTwo/expected/.git_keep/objects/f7/93cf3fd99464dbd3499093e95197229b771b11 differ diff --git a/test/integration/stagingTwo/expected/.git_keep/refs/heads/master b/test/integration/stagingTwo/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..32457d556 --- /dev/null +++ b/test/integration/stagingTwo/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +f793cf3fd99464dbd3499093e95197229b771b11 diff --git a/test/integration/stagingTwo/expected/one.txt b/test/integration/stagingTwo/expected/one.txt new file mode 100644 index 000000000..9b92aa365 --- /dev/null +++ b/test/integration/stagingTwo/expected/one.txt @@ -0,0 +1,64 @@ +package oscommands + +import ( + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// NewDummyOSCommand creates a new dummy OSCommand for testing +func NewDummyOSCommand() *OSCommand { + osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + + return osCmd +} + +type OSCommandDeps struct { + Common *common.Common + Platform *Platform + GetenvFn func(string) string + RemoveFileFn func(string) error + Cmd *CmdObjBuilder +} + +func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { + common := deps.Common + if common == nil { + common = utils.NewDummyCommon() + } + + platform := deps.Platform + if platform == nil { + platform = dummyPlatform + } + + return &OSCommand{ + Common: common, + Platform: platform, + getenvFn: deps.GetenvFn, + removeFileFn: deps.RemoveFileFn, + guiIO: NewNullGuiIO(utils.NewDummyLog()), + Cmd: deps.Cmd, + } +} + +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(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + osCommand.Cmd = NewDummyCmdObjBuilder(runner) + + return osCommand +} diff --git a/test/integration/stagingTwo/files/one.txt b/test/integration/stagingTwo/files/one.txt new file mode 100644 index 000000000..158e9a9c1 --- /dev/null +++ b/test/integration/stagingTwo/files/one.txt @@ -0,0 +1,63 @@ +package oscommands + +import ( + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// NewDummyOSCommand creates a new dummy OSCommand for testing +func NewDummyOSCommand() *OSCommand { + osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + + return osCmd +} + +type OSCommandDeps struct { + Common *common.Common + Platform *Platform + GetenvFn func(string) string + RemoveFileFn func(string) error + Cmd *CmdObjBuilder +} + +func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { + common := deps.Common + if common == nil { + common = utils.NewDummyCommon() + } + + platform := deps.Platform + if platform == nil { + platform = dummyPlatform + } + + return &OSCommand{ + Common: common, + Platform: platform, + getenvFn: deps.GetenvFn, + removeFileFn: deps.RemoveFileFn, + guiIO: NewNullGuiIO(utils.NewDummyLog()), + } +} + +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(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + osCommand.Cmd = NewDummyCmdObjBuilder(runner) + + return osCommand +} diff --git a/test/integration/stagingTwo/files/one_new.txt b/test/integration/stagingTwo/files/one_new.txt new file mode 100644 index 000000000..c3323cfc9 --- /dev/null +++ b/test/integration/stagingTwo/files/one_new.txt @@ -0,0 +1,67 @@ +package oscommands + +import ( + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// NewDummyOSCommand creates a new dummy OSCommand for testing +func NewDummyOSCommand() *OSCommand { + osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + + return osCmd +} + +type OSCommandDeps struct { + Common *common.Common + Platform *Platform + GetenvFn func(string) string + RemoveFileFn func(string) error + Cmd *CmdObjBuilder +} + +func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand { + if deps.Cmd == nil { + panic("WHAT") + } + common := deps.Common + if common == nil { + common = utils.NewDummyCommon() + } + + platform := deps.Platform + if platform == nil { + platform = dummyPlatform + } + + return &OSCommand{ + Common: common, + Platform: platform, + getenvFn: deps.GetenvFn, + removeFileFn: deps.RemoveFileFn, + guiIO: NewNullGuiIO(utils.NewDummyLog()), + Cmd: deps.Cmd, + } +} + +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(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog())) + osCommand.Cmd = NewDummyCmdObjBuilder(runner) + + return osCommand +} diff --git a/test/integration/stagingTwo/recording.json b/test/integration/stagingTwo/recording.json new file mode 100644 index 000000000..c089a5783 --- /dev/null +++ b/test/integration/stagingTwo/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":843,"Mod":0,"Key":13,"Ch":13},{"Timestamp":1122,"Mod":0,"Key":256,"Ch":118},{"Timestamp":1266,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1386,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1602,"Mod":0,"Key":256,"Ch":100},{"Timestamp":1851,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2520,"Mod":0,"Key":27,"Ch":0},{"Timestamp":3195,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/stagingTwo/setup.sh b/test/integration/stagingTwo/setup.sh new file mode 100644 index 000000000..ac450f8d2 --- /dev/null +++ b/test/integration/stagingTwo/setup.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +cp ../files/one.txt one.txt +git add . +git commit -am file1 + +cp ../files/one_new.txt one.txt diff --git a/test/integration/stagingTwo/test.json b/test/integration/stagingTwo/test.json new file mode 100644 index 000000000..023d766c4 --- /dev/null +++ b/test/integration/stagingTwo/test.json @@ -0,0 +1,4 @@ +{ + "description": "Some more line-by-line staging", + "speed": 20 +} diff --git a/test/integration/stash/expected/.git_keep/ORIG_HEAD b/test/integration/stash/expected/.git_keep/ORIG_HEAD index 43680a2d9..78e4eb58e 100644 --- a/test/integration/stash/expected/.git_keep/ORIG_HEAD +++ b/test/integration/stash/expected/.git_keep/ORIG_HEAD @@ -1 +1 @@ -05f4edebb0aadc631926ea90f2f4f74033127a19 +f348ff60bdbb3695f2f519db6bc115b1b8d50886 diff --git a/test/integration/stash/expected/.git_keep/index b/test/integration/stash/expected/.git_keep/index index 705b3a73b..daebfa4ef 100644 Binary files a/test/integration/stash/expected/.git_keep/index and b/test/integration/stash/expected/.git_keep/index differ diff --git a/test/integration/stash/expected/.git_keep/logs/HEAD b/test/integration/stash/expected/.git_keep/logs/HEAD index 474266cc1..f95cf06ac 100644 --- a/test/integration/stash/expected/.git_keep/logs/HEAD +++ b/test/integration/stash/expected/.git_keep/logs/HEAD @@ -1,6 +1,6 @@ -0000000000000000000000000000000000000000 df8cbdc0e9dca87b81e783ac7d814ac6d2a9c7ff CI 1617674510 +1000 commit (initial): file0 -df8cbdc0e9dca87b81e783ac7d814ac6d2a9c7ff 211d7ffb63edc061a144eb1e198d94768000c723 CI 1617674510 +1000 commit: file1 -211d7ffb63edc061a144eb1e198d94768000c723 05f4edebb0aadc631926ea90f2f4f74033127a19 CI 1617674510 +1000 commit: file2 -05f4edebb0aadc631926ea90f2f4f74033127a19 05f4edebb0aadc631926ea90f2f4f74033127a19 CI 1617674513 +1000 reset: moving to HEAD -05f4edebb0aadc631926ea90f2f4f74033127a19 05f4edebb0aadc631926ea90f2f4f74033127a19 CI 1617674513 +1000 reset: moving to HEAD -05f4edebb0aadc631926ea90f2f4f74033127a19 05f4edebb0aadc631926ea90f2f4f74033127a19 CI 1617674517 +1000 reset: moving to HEAD +0000000000000000000000000000000000000000 a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b CI 1643011553 +1100 commit (initial): file0 +a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b 4cc838ea1466afc5be1d3bc3e7a937641ec84d7d CI 1643011553 +1100 commit: file1 +4cc838ea1466afc5be1d3bc3e7a937641ec84d7d f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011553 +1100 commit: file2 +f348ff60bdbb3695f2f519db6bc115b1b8d50886 f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011556 +1100 reset: moving to HEAD +f348ff60bdbb3695f2f519db6bc115b1b8d50886 f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011556 +1100 reset: moving to HEAD +f348ff60bdbb3695f2f519db6bc115b1b8d50886 f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011558 +1100 reset: moving to HEAD diff --git a/test/integration/stash/expected/.git_keep/logs/refs/heads/master b/test/integration/stash/expected/.git_keep/logs/refs/heads/master index 0dd71a278..d1bf421f8 100644 --- a/test/integration/stash/expected/.git_keep/logs/refs/heads/master +++ b/test/integration/stash/expected/.git_keep/logs/refs/heads/master @@ -1,3 +1,3 @@ -0000000000000000000000000000000000000000 df8cbdc0e9dca87b81e783ac7d814ac6d2a9c7ff CI 1617674510 +1000 commit (initial): file0 -df8cbdc0e9dca87b81e783ac7d814ac6d2a9c7ff 211d7ffb63edc061a144eb1e198d94768000c723 CI 1617674510 +1000 commit: file1 -211d7ffb63edc061a144eb1e198d94768000c723 05f4edebb0aadc631926ea90f2f4f74033127a19 CI 1617674510 +1000 commit: file2 +0000000000000000000000000000000000000000 a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b CI 1643011553 +1100 commit (initial): file0 +a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b 4cc838ea1466afc5be1d3bc3e7a937641ec84d7d CI 1643011553 +1100 commit: file1 +4cc838ea1466afc5be1d3bc3e7a937641ec84d7d f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011553 +1100 commit: file2 diff --git a/test/integration/stash/expected/.git_keep/logs/refs/stash b/test/integration/stash/expected/.git_keep/logs/refs/stash index 2815df3de..3f07cf22e 100644 --- a/test/integration/stash/expected/.git_keep/logs/refs/stash +++ b/test/integration/stash/expected/.git_keep/logs/refs/stash @@ -1,2 +1,2 @@ -0000000000000000000000000000000000000000 1abf1a2a5d2e97b92e49391b4c92f945b5283606 CI 1617674513 +1000 On master: asd -1abf1a2a5d2e97b92e49391b4c92f945b5283606 400778244da4711bc99a7b1a84bab99213601a4e CI 1617674517 +1000 On master: asd +0000000000000000000000000000000000000000 e09b4dfcd66bfa1c81feeaf67e04d55368a2b065 CI 1643011556 +1100 On master: asd +e09b4dfcd66bfa1c81feeaf67e04d55368a2b065 2efac8148440778cbddcd80ac7477981277dcffe CI 1643011558 +1100 On master: asd diff --git a/test/integration/stash/expected/.git_keep/objects/05/f4edebb0aadc631926ea90f2f4f74033127a19 b/test/integration/stash/expected/.git_keep/objects/05/f4edebb0aadc631926ea90f2f4f74033127a19 deleted file mode 100644 index 14ea7d52a..000000000 Binary files a/test/integration/stash/expected/.git_keep/objects/05/f4edebb0aadc631926ea90f2f4f74033127a19 and /dev/null differ diff --git a/test/integration/stash/expected/.git_keep/objects/1a/bf1a2a5d2e97b92e49391b4c92f945b5283606 b/test/integration/stash/expected/.git_keep/objects/1a/bf1a2a5d2e97b92e49391b4c92f945b5283606 deleted file mode 100644 index 100e8dc01..000000000 --- a/test/integration/stash/expected/.git_keep/objects/1a/bf1a2a5d2e97b92e49391b4c92f945b5283606 +++ /dev/null @@ -1 +0,0 @@ -xϱj1 Eu]J dge\-ݻ|˯3D*Os7 9v^2yc*Zq).fɺ"]sIN)adYzi55*XW36.|Ϗmkx\#eϺ93 `911|y!ÿy_&KF \ No newline at end of file diff --git a/test/integration/stash/expected/.git_keep/objects/21/1d7ffb63edc061a144eb1e198d94768000c723 b/test/integration/stash/expected/.git_keep/objects/21/1d7ffb63edc061a144eb1e198d94768000c723 deleted file mode 100644 index f30621f1b..000000000 Binary files a/test/integration/stash/expected/.git_keep/objects/21/1d7ffb63edc061a144eb1e198d94768000c723 and /dev/null differ diff --git a/test/integration/stash/expected/.git_keep/objects/25/abc3e66a6aa505fb0a2ceb6ad5cda0cc89ecbc b/test/integration/stash/expected/.git_keep/objects/25/abc3e66a6aa505fb0a2ceb6ad5cda0cc89ecbc new file mode 100644 index 000000000..aab767a08 Binary files /dev/null and b/test/integration/stash/expected/.git_keep/objects/25/abc3e66a6aa505fb0a2ceb6ad5cda0cc89ecbc differ diff --git a/test/integration/stash/expected/.git_keep/objects/2e/fac8148440778cbddcd80ac7477981277dcffe b/test/integration/stash/expected/.git_keep/objects/2e/fac8148440778cbddcd80ac7477981277dcffe new file mode 100644 index 000000000..54545b630 --- /dev/null +++ b/test/integration/stash/expected/.git_keep/objects/2e/fac8148440778cbddcd80ac7477981277dcffe @@ -0,0 +1 @@ +xAj1 4v)%UV=$˴Pgątg{bOpF[`X,`L ˶eQMl,y.5\FTeHJ3H:!5ER [.nj|^/׳H g2B@\:O '_o1 ȣK \ No newline at end of file diff --git a/test/integration/stash/expected/.git_keep/objects/3b/29b35d4357f8e64cafd95140a70d7c9b25138a b/test/integration/stash/expected/.git_keep/objects/3b/29b35d4357f8e64cafd95140a70d7c9b25138a new file mode 100644 index 000000000..1b8805172 Binary files /dev/null and b/test/integration/stash/expected/.git_keep/objects/3b/29b35d4357f8e64cafd95140a70d7c9b25138a differ diff --git a/test/integration/stash/expected/.git_keep/objects/40/0778244da4711bc99a7b1a84bab99213601a4e b/test/integration/stash/expected/.git_keep/objects/40/0778244da4711bc99a7b1a84bab99213601a4e deleted file mode 100644 index 791a8042e..000000000 --- a/test/integration/stash/expected/.git_keep/objects/40/0778244da4711bc99a7b1a84bab99213601a4e +++ /dev/null @@ -1,2 +0,0 @@ -xAj1 d֨*A%Z3aBt߶1>g>,GΌ - -yZ4orn3@vnjo%b*ə %(>W!.^k)IDMJw'|/=?=\r=ۏ=m,ȅ)#rGĴ>J` \ No newline at end of file diff --git a/test/integration/stash/expected/.git_keep/objects/4c/c838ea1466afc5be1d3bc3e7a937641ec84d7d b/test/integration/stash/expected/.git_keep/objects/4c/c838ea1466afc5be1d3bc3e7a937641ec84d7d new file mode 100644 index 000000000..bc099c320 Binary files /dev/null and b/test/integration/stash/expected/.git_keep/objects/4c/c838ea1466afc5be1d3bc3e7a937641ec84d7d differ diff --git a/test/integration/stash/expected/.git_keep/objects/5b/9476fbb5d3283ab920b724fe91b9dff40a5c29 b/test/integration/stash/expected/.git_keep/objects/5b/9476fbb5d3283ab920b724fe91b9dff40a5c29 deleted file mode 100644 index 391a716d7..000000000 Binary files a/test/integration/stash/expected/.git_keep/objects/5b/9476fbb5d3283ab920b724fe91b9dff40a5c29 and /dev/null differ diff --git a/test/integration/stash/expected/.git_keep/objects/7c/00a9fdad735ecd011f92c03295ee7e784ca1cc b/test/integration/stash/expected/.git_keep/objects/7c/00a9fdad735ecd011f92c03295ee7e784ca1cc deleted file mode 100644 index 10d9df475..000000000 Binary files a/test/integration/stash/expected/.git_keep/objects/7c/00a9fdad735ecd011f92c03295ee7e784ca1cc and /dev/null differ diff --git a/test/integration/stash/expected/.git_keep/objects/85/d387b0ab853a34de01344e8ca614fe8ea58318 b/test/integration/stash/expected/.git_keep/objects/85/d387b0ab853a34de01344e8ca614fe8ea58318 deleted file mode 100644 index 7a7fe2249..000000000 --- a/test/integration/stash/expected/.git_keep/objects/85/d387b0ab853a34de01344e8ca614fe8ea58318 +++ /dev/null @@ -1 +0,0 @@ -xjA ESWi^KZ5 s9]{% Wq"۱z Bř.%6r!`d͖EH5<9' 'ϊ(՛4ٴ!ѮPf36>&Rg|gx;+qN""NG '>]OX{ כ?XP \ No newline at end of file diff --git a/test/integration/stash/expected/.git_keep/objects/a6/ada9f3d895e751ec289c69913a02146c0ca844 b/test/integration/stash/expected/.git_keep/objects/a6/ada9f3d895e751ec289c69913a02146c0ca844 new file mode 100644 index 000000000..0cdd88ea0 Binary files /dev/null and b/test/integration/stash/expected/.git_keep/objects/a6/ada9f3d895e751ec289c69913a02146c0ca844 differ diff --git a/test/integration/stash/expected/.git_keep/objects/a7/dde526f2e93ffa08897fbfca2c98ce40a8fa5b b/test/integration/stash/expected/.git_keep/objects/a7/dde526f2e93ffa08897fbfca2c98ce40a8fa5b new file mode 100644 index 000000000..9dcd075d6 Binary files /dev/null and b/test/integration/stash/expected/.git_keep/objects/a7/dde526f2e93ffa08897fbfca2c98ce40a8fa5b differ diff --git a/test/integration/stash/expected/.git_keep/objects/df/8cbdc0e9dca87b81e783ac7d814ac6d2a9c7ff b/test/integration/stash/expected/.git_keep/objects/df/8cbdc0e9dca87b81e783ac7d814ac6d2a9c7ff deleted file mode 100644 index 341ebdd36..000000000 Binary files a/test/integration/stash/expected/.git_keep/objects/df/8cbdc0e9dca87b81e783ac7d814ac6d2a9c7ff and /dev/null differ diff --git a/test/integration/stash/expected/.git_keep/objects/e0/9b4dfcd66bfa1c81feeaf67e04d55368a2b065 b/test/integration/stash/expected/.git_keep/objects/e0/9b4dfcd66bfa1c81feeaf67e04d55368a2b065 new file mode 100644 index 000000000..f57ce417b Binary files /dev/null and b/test/integration/stash/expected/.git_keep/objects/e0/9b4dfcd66bfa1c81feeaf67e04d55368a2b065 differ diff --git a/test/integration/stash/expected/.git_keep/objects/f3/48ff60bdbb3695f2f519db6bc115b1b8d50886 b/test/integration/stash/expected/.git_keep/objects/f3/48ff60bdbb3695f2f519db6bc115b1b8d50886 new file mode 100644 index 000000000..8faee1fcd Binary files /dev/null and b/test/integration/stash/expected/.git_keep/objects/f3/48ff60bdbb3695f2f519db6bc115b1b8d50886 differ diff --git a/test/integration/stash/expected/.git_keep/refs/heads/master b/test/integration/stash/expected/.git_keep/refs/heads/master index 43680a2d9..78e4eb58e 100644 --- a/test/integration/stash/expected/.git_keep/refs/heads/master +++ b/test/integration/stash/expected/.git_keep/refs/heads/master @@ -1 +1 @@ -05f4edebb0aadc631926ea90f2f4f74033127a19 +f348ff60bdbb3695f2f519db6bc115b1b8d50886 diff --git a/test/integration/stash/expected/.git_keep/refs/stash b/test/integration/stash/expected/.git_keep/refs/stash index 7fd3144c3..9123248e5 100644 --- a/test/integration/stash/expected/.git_keep/refs/stash +++ b/test/integration/stash/expected/.git_keep/refs/stash @@ -1 +1 @@ -400778244da4711bc99a7b1a84bab99213601a4e +2efac8148440778cbddcd80ac7477981277dcffe diff --git a/test/integration/stash/recording.json b/test/integration/stash/recording.json index e73c9e3cb..48fc35158 100644 --- a/test/integration/stash/recording.json +++ b/test/integration/stash/recording.json @@ -1 +1 @@ -{"KeyEvents":[{"Timestamp":1218,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1555,"Mod":0,"Key":256,"Ch":83},{"Timestamp":1868,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2116,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2411,"Mod":0,"Key":256,"Ch":97},{"Timestamp":2459,"Mod":0,"Key":256,"Ch":115},{"Timestamp":2523,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2955,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4059,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4355,"Mod":0,"Key":256,"Ch":32},{"Timestamp":5611,"Mod":0,"Key":256,"Ch":115},{"Timestamp":6427,"Mod":0,"Key":256,"Ch":97},{"Timestamp":6483,"Mod":0,"Key":256,"Ch":115},{"Timestamp":6522,"Mod":0,"Key":256,"Ch":100},{"Timestamp":6907,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7563,"Mod":0,"Key":259,"Ch":0},{"Timestamp":7819,"Mod":0,"Key":259,"Ch":0},{"Timestamp":8139,"Mod":0,"Key":259,"Ch":0},{"Timestamp":8491,"Mod":0,"Key":258,"Ch":0},{"Timestamp":9067,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9843,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file +{"KeyEvents":[{"Timestamp":809,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1369,"Mod":0,"Key":256,"Ch":83},{"Timestamp":1713,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2087,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2376,"Mod":0,"Key":256,"Ch":97},{"Timestamp":2440,"Mod":0,"Key":256,"Ch":115},{"Timestamp":2512,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2793,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3498,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4113,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4785,"Mod":0,"Key":256,"Ch":115},{"Timestamp":5145,"Mod":0,"Key":256,"Ch":97},{"Timestamp":5183,"Mod":0,"Key":256,"Ch":115},{"Timestamp":5249,"Mod":0,"Key":256,"Ch":100},{"Timestamp":5609,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6216,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6457,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6728,"Mod":0,"Key":259,"Ch":0},{"Timestamp":7098,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7408,"Mod":0,"Key":256,"Ch":32},{"Timestamp":8080,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8752,"Mod":0,"Key":260,"Ch":0},{"Timestamp":8952,"Mod":0,"Key":260,"Ch":0},{"Timestamp":9145,"Mod":0,"Key":260,"Ch":0},{"Timestamp":9904,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/stash/setup.sh b/test/integration/stash/setup.sh index 7b6eea95c..caff56b7d 100644 --- a/test/integration/stash/setup.sh +++ b/test/integration/stash/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/stashDrop/expected/.git_keep/COMMIT_EDITMSG b/test/integration/stashDrop/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..6c493ff74 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +file2 diff --git a/test/integration/stashDrop/expected/.git_keep/FETCH_HEAD b/test/integration/stashDrop/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/stashDrop/expected/.git_keep/HEAD b/test/integration/stashDrop/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/stashDrop/expected/.git_keep/ORIG_HEAD b/test/integration/stashDrop/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..0a7c9cc2b --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +d22496528fe3c076a668c496ae7ba1f8136f1614 diff --git a/test/integration/stashDrop/expected/.git_keep/config b/test/integration/stashDrop/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/stashDrop/expected/.git_keep/description b/test/integration/stashDrop/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/stashDrop/expected/.git_keep/index b/test/integration/stashDrop/expected/.git_keep/index new file mode 100644 index 000000000..44fe0833f Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/index differ diff --git a/test/integration/stashDrop/expected/.git_keep/info/exclude b/test/integration/stashDrop/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/stashDrop/expected/.git_keep/logs/HEAD b/test/integration/stashDrop/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..b2e6d1769 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/logs/HEAD @@ -0,0 +1,7 @@ +0000000000000000000000000000000000000000 c00c9eb1ae239494475772c3f3dbae5ea4169575 CI 1643011799 +1100 commit (initial): file0 +c00c9eb1ae239494475772c3f3dbae5ea4169575 7605fecac5dee01fb9df55ca984dcc7a72810f48 CI 1643011799 +1100 commit: file1 +7605fecac5dee01fb9df55ca984dcc7a72810f48 d22496528fe3c076a668c496ae7ba1f8136f1614 CI 1643011799 +1100 commit: file2 +d22496528fe3c076a668c496ae7ba1f8136f1614 d22496528fe3c076a668c496ae7ba1f8136f1614 CI 1643011803 +1100 reset: moving to HEAD +d22496528fe3c076a668c496ae7ba1f8136f1614 d22496528fe3c076a668c496ae7ba1f8136f1614 CI 1643011803 +1100 reset: moving to HEAD +d22496528fe3c076a668c496ae7ba1f8136f1614 d22496528fe3c076a668c496ae7ba1f8136f1614 CI 1643011806 +1100 reset: moving to HEAD +d22496528fe3c076a668c496ae7ba1f8136f1614 d22496528fe3c076a668c496ae7ba1f8136f1614 CI 1643011806 +1100 reset: moving to HEAD diff --git a/test/integration/stashDrop/expected/.git_keep/logs/refs/heads/master b/test/integration/stashDrop/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..62c5e5c79 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 c00c9eb1ae239494475772c3f3dbae5ea4169575 CI 1643011799 +1100 commit (initial): file0 +c00c9eb1ae239494475772c3f3dbae5ea4169575 7605fecac5dee01fb9df55ca984dcc7a72810f48 CI 1643011799 +1100 commit: file1 +7605fecac5dee01fb9df55ca984dcc7a72810f48 d22496528fe3c076a668c496ae7ba1f8136f1614 CI 1643011799 +1100 commit: file2 diff --git a/test/integration/stashDrop/expected/.git_keep/logs/refs/stash b/test/integration/stashDrop/expected/.git_keep/logs/refs/stash new file mode 100644 index 000000000..0b47b5c2e --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/logs/refs/stash @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 f4f81b6542e98a2f80269449674b0f8f454b74b0 CI 1643011806 +1100 On master: dsa diff --git a/test/integration/stashDrop/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/stashDrop/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/stashDrop/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 new file mode 100644 index 000000000..79fcadf67 Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/1e/6f4a55f3dd26848238337763f249681ef9397b b/test/integration/stashDrop/expected/.git_keep/objects/1e/6f4a55f3dd26848238337763f249681ef9397b new file mode 100644 index 000000000..24145cefe Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/1e/6f4a55f3dd26848238337763f249681ef9397b differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/stashDrop/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da new file mode 100644 index 000000000..06c9cb73d Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/56/52247b638d1516506790d6648b864ba3447f68 b/test/integration/stashDrop/expected/.git_keep/objects/56/52247b638d1516506790d6648b864ba3447f68 new file mode 100644 index 000000000..8535af67c Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/56/52247b638d1516506790d6648b864ba3447f68 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 b/test/integration/stashDrop/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 new file mode 100644 index 000000000..6a6f24362 Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 b/test/integration/stashDrop/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 new file mode 100644 index 000000000..c84b87a17 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 @@ -0,0 +1,3 @@ +x+)JMUd040031QHI5`ֶww.hT[H + e"ǨS,gu"YH +$x~5(;rբW-Ж+^ \ No newline at end of file diff --git a/test/integration/stashDrop/expected/.git_keep/objects/6d/c07da80aed51d01a56a89ef37f4411adbd75c5 b/test/integration/stashDrop/expected/.git_keep/objects/6d/c07da80aed51d01a56a89ef37f4411adbd75c5 new file mode 100644 index 000000000..6d84f0e3f Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/6d/c07da80aed51d01a56a89ef37f4411adbd75c5 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/76/05fecac5dee01fb9df55ca984dcc7a72810f48 b/test/integration/stashDrop/expected/.git_keep/objects/76/05fecac5dee01fb9df55ca984dcc7a72810f48 new file mode 100644 index 000000000..782d5f0ee Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/76/05fecac5dee01fb9df55ca984dcc7a72810f48 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c b/test/integration/stashDrop/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c new file mode 100644 index 000000000..0e95eb06d Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/9f/2757166809c291c65f09778abb46cfcc4e4a0c b/test/integration/stashDrop/expected/.git_keep/objects/9f/2757166809c291c65f09778abb46cfcc4e4a0c new file mode 100644 index 000000000..539f97919 Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/9f/2757166809c291c65f09778abb46cfcc4e4a0c differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/stashDrop/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/a6/ed180e13649885eed39866051ca0e25c0ad6ac b/test/integration/stashDrop/expected/.git_keep/objects/a6/ed180e13649885eed39866051ca0e25c0ad6ac new file mode 100644 index 000000000..20498e40d Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/a6/ed180e13649885eed39866051ca0e25c0ad6ac differ diff --git a/test/integration/tags3/expected/.git_keep/objects/54/bd2b22db1e849928a333ebfe8f3fba1159adde b/test/integration/stashDrop/expected/.git_keep/objects/c0/0c9eb1ae239494475772c3f3dbae5ea4169575 similarity index 59% rename from test/integration/tags3/expected/.git_keep/objects/54/bd2b22db1e849928a333ebfe8f3fba1159adde rename to test/integration/stashDrop/expected/.git_keep/objects/c0/0c9eb1ae239494475772c3f3dbae5ea4169575 index 94cec8242..3a5979b5c 100644 --- a/test/integration/tags3/expected/.git_keep/objects/54/bd2b22db1e849928a333ebfe8f3fba1159adde +++ b/test/integration/stashDrop/expected/.git_keep/objects/c0/0c9eb1ae239494475772c3f3dbae5ea4169575 @@ -1,2 +1,2 @@ xA -0Fa9I#i`R"x|{޼tS 1Yr!VToPS.}ki>NO|S{[{!ǤO겂۝+ \ No newline at end of file +0Fa9I#i`R"x|{޼tS 1Yr!VToPS.}ki>NO|S{[{X$Yt]]V?+ \ No newline at end of file diff --git a/test/integration/stashFiles/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a b/test/integration/stashDrop/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a similarity index 100% rename from test/integration/stashFiles/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a rename to test/integration/stashDrop/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a diff --git a/test/integration/stashDrop/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/stashDrop/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 new file mode 100644 index 000000000..2e9066287 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 @@ -0,0 +1,2 @@ +x+)JMU03c040031QHI5`ֶww.hT[H + yW5Ɨ(| ^-W(x9 \ No newline at end of file diff --git a/test/integration/stashDrop/expected/.git_keep/objects/d2/2496528fe3c076a668c496ae7ba1f8136f1614 b/test/integration/stashDrop/expected/.git_keep/objects/d2/2496528fe3c076a668c496ae7ba1f8136f1614 new file mode 100644 index 000000000..ce64e7f52 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/objects/d2/2496528fe3c076a668c496ae7ba1f8136f1614 @@ -0,0 +1,3 @@ +x1 +0 @>B8BC%\#tV<})gEpg=ɝ-0V: \ No newline at end of file diff --git a/test/integration/stashDrop/expected/.git_keep/objects/e0/61c8716830532562f919dcb125ea804f87ca2b b/test/integration/stashDrop/expected/.git_keep/objects/e0/61c8716830532562f919dcb125ea804f87ca2b new file mode 100644 index 000000000..57420d027 Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/e0/61c8716830532562f919dcb125ea804f87ca2b differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/e5/cef1a548f3613b3e538bd0fc2b4ec88043fc25 b/test/integration/stashDrop/expected/.git_keep/objects/e5/cef1a548f3613b3e538bd0fc2b4ec88043fc25 new file mode 100644 index 000000000..965e28b60 Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/e5/cef1a548f3613b3e538bd0fc2b4ec88043fc25 differ diff --git a/test/integration/stashDrop/expected/.git_keep/objects/f4/f81b6542e98a2f80269449674b0f8f454b74b0 b/test/integration/stashDrop/expected/.git_keep/objects/f4/f81b6542e98a2f80269449674b0f8f454b74b0 new file mode 100644 index 000000000..0c09e49d2 Binary files /dev/null and b/test/integration/stashDrop/expected/.git_keep/objects/f4/f81b6542e98a2f80269449674b0f8f454b74b0 differ diff --git a/test/integration/stashDrop/expected/.git_keep/refs/heads/master b/test/integration/stashDrop/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..0a7c9cc2b --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +d22496528fe3c076a668c496ae7ba1f8136f1614 diff --git a/test/integration/stashDrop/expected/.git_keep/refs/stash b/test/integration/stashDrop/expected/.git_keep/refs/stash new file mode 100644 index 000000000..0a4ae7ab5 --- /dev/null +++ b/test/integration/stashDrop/expected/.git_keep/refs/stash @@ -0,0 +1 @@ +f4f81b6542e98a2f80269449674b0f8f454b74b0 diff --git a/test/integration/stashDrop/expected/file0 b/test/integration/stashDrop/expected/file0 new file mode 100644 index 000000000..38143ad4a --- /dev/null +++ b/test/integration/stashDrop/expected/file0 @@ -0,0 +1 @@ +test0 diff --git a/test/integration/stashDrop/expected/file1 b/test/integration/stashDrop/expected/file1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/stashDrop/expected/file1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/stashFiles/expected/file2 b/test/integration/stashDrop/expected/file2 similarity index 100% rename from test/integration/stashFiles/expected/file2 rename to test/integration/stashDrop/expected/file2 diff --git a/test/integration/stashDrop/recording.json b/test/integration/stashDrop/recording.json new file mode 100644 index 000000000..c61c97b24 --- /dev/null +++ b/test/integration/stashDrop/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":1329,"Mod":0,"Key":256,"Ch":32},{"Timestamp":2003,"Mod":0,"Key":256,"Ch":83},{"Timestamp":2466,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2754,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2979,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3051,"Mod":0,"Key":256,"Ch":115},{"Timestamp":3081,"Mod":0,"Key":256,"Ch":100},{"Timestamp":3386,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3961,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4186,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4906,"Mod":0,"Key":256,"Ch":83},{"Timestamp":5394,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5626,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6002,"Mod":0,"Key":256,"Ch":100},{"Timestamp":6066,"Mod":0,"Key":256,"Ch":115},{"Timestamp":6113,"Mod":0,"Key":256,"Ch":97},{"Timestamp":6378,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6842,"Mod":0,"Key":259,"Ch":0},{"Timestamp":7074,"Mod":0,"Key":259,"Ch":0},{"Timestamp":7315,"Mod":0,"Key":259,"Ch":0},{"Timestamp":7714,"Mod":0,"Key":258,"Ch":0},{"Timestamp":8114,"Mod":0,"Key":256,"Ch":100},{"Timestamp":8546,"Mod":0,"Key":13,"Ch":13},{"Timestamp":9379,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/stashFiles/setup.sh b/test/integration/stashDrop/setup.sh similarity index 97% rename from test/integration/stashFiles/setup.sh rename to test/integration/stashDrop/setup.sh index 7b6eea95c..caff56b7d 100644 --- a/test/integration/stashFiles/setup.sh +++ b/test/integration/stashDrop/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/stashDrop/test.json b/test/integration/stashDrop/test.json new file mode 100644 index 000000000..7cb436f8f --- /dev/null +++ b/test/integration/stashDrop/test.json @@ -0,0 +1,4 @@ +{ + "description": "Stashing some files", + "speed": 20 +} diff --git a/test/integration/stashFiles/expected/.git_keep/ORIG_HEAD b/test/integration/stashFiles/expected/.git_keep/ORIG_HEAD deleted file mode 100644 index 52c8d5849..000000000 --- a/test/integration/stashFiles/expected/.git_keep/ORIG_HEAD +++ /dev/null @@ -1 +0,0 @@ -ee9c2e33476df0ea209b0458006d8f6a0f03d2a1 diff --git a/test/integration/stashFiles/expected/.git_keep/index b/test/integration/stashFiles/expected/.git_keep/index deleted file mode 100644 index 5f67258e9..000000000 Binary files a/test/integration/stashFiles/expected/.git_keep/index and /dev/null differ diff --git a/test/integration/stashFiles/expected/.git_keep/logs/HEAD b/test/integration/stashFiles/expected/.git_keep/logs/HEAD deleted file mode 100644 index b5128c522..000000000 --- a/test/integration/stashFiles/expected/.git_keep/logs/HEAD +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 5e1c4f2926fa291fbd2f95095429c75bc1148693 CI 1617674625 +1000 commit (initial): file0 -5e1c4f2926fa291fbd2f95095429c75bc1148693 143b5c7bcd4b9a7fe84a66eed94ed680660bcfb2 CI 1617674625 +1000 commit: file1 -143b5c7bcd4b9a7fe84a66eed94ed680660bcfb2 ee9c2e33476df0ea209b0458006d8f6a0f03d2a1 CI 1617674625 +1000 commit: file2 -ee9c2e33476df0ea209b0458006d8f6a0f03d2a1 ee9c2e33476df0ea209b0458006d8f6a0f03d2a1 CI 1617674628 +1000 reset: moving to HEAD diff --git a/test/integration/stashFiles/expected/.git_keep/logs/refs/heads/master b/test/integration/stashFiles/expected/.git_keep/logs/refs/heads/master deleted file mode 100644 index 93b075067..000000000 --- a/test/integration/stashFiles/expected/.git_keep/logs/refs/heads/master +++ /dev/null @@ -1,3 +0,0 @@ -0000000000000000000000000000000000000000 5e1c4f2926fa291fbd2f95095429c75bc1148693 CI 1617674625 +1000 commit (initial): file0 -5e1c4f2926fa291fbd2f95095429c75bc1148693 143b5c7bcd4b9a7fe84a66eed94ed680660bcfb2 CI 1617674625 +1000 commit: file1 -143b5c7bcd4b9a7fe84a66eed94ed680660bcfb2 ee9c2e33476df0ea209b0458006d8f6a0f03d2a1 CI 1617674625 +1000 commit: file2 diff --git a/test/integration/stashFiles/expected/.git_keep/logs/refs/stash b/test/integration/stashFiles/expected/.git_keep/logs/refs/stash deleted file mode 100644 index af582ad3d..000000000 --- a/test/integration/stashFiles/expected/.git_keep/logs/refs/stash +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 6ca18002905d50a6a77808f8b7d50839f68da7b7 CI 1617674628 +1000 On master: asd diff --git a/test/integration/stashFiles/expected/.git_keep/objects/14/3b5c7bcd4b9a7fe84a66eed94ed680660bcfb2 b/test/integration/stashFiles/expected/.git_keep/objects/14/3b5c7bcd4b9a7fe84a66eed94ed680660bcfb2 deleted file mode 100644 index d3160cfee..000000000 Binary files a/test/integration/stashFiles/expected/.git_keep/objects/14/3b5c7bcd4b9a7fe84a66eed94ed680660bcfb2 and /dev/null differ diff --git a/test/integration/stashFiles/expected/.git_keep/objects/28/59c9a5f343c80929844d6e49d3792b9169c4da b/test/integration/stashFiles/expected/.git_keep/objects/28/59c9a5f343c80929844d6e49d3792b9169c4da deleted file mode 100644 index ea6cd3866..000000000 Binary files a/test/integration/stashFiles/expected/.git_keep/objects/28/59c9a5f343c80929844d6e49d3792b9169c4da and /dev/null differ diff --git a/test/integration/stashFiles/expected/.git_keep/objects/5e/1c4f2926fa291fbd2f95095429c75bc1148693 b/test/integration/stashFiles/expected/.git_keep/objects/5e/1c4f2926fa291fbd2f95095429c75bc1148693 deleted file mode 100644 index 6ff9310ea..000000000 Binary files a/test/integration/stashFiles/expected/.git_keep/objects/5e/1c4f2926fa291fbd2f95095429c75bc1148693 and /dev/null differ diff --git a/test/integration/stashFiles/expected/.git_keep/objects/6c/a18002905d50a6a77808f8b7d50839f68da7b7 b/test/integration/stashFiles/expected/.git_keep/objects/6c/a18002905d50a6a77808f8b7d50839f68da7b7 deleted file mode 100644 index ae0db48fe..000000000 --- a/test/integration/stashFiles/expected/.git_keep/objects/6c/a18002905d50a6a77808f8b7d50839f68da7b7 +++ /dev/null @@ -1 +0,0 @@ -xϱj1 E|:Ȕ [2-Թpq [w)=`΍ S2$F`Rtͯ#KE'J [W) +4 C`D,RKVUrI{>ϗ?~"DžĘ!}b?u ]zJ \ No newline at end of file diff --git a/test/integration/stashFiles/expected/.git_keep/objects/bf/b00d33696a9cadeeb1dfbe2a9f335f8c821fac b/test/integration/stashFiles/expected/.git_keep/objects/bf/b00d33696a9cadeeb1dfbe2a9f335f8c821fac deleted file mode 100644 index 2aab99cd8..000000000 Binary files a/test/integration/stashFiles/expected/.git_keep/objects/bf/b00d33696a9cadeeb1dfbe2a9f335f8c821fac and /dev/null differ diff --git a/test/integration/stashFiles/expected/.git_keep/objects/ee/9c2e33476df0ea209b0458006d8f6a0f03d2a1 b/test/integration/stashFiles/expected/.git_keep/objects/ee/9c2e33476df0ea209b0458006d8f6a0f03d2a1 deleted file mode 100644 index 4803fd8cf..000000000 --- a/test/integration/stashFiles/expected/.git_keep/objects/ee/9c2e33476df0ea209b0458006d8f6a0f03d2a1 +++ /dev/null @@ -1,3 +0,0 @@ -xA -0E] LDzdfƖ#׺6'SNJ&!TrZ"'!39ҩ.!3KSE)$DU -CtK>v^}^M{}X=h9gO57e': \ No newline at end of file diff --git a/test/integration/stashFiles/expected/.git_keep/refs/heads/master b/test/integration/stashFiles/expected/.git_keep/refs/heads/master deleted file mode 100644 index 52c8d5849..000000000 --- a/test/integration/stashFiles/expected/.git_keep/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -ee9c2e33476df0ea209b0458006d8f6a0f03d2a1 diff --git a/test/integration/stashFiles/expected/.git_keep/refs/stash b/test/integration/stashFiles/expected/.git_keep/refs/stash deleted file mode 100644 index 4b3a3a692..000000000 --- a/test/integration/stashFiles/expected/.git_keep/refs/stash +++ /dev/null @@ -1 +0,0 @@ -6ca18002905d50a6a77808f8b7d50839f68da7b7 diff --git a/test/integration/stashFiles/recording.json b/test/integration/stashFiles/recording.json deleted file mode 100644 index e1b1132ad..000000000 --- a/test/integration/stashFiles/recording.json +++ /dev/null @@ -1 +0,0 @@ -{"KeyEvents":[{"Timestamp":1237,"Mod":0,"Key":256,"Ch":97},{"Timestamp":1677,"Mod":0,"Key":256,"Ch":115},{"Timestamp":1925,"Mod":0,"Key":256,"Ch":97},{"Timestamp":1965,"Mod":0,"Key":256,"Ch":115},{"Timestamp":2053,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2365,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2709,"Mod":0,"Key":259,"Ch":0},{"Timestamp":2941,"Mod":0,"Key":259,"Ch":0},{"Timestamp":3172,"Mod":0,"Key":259,"Ch":0},{"Timestamp":3629,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3973,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4237,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4325,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4477,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4701,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5157,"Mod":0,"Key":256,"Ch":32},{"Timestamp":5957,"Mod":2,"Key":16,"Ch":16},{"Timestamp":6725,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6997,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7613,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/stashNewBranch/expected/.git_keep/index b/test/integration/stashNewBranch/expected/.git_keep/index index f73149efa..7d5c3c25b 100644 Binary files a/test/integration/stashNewBranch/expected/.git_keep/index and b/test/integration/stashNewBranch/expected/.git_keep/index differ diff --git a/test/integration/stashNewBranch/setup.sh b/test/integration/stashNewBranch/setup.sh index 7b6eea95c..caff56b7d 100644 --- a/test/integration/stashNewBranch/setup.sh +++ b/test/integration/stashNewBranch/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/stashPop/expected/.git_keep/COMMIT_EDITMSG b/test/integration/stashPop/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..6c493ff74 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +file2 diff --git a/test/integration/stashPop/expected/.git_keep/FETCH_HEAD b/test/integration/stashPop/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/stashPop/expected/.git_keep/HEAD b/test/integration/stashPop/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/stashPop/expected/.git_keep/ORIG_HEAD b/test/integration/stashPop/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..8e8f6abd0 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +8634432ef171aa4b8d8e688fc1e5645245bf36ac diff --git a/test/integration/stashPop/expected/.git_keep/config b/test/integration/stashPop/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/stashPop/expected/.git_keep/description b/test/integration/stashPop/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/stashPop/expected/.git_keep/index b/test/integration/stashPop/expected/.git_keep/index new file mode 100644 index 000000000..1c25c6cf6 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/index differ diff --git a/test/integration/stashPop/expected/.git_keep/info/exclude b/test/integration/stashPop/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/stashPop/expected/.git_keep/logs/HEAD b/test/integration/stashPop/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..36f584bdf --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/logs/HEAD @@ -0,0 +1,7 @@ +0000000000000000000000000000000000000000 8b081dcb0e1fd5e9862d1aa6891b805b101abe7b CI 1643011851 +1100 commit (initial): file0 +8b081dcb0e1fd5e9862d1aa6891b805b101abe7b 3ae4e5d4920afbb1bac23426afb237524c8dbe41 CI 1643011851 +1100 commit: file1 +3ae4e5d4920afbb1bac23426afb237524c8dbe41 8634432ef171aa4b8d8e688fc1e5645245bf36ac CI 1643011852 +1100 commit: file2 +8634432ef171aa4b8d8e688fc1e5645245bf36ac 8634432ef171aa4b8d8e688fc1e5645245bf36ac CI 1643011854 +1100 reset: moving to HEAD +8634432ef171aa4b8d8e688fc1e5645245bf36ac 8634432ef171aa4b8d8e688fc1e5645245bf36ac CI 1643011854 +1100 reset: moving to HEAD +8634432ef171aa4b8d8e688fc1e5645245bf36ac 8634432ef171aa4b8d8e688fc1e5645245bf36ac CI 1643011856 +1100 reset: moving to HEAD +8634432ef171aa4b8d8e688fc1e5645245bf36ac 8634432ef171aa4b8d8e688fc1e5645245bf36ac CI 1643011856 +1100 reset: moving to HEAD diff --git a/test/integration/stashPop/expected/.git_keep/logs/refs/heads/master b/test/integration/stashPop/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..5493552ad --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 8b081dcb0e1fd5e9862d1aa6891b805b101abe7b CI 1643011851 +1100 commit (initial): file0 +8b081dcb0e1fd5e9862d1aa6891b805b101abe7b 3ae4e5d4920afbb1bac23426afb237524c8dbe41 CI 1643011851 +1100 commit: file1 +3ae4e5d4920afbb1bac23426afb237524c8dbe41 8634432ef171aa4b8d8e688fc1e5645245bf36ac CI 1643011852 +1100 commit: file2 diff --git a/test/integration/stashPop/expected/.git_keep/logs/refs/stash b/test/integration/stashPop/expected/.git_keep/logs/refs/stash new file mode 100644 index 000000000..4a66ec164 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/logs/refs/stash @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 437b9b0ca941f1e12c8b45958f5d6ebd11cdd41a CI 1643011856 +1100 On master: asd diff --git a/test/integration/stashPop/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/stashPop/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/stashPop/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 new file mode 100644 index 000000000..79fcadf67 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/stashPop/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da new file mode 100644 index 000000000..06c9cb73d Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/3a/e4e5d4920afbb1bac23426afb237524c8dbe41 b/test/integration/stashPop/expected/.git_keep/objects/3a/e4e5d4920afbb1bac23426afb237524c8dbe41 new file mode 100644 index 000000000..aa0e72eed Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/3a/e4e5d4920afbb1bac23426afb237524c8dbe41 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/43/7b9b0ca941f1e12c8b45958f5d6ebd11cdd41a b/test/integration/stashPop/expected/.git_keep/objects/43/7b9b0ca941f1e12c8b45958f5d6ebd11cdd41a new file mode 100644 index 000000000..38198edd1 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/objects/43/7b9b0ca941f1e12c8b45958f5d6ebd11cdd41a @@ -0,0 +1 @@ +xϱj1 E%YWJ dgm\M|_1>gOswTDɍkX[nfPID)znjfTj,đ$V|&MdAՆyr lܿn-PDe 'D=~cJ) \ No newline at end of file diff --git a/test/integration/stashPop/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 b/test/integration/stashPop/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 new file mode 100644 index 000000000..6a6f24362 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 b/test/integration/stashPop/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 new file mode 100644 index 000000000..c84b87a17 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 @@ -0,0 +1,3 @@ +x+)JMUd040031QHI5`ֶww.hT[H + e"ǨS,gu"YH +$x~5(;rբW-Ж+^ \ No newline at end of file diff --git a/test/integration/stashPop/expected/.git_keep/objects/82/cc524693ae9fb40af0ed8ab7e22581084dcd17 b/test/integration/stashPop/expected/.git_keep/objects/82/cc524693ae9fb40af0ed8ab7e22581084dcd17 new file mode 100644 index 000000000..1794b8694 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/82/cc524693ae9fb40af0ed8ab7e22581084dcd17 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/86/34432ef171aa4b8d8e688fc1e5645245bf36ac b/test/integration/stashPop/expected/.git_keep/objects/86/34432ef171aa4b8d8e688fc1e5645245bf36ac new file mode 100644 index 000000000..24d309c9e Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/86/34432ef171aa4b8d8e688fc1e5645245bf36ac differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/8b/081dcb0e1fd5e9862d1aa6891b805b101abe7b b/test/integration/stashPop/expected/.git_keep/objects/8b/081dcb0e1fd5e9862d1aa6891b805b101abe7b new file mode 100644 index 000000000..e730193f1 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/8b/081dcb0e1fd5e9862d1aa6891b805b101abe7b differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/8b/d86c566a91e9f8ace9883f7017f562c971b3f7 b/test/integration/stashPop/expected/.git_keep/objects/8b/d86c566a91e9f8ace9883f7017f562c971b3f7 new file mode 100644 index 000000000..4541e3cc9 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/8b/d86c566a91e9f8ace9883f7017f562c971b3f7 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/8e/b0f6c64dea2004a684ea55f9589b71b45d76a6 b/test/integration/stashPop/expected/.git_keep/objects/8e/b0f6c64dea2004a684ea55f9589b71b45d76a6 new file mode 100644 index 000000000..2a1928079 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/8e/b0f6c64dea2004a684ea55f9589b71b45d76a6 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c b/test/integration/stashPop/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c new file mode 100644 index 000000000..0e95eb06d Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/stashPop/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/b0/00623a052b4d2226c43ba396b830738799740e b/test/integration/stashPop/expected/.git_keep/objects/b0/00623a052b4d2226c43ba396b830738799740e new file mode 100644 index 000000000..8ebf85670 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/b0/00623a052b4d2226c43ba396b830738799740e differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/c6/a8d49b926afc9ff2b4c64398ee678c50c2c953 b/test/integration/stashPop/expected/.git_keep/objects/c6/a8d49b926afc9ff2b4c64398ee678c50c2c953 new file mode 100644 index 000000000..6f47ae7a5 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/objects/c6/a8d49b926afc9ff2b4c64398ee678c50c2c953 @@ -0,0 +1 @@ +xj1 @;Wh/˖e](le[8.sK;g}7~ٿ IJk\wLcls9'kb馛]aBURX=k});9-權dGA'TK4^78.~\>v@%2":7t>}-'XgoM} \ No newline at end of file diff --git a/test/integration/stashPop/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a b/test/integration/stashPop/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a new file mode 100644 index 000000000..ee4385f12 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a differ diff --git a/test/integration/stashPop/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/stashPop/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 new file mode 100644 index 000000000..2e9066287 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 @@ -0,0 +1,2 @@ +x+)JMU03c040031QHI5`ֶww.hT[H + yW5Ɨ(| ^-W(x9 \ No newline at end of file diff --git a/test/integration/stashPop/expected/.git_keep/objects/e0/0e994a4acb98bcbe93ad478e09dcb3bed6b26c b/test/integration/stashPop/expected/.git_keep/objects/e0/0e994a4acb98bcbe93ad478e09dcb3bed6b26c new file mode 100644 index 000000000..026ea2da1 Binary files /dev/null and b/test/integration/stashPop/expected/.git_keep/objects/e0/0e994a4acb98bcbe93ad478e09dcb3bed6b26c differ diff --git a/test/integration/stashPop/expected/.git_keep/refs/heads/master b/test/integration/stashPop/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..8e8f6abd0 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +8634432ef171aa4b8d8e688fc1e5645245bf36ac diff --git a/test/integration/stashPop/expected/.git_keep/refs/stash b/test/integration/stashPop/expected/.git_keep/refs/stash new file mode 100644 index 000000000..86ea2c9b3 --- /dev/null +++ b/test/integration/stashPop/expected/.git_keep/refs/stash @@ -0,0 +1 @@ +437b9b0ca941f1e12c8b45958f5d6ebd11cdd41a diff --git a/test/integration/stashPop/expected/file0 b/test/integration/stashPop/expected/file0 new file mode 100644 index 000000000..38143ad4a --- /dev/null +++ b/test/integration/stashPop/expected/file0 @@ -0,0 +1 @@ +test0 diff --git a/test/integration/stashFiles/expected/file1 b/test/integration/stashPop/expected/file1 similarity index 100% rename from test/integration/stashFiles/expected/file1 rename to test/integration/stashPop/expected/file1 diff --git a/test/integration/stashPop/expected/file2 b/test/integration/stashPop/expected/file2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/stashPop/expected/file2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/stashFiles/expected/file3 b/test/integration/stashPop/expected/file3 similarity index 100% rename from test/integration/stashFiles/expected/file3 rename to test/integration/stashPop/expected/file3 diff --git a/test/integration/stashPop/recording.json b/test/integration/stashPop/recording.json new file mode 100644 index 000000000..8629fa444 --- /dev/null +++ b/test/integration/stashPop/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":752,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1144,"Mod":0,"Key":256,"Ch":83},{"Timestamp":1424,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1640,"Mod":0,"Key":13,"Ch":13},{"Timestamp":1742,"Mod":0,"Key":256,"Ch":97},{"Timestamp":1775,"Mod":0,"Key":256,"Ch":115},{"Timestamp":1832,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2097,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2552,"Mod":0,"Key":256,"Ch":32},{"Timestamp":3065,"Mod":0,"Key":256,"Ch":83},{"Timestamp":3425,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3584,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3840,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3880,"Mod":0,"Key":256,"Ch":115},{"Timestamp":3967,"Mod":0,"Key":256,"Ch":100},{"Timestamp":4656,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5121,"Mod":0,"Key":259,"Ch":0},{"Timestamp":5304,"Mod":0,"Key":259,"Ch":0},{"Timestamp":5608,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6008,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6921,"Mod":0,"Key":256,"Ch":103},{"Timestamp":7328,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8304,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/stashPop/setup.sh b/test/integration/stashPop/setup.sh new file mode 100644 index 000000000..caff56b7d --- /dev/null +++ b/test/integration/stashPop/setup.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test0 > file0 +git add . +git commit -am file0 + +echo test1 > file1 +git add . +git commit -am file1 + +echo test2 > file2 +git add . +git commit -am file2 + +echo "hello there" > file1 +echo "hello there" > file2 +echo "hello there" > file3 diff --git a/test/integration/stashPop/test.json b/test/integration/stashPop/test.json new file mode 100644 index 000000000..7cb436f8f --- /dev/null +++ b/test/integration/stashPop/test.json @@ -0,0 +1,4 @@ +{ + "description": "Stashing some files", + "speed": 20 +} diff --git a/test/integration/stash_Copy/expected/.git_keep/COMMIT_EDITMSG b/test/integration/stash_Copy/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..6c493ff74 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +file2 diff --git a/test/integration/stash_Copy/expected/.git_keep/FETCH_HEAD b/test/integration/stash_Copy/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/stash_Copy/expected/.git_keep/HEAD b/test/integration/stash_Copy/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/stash_Copy/expected/.git_keep/ORIG_HEAD b/test/integration/stash_Copy/expected/.git_keep/ORIG_HEAD new file mode 100644 index 000000000..78e4eb58e --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/ORIG_HEAD @@ -0,0 +1 @@ +f348ff60bdbb3695f2f519db6bc115b1b8d50886 diff --git a/test/integration/stash_Copy/expected/.git_keep/config b/test/integration/stash_Copy/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/stash_Copy/expected/.git_keep/description b/test/integration/stash_Copy/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/stash_Copy/expected/.git_keep/index b/test/integration/stash_Copy/expected/.git_keep/index new file mode 100644 index 000000000..daebfa4ef Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/index differ diff --git a/test/integration/stash_Copy/expected/.git_keep/info/exclude b/test/integration/stash_Copy/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/stash_Copy/expected/.git_keep/logs/HEAD b/test/integration/stash_Copy/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..f95cf06ac --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/logs/HEAD @@ -0,0 +1,6 @@ +0000000000000000000000000000000000000000 a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b CI 1643011553 +1100 commit (initial): file0 +a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b 4cc838ea1466afc5be1d3bc3e7a937641ec84d7d CI 1643011553 +1100 commit: file1 +4cc838ea1466afc5be1d3bc3e7a937641ec84d7d f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011553 +1100 commit: file2 +f348ff60bdbb3695f2f519db6bc115b1b8d50886 f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011556 +1100 reset: moving to HEAD +f348ff60bdbb3695f2f519db6bc115b1b8d50886 f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011556 +1100 reset: moving to HEAD +f348ff60bdbb3695f2f519db6bc115b1b8d50886 f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011558 +1100 reset: moving to HEAD diff --git a/test/integration/stash_Copy/expected/.git_keep/logs/refs/heads/master b/test/integration/stash_Copy/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..d1bf421f8 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b CI 1643011553 +1100 commit (initial): file0 +a7dde526f2e93ffa08897fbfca2c98ce40a8fa5b 4cc838ea1466afc5be1d3bc3e7a937641ec84d7d CI 1643011553 +1100 commit: file1 +4cc838ea1466afc5be1d3bc3e7a937641ec84d7d f348ff60bdbb3695f2f519db6bc115b1b8d50886 CI 1643011553 +1100 commit: file2 diff --git a/test/integration/stash_Copy/expected/.git_keep/logs/refs/stash b/test/integration/stash_Copy/expected/.git_keep/logs/refs/stash new file mode 100644 index 000000000..3f07cf22e --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/logs/refs/stash @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 e09b4dfcd66bfa1c81feeaf67e04d55368a2b065 CI 1643011556 +1100 On master: asd +e09b4dfcd66bfa1c81feeaf67e04d55368a2b065 2efac8148440778cbddcd80ac7477981277dcffe CI 1643011558 +1100 On master: asd diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/stash_Copy/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/stash_Copy/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 new file mode 100644 index 000000000..79fcadf67 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/25/abc3e66a6aa505fb0a2ceb6ad5cda0cc89ecbc b/test/integration/stash_Copy/expected/.git_keep/objects/25/abc3e66a6aa505fb0a2ceb6ad5cda0cc89ecbc new file mode 100644 index 000000000..aab767a08 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/25/abc3e66a6aa505fb0a2ceb6ad5cda0cc89ecbc differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/2e/fac8148440778cbddcd80ac7477981277dcffe b/test/integration/stash_Copy/expected/.git_keep/objects/2e/fac8148440778cbddcd80ac7477981277dcffe new file mode 100644 index 000000000..54545b630 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/objects/2e/fac8148440778cbddcd80ac7477981277dcffe @@ -0,0 +1 @@ +xAj1 4v)%UV=$˴Pgątg{bOpF[`X,`L ˶eQMl,y.5\FTeHJ3H:!5ER [.nj|^/׳H g2B@\:O '_o1 ȣK \ No newline at end of file diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/stash_Copy/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da new file mode 100644 index 000000000..06c9cb73d Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/3b/29b35d4357f8e64cafd95140a70d7c9b25138a b/test/integration/stash_Copy/expected/.git_keep/objects/3b/29b35d4357f8e64cafd95140a70d7c9b25138a new file mode 100644 index 000000000..1b8805172 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/3b/29b35d4357f8e64cafd95140a70d7c9b25138a differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/4c/c838ea1466afc5be1d3bc3e7a937641ec84d7d b/test/integration/stash_Copy/expected/.git_keep/objects/4c/c838ea1466afc5be1d3bc3e7a937641ec84d7d new file mode 100644 index 000000000..bc099c320 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/4c/c838ea1466afc5be1d3bc3e7a937641ec84d7d differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/56/52247b638d1516506790d6648b864ba3447f68 b/test/integration/stash_Copy/expected/.git_keep/objects/56/52247b638d1516506790d6648b864ba3447f68 new file mode 100644 index 000000000..8535af67c Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/56/52247b638d1516506790d6648b864ba3447f68 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 b/test/integration/stash_Copy/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 new file mode 100644 index 000000000..6a6f24362 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/5c/ef9afea6a37d89f925e24ebf71adecb63d1f07 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 b/test/integration/stash_Copy/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 new file mode 100644 index 000000000..c84b87a17 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/objects/66/bbc809cdafd867cf9320bfb7484bb8fa898448 @@ -0,0 +1,3 @@ +x+)JMUd040031QHI5`ֶww.hT[H + e"ǨS,gu"YH +$x~5(;rբW-Ж+^ \ No newline at end of file diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c b/test/integration/stash_Copy/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c new file mode 100644 index 000000000..0e95eb06d Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/9f/2757166809c291c65f09778abb46cfcc4e4a0c b/test/integration/stash_Copy/expected/.git_keep/objects/9f/2757166809c291c65f09778abb46cfcc4e4a0c new file mode 100644 index 000000000..539f97919 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/9f/2757166809c291c65f09778abb46cfcc4e4a0c differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/stash_Copy/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/a6/ada9f3d895e751ec289c69913a02146c0ca844 b/test/integration/stash_Copy/expected/.git_keep/objects/a6/ada9f3d895e751ec289c69913a02146c0ca844 new file mode 100644 index 000000000..0cdd88ea0 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/a6/ada9f3d895e751ec289c69913a02146c0ca844 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/a7/dde526f2e93ffa08897fbfca2c98ce40a8fa5b b/test/integration/stash_Copy/expected/.git_keep/objects/a7/dde526f2e93ffa08897fbfca2c98ce40a8fa5b new file mode 100644 index 000000000..9dcd075d6 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/a7/dde526f2e93ffa08897fbfca2c98ce40a8fa5b differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a b/test/integration/stash_Copy/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a new file mode 100644 index 000000000..ee4385f12 Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/c7/c7da3c64e86c3270f2639a1379e67e14891b6a differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/stash_Copy/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 new file mode 100644 index 000000000..2e9066287 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 @@ -0,0 +1,2 @@ +x+)JMU03c040031QHI5`ֶww.hT[H + yW5Ɨ(| ^-W(x9 \ No newline at end of file diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/e0/9b4dfcd66bfa1c81feeaf67e04d55368a2b065 b/test/integration/stash_Copy/expected/.git_keep/objects/e0/9b4dfcd66bfa1c81feeaf67e04d55368a2b065 new file mode 100644 index 000000000..f57ce417b Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/e0/9b4dfcd66bfa1c81feeaf67e04d55368a2b065 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/objects/f3/48ff60bdbb3695f2f519db6bc115b1b8d50886 b/test/integration/stash_Copy/expected/.git_keep/objects/f3/48ff60bdbb3695f2f519db6bc115b1b8d50886 new file mode 100644 index 000000000..8faee1fcd Binary files /dev/null and b/test/integration/stash_Copy/expected/.git_keep/objects/f3/48ff60bdbb3695f2f519db6bc115b1b8d50886 differ diff --git a/test/integration/stash_Copy/expected/.git_keep/refs/heads/master b/test/integration/stash_Copy/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..78e4eb58e --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +f348ff60bdbb3695f2f519db6bc115b1b8d50886 diff --git a/test/integration/stash_Copy/expected/.git_keep/refs/stash b/test/integration/stash_Copy/expected/.git_keep/refs/stash new file mode 100644 index 000000000..9123248e5 --- /dev/null +++ b/test/integration/stash_Copy/expected/.git_keep/refs/stash @@ -0,0 +1 @@ +2efac8148440778cbddcd80ac7477981277dcffe diff --git a/test/integration/stash_Copy/expected/file0 b/test/integration/stash_Copy/expected/file0 new file mode 100644 index 000000000..38143ad4a --- /dev/null +++ b/test/integration/stash_Copy/expected/file0 @@ -0,0 +1 @@ +test0 diff --git a/test/integration/stash_Copy/expected/file1 b/test/integration/stash_Copy/expected/file1 new file mode 100644 index 000000000..c7c7da3c6 --- /dev/null +++ b/test/integration/stash_Copy/expected/file1 @@ -0,0 +1 @@ +hello there diff --git a/test/integration/stash_Copy/expected/file2 b/test/integration/stash_Copy/expected/file2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/stash_Copy/expected/file2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/stash_Copy/recording.json b/test/integration/stash_Copy/recording.json new file mode 100644 index 000000000..48fc35158 --- /dev/null +++ b/test/integration/stash_Copy/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":809,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1369,"Mod":0,"Key":256,"Ch":83},{"Timestamp":1713,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2087,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2376,"Mod":0,"Key":256,"Ch":97},{"Timestamp":2440,"Mod":0,"Key":256,"Ch":115},{"Timestamp":2512,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2793,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3498,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4113,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4785,"Mod":0,"Key":256,"Ch":115},{"Timestamp":5145,"Mod":0,"Key":256,"Ch":97},{"Timestamp":5183,"Mod":0,"Key":256,"Ch":115},{"Timestamp":5249,"Mod":0,"Key":256,"Ch":100},{"Timestamp":5609,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6216,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6457,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6728,"Mod":0,"Key":259,"Ch":0},{"Timestamp":7098,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7408,"Mod":0,"Key":256,"Ch":32},{"Timestamp":8080,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8752,"Mod":0,"Key":260,"Ch":0},{"Timestamp":8952,"Mod":0,"Key":260,"Ch":0},{"Timestamp":9145,"Mod":0,"Key":260,"Ch":0},{"Timestamp":9904,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/stash_Copy/setup.sh b/test/integration/stash_Copy/setup.sh new file mode 100644 index 000000000..caff56b7d --- /dev/null +++ b/test/integration/stash_Copy/setup.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test0 > file0 +git add . +git commit -am file0 + +echo test1 > file1 +git add . +git commit -am file1 + +echo test2 > file2 +git add . +git commit -am file2 + +echo "hello there" > file1 +echo "hello there" > file2 +echo "hello there" > file3 diff --git a/test/integration/stashFiles/test.json b/test/integration/stash_Copy/test.json similarity index 100% rename from test/integration/stashFiles/test.json rename to test/integration/stash_Copy/test.json diff --git a/test/integration/submoduleAdd/expected/.git_keep/COMMIT_EDITMSG b/test/integration/submoduleAdd/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..9daeafb98 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +test diff --git a/test/integration/submoduleAdd/expected/.git_keep/FETCH_HEAD b/test/integration/submoduleAdd/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/submoduleAdd/expected/.git_keep/HEAD b/test/integration/submoduleAdd/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/submoduleAdd/expected/.git_keep/config b/test/integration/submoduleAdd/expected/.git_keep/config new file mode 100644 index 000000000..3770f8692 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI +[submodule "blah"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleAdd/other_repo + active = true diff --git a/test/integration/submoduleAdd/expected/.git_keep/description b/test/integration/submoduleAdd/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/submoduleAdd/expected/.git_keep/index b/test/integration/submoduleAdd/expected/.git_keep/index new file mode 100644 index 000000000..df11fba0b Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/index differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/info/exclude b/test/integration/submoduleAdd/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/submoduleAdd/expected/.git_keep/logs/HEAD b/test/integration/submoduleAdd/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..df53976dc --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/logs/HEAD @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e 6de70e35394a99cc437d1bc70b0852b70c5bb03d CI 1617797593 +1000 commit: test diff --git a/test/integration/submoduleAdd/expected/.git_keep/logs/refs/heads/master b/test/integration/submoduleAdd/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..df53976dc --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e 6de70e35394a99cc437d1bc70b0852b70c5bb03d CI 1617797593 +1000 commit: test diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/HEAD b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/config b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/config new file mode 100644 index 000000000..91988eba6 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/config @@ -0,0 +1,14 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true + worktree = ../../../haha +[remote "origin"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleAdd/other_repo + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/description b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/index b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/index new file mode 100644 index 000000000..64a52c781 Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/index differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/info/exclude b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/HEAD b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/HEAD new file mode 100644 index 000000000..0ed3aba82 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1617797593 +1000 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleAdd/other_repo diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/refs/heads/master b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/refs/heads/master new file mode 100644 index 000000000..0ed3aba82 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1617797593 +1000 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleAdd/other_repo diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/refs/remotes/origin/HEAD b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/refs/remotes/origin/HEAD new file mode 100644 index 000000000..0ed3aba82 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1617797593 +1000 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleAdd/other_repo diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e new file mode 100644 index 000000000..64d20cb1e Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 new file mode 100644 index 000000000..5dd5f3236 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 @@ -0,0 +1,2 @@ +xM +0@sJ&N  I޾/ZHR%d"r@X<-4dG5?fxN(_& 6]פ˟\ճ5, \ No newline at end of file diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/packed-refs b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/packed-refs new file mode 100644 index 000000000..2991aad64 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +42530e986dbb65877ed8d61ca0c816e425e5c62e refs/remotes/origin/master diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/refs/heads/master b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/refs/heads/master new file mode 100644 index 000000000..a70ff80c0 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/refs/heads/master @@ -0,0 +1 @@ +42530e986dbb65877ed8d61ca0c816e425e5c62e diff --git a/test/integration/submoduleAdd/expected/.git_keep/modules/blah/refs/remotes/origin/HEAD b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/modules/blah/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/submoduleAdd/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/submoduleAdd/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e b/test/integration/submoduleAdd/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e new file mode 100644 index 000000000..64d20cb1e Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/5f/77fb3622a1035782a7dacc0cca12e674066b9e b/test/integration/submoduleAdd/expected/.git_keep/objects/5f/77fb3622a1035782a7dacc0cca12e674066b9e new file mode 100644 index 000000000..bc8326e0b Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/5f/77fb3622a1035782a7dacc0cca12e674066b9e differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/6d/e70e35394a99cc437d1bc70b0852b70c5bb03d b/test/integration/submoduleAdd/expected/.git_keep/objects/6d/e70e35394a99cc437d1bc70b0852b70c5bb03d new file mode 100644 index 000000000..8545bdc31 Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/6d/e70e35394a99cc437d1bc70b0852b70c5bb03d differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 b/test/integration/submoduleAdd/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 new file mode 100644 index 000000000..5dd5f3236 --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 @@ -0,0 +1,2 @@ +xM +0@sJ&N  I޾/ZHR%d"r@X<-4dG5?fxN(_& 6]פ˟\ճ5, \ No newline at end of file diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/submoduleAdd/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/submoduleAdd/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/objects/b9/7660affc790464b00ad45c7186a882238d77fb b/test/integration/submoduleAdd/expected/.git_keep/objects/b9/7660affc790464b00ad45c7186a882238d77fb new file mode 100644 index 000000000..c4d570c9d Binary files /dev/null and b/test/integration/submoduleAdd/expected/.git_keep/objects/b9/7660affc790464b00ad45c7186a882238d77fb differ diff --git a/test/integration/submoduleAdd/expected/.git_keep/refs/heads/master b/test/integration/submoduleAdd/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..d41df026f --- /dev/null +++ b/test/integration/submoduleAdd/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +6de70e35394a99cc437d1bc70b0852b70c5bb03d diff --git a/test/integration/submoduleAdd/expected/.gitmodules_keep b/test/integration/submoduleAdd/expected/.gitmodules_keep new file mode 100644 index 000000000..b97660aff --- /dev/null +++ b/test/integration/submoduleAdd/expected/.gitmodules_keep @@ -0,0 +1,3 @@ +[submodule "blah"] + path = haha + url = ../other_repo diff --git a/test/integration/submoduleAdd/expected/haha/.git_keep b/test/integration/submoduleAdd/expected/haha/.git_keep new file mode 100644 index 000000000..a12aca648 --- /dev/null +++ b/test/integration/submoduleAdd/expected/haha/.git_keep @@ -0,0 +1 @@ +gitdir: ../.git/modules/blah diff --git a/test/integration/submoduleAdd/expected/haha/myfile1 b/test/integration/submoduleAdd/expected/haha/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/submoduleAdd/expected/haha/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/submoduleAdd/expected/haha/myfile2 b/test/integration/submoduleAdd/expected/haha/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/submoduleAdd/expected/haha/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/submoduleAdd/expected/myfile1 b/test/integration/submoduleAdd/expected/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/submoduleAdd/expected/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/submoduleAdd/expected/myfile2 b/test/integration/submoduleAdd/expected/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/submoduleAdd/expected/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/submoduleAdd/recording.json b/test/integration/submoduleAdd/recording.json new file mode 100644 index 000000000..aba73bc1c --- /dev/null +++ b/test/integration/submoduleAdd/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":1858,"Mod":0,"Key":256,"Ch":93},{"Timestamp":4332,"Mod":0,"Key":256,"Ch":110},{"Timestamp":5067,"Mod":0,"Key":256,"Ch":46},{"Timestamp":5188,"Mod":0,"Key":256,"Ch":46},{"Timestamp":5323,"Mod":0,"Key":256,"Ch":47},{"Timestamp":5532,"Mod":0,"Key":256,"Ch":111},{"Timestamp":5652,"Mod":0,"Key":256,"Ch":116},{"Timestamp":5743,"Mod":0,"Key":256,"Ch":104},{"Timestamp":5862,"Mod":0,"Key":256,"Ch":101},{"Timestamp":5892,"Mod":0,"Key":256,"Ch":114},{"Timestamp":6102,"Mod":0,"Key":256,"Ch":95},{"Timestamp":6297,"Mod":0,"Key":256,"Ch":114},{"Timestamp":6312,"Mod":0,"Key":256,"Ch":101},{"Timestamp":6402,"Mod":0,"Key":256,"Ch":112},{"Timestamp":6432,"Mod":0,"Key":256,"Ch":111},{"Timestamp":6718,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7632,"Mod":0,"Key":127,"Ch":127},{"Timestamp":7966,"Mod":0,"Key":127,"Ch":127},{"Timestamp":7982,"Mod":0,"Key":127,"Ch":127},{"Timestamp":7998,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8015,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8031,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8049,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8065,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8081,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8097,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8113,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8129,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8146,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8162,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8178,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8195,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8212,"Mod":0,"Key":127,"Ch":127},{"Timestamp":8397,"Mod":0,"Key":256,"Ch":98},{"Timestamp":8578,"Mod":0,"Key":256,"Ch":108},{"Timestamp":8698,"Mod":0,"Key":256,"Ch":97},{"Timestamp":8787,"Mod":0,"Key":256,"Ch":104},{"Timestamp":9012,"Mod":0,"Key":13,"Ch":13},{"Timestamp":9493,"Mod":0,"Key":127,"Ch":127},{"Timestamp":9717,"Mod":0,"Key":127,"Ch":127},{"Timestamp":9868,"Mod":0,"Key":127,"Ch":127},{"Timestamp":9973,"Mod":0,"Key":127,"Ch":127},{"Timestamp":10168,"Mod":0,"Key":256,"Ch":104},{"Timestamp":10228,"Mod":0,"Key":256,"Ch":97},{"Timestamp":10257,"Mod":0,"Key":256,"Ch":104},{"Timestamp":10393,"Mod":0,"Key":256,"Ch":97},{"Timestamp":10752,"Mod":0,"Key":13,"Ch":13},{"Timestamp":12612,"Mod":0,"Key":256,"Ch":91},{"Timestamp":13543,"Mod":0,"Key":256,"Ch":99},{"Timestamp":13797,"Mod":0,"Key":256,"Ch":116},{"Timestamp":13857,"Mod":0,"Key":256,"Ch":101},{"Timestamp":14037,"Mod":0,"Key":256,"Ch":115},{"Timestamp":14081,"Mod":0,"Key":256,"Ch":116},{"Timestamp":14368,"Mod":0,"Key":13,"Ch":13},{"Timestamp":15312,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":36}]} \ No newline at end of file diff --git a/test/integration/submoduleAdd/setup.sh b/test/integration/submoduleAdd/setup.sh new file mode 100644 index 000000000..b5dc60ab7 --- /dev/null +++ b/test/integration/submoduleAdd/setup.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +set -e + +cd $1 + +export GIT_COMMITTER_DATE="Mon 20 Aug 2018 20:19:19 BST" +export GIT_AUTHOR_DATE="Mon 20 Aug 2018 20:19:19 BST" + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" +echo test2 > myfile2 +git add . +git commit -am "myfile2" + +cd .. +git clone --bare ./actual other_repo +cd actual diff --git a/test/integration/submoduleAdd/test.json b/test/integration/submoduleAdd/test.json new file mode 100644 index 000000000..d937c5747 --- /dev/null +++ b/test/integration/submoduleAdd/test.json @@ -0,0 +1,4 @@ +{ + "description": "Add submodule", + "speed": 5 +} diff --git a/test/integration/submoduleEnter/expected/.git_keep/COMMIT_EDITMSG b/test/integration/submoduleEnter/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..9daeafb98 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +test diff --git a/test/integration/submoduleEnter/expected/.git_keep/FETCH_HEAD b/test/integration/submoduleEnter/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/submoduleEnter/expected/.git_keep/HEAD b/test/integration/submoduleEnter/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/submoduleEnter/expected/.git_keep/config b/test/integration/submoduleEnter/expected/.git_keep/config new file mode 100644 index 000000000..6806f15bd --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI +[submodule "other_repo"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleEnter/other_repo + active = true diff --git a/test/integration/submoduleEnter/expected/.git_keep/description b/test/integration/submoduleEnter/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/submoduleEnter/expected/.git_keep/index b/test/integration/submoduleEnter/expected/.git_keep/index new file mode 100644 index 000000000..588f51460 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/index differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/info/exclude b/test/integration/submoduleEnter/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/submoduleEnter/expected/.git_keep/logs/HEAD b/test/integration/submoduleEnter/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..123fc35c9 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/logs/HEAD @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 CI 1534792759 +0100 commit: myfile3 +fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 e1eb418c0ff98940d4ea817eebcff5dcdde645ce CI 1534792759 +0100 commit: add submodule +e1eb418c0ff98940d4ea817eebcff5dcdde645ce c83cc777cf98a8c0f3c0995d7c1b21db92a71c66 CI 1643370762 +1100 commit: test diff --git a/test/integration/submoduleEnter/expected/.git_keep/logs/refs/heads/master b/test/integration/submoduleEnter/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..123fc35c9 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 CI 1534792759 +0100 commit: myfile3 +fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 e1eb418c0ff98940d4ea817eebcff5dcdde645ce CI 1534792759 +0100 commit: add submodule +e1eb418c0ff98940d4ea817eebcff5dcdde645ce c83cc777cf98a8c0f3c0995d7c1b21db92a71c66 CI 1643370762 +1100 commit: test diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/HEAD b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/ORIG_HEAD b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/ORIG_HEAD new file mode 100644 index 000000000..a70ff80c0 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/ORIG_HEAD @@ -0,0 +1 @@ +42530e986dbb65877ed8d61ca0c816e425e5c62e diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/config b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/config new file mode 100644 index 000000000..153285317 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/config @@ -0,0 +1,14 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true + worktree = ../../../other_repo +[remote "origin"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleEnter/other_repo + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/description b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/index b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/index new file mode 100644 index 000000000..385fd7bf9 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/index differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/info/exclude b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/HEAD b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/HEAD new file mode 100644 index 000000000..149d30cef --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/HEAD @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 Jesse Duffield 1534792759 +0100 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleEnter/other_repo +fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1643370757 +1100 rebase -i (start): checkout 42530e986dbb65877ed8d61ca0c816e425e5c62e +42530e986dbb65877ed8d61ca0c816e425e5c62e 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1643370757 +1100 rebase -i (finish): returning to refs/heads/master +42530e986dbb65877ed8d61ca0c816e425e5c62e a50a5125768001a3ea263ffb7cafbc421a508153 Jesse Duffield 1643370757 +1100 rebase -i (start): checkout a50a5125768001a3ea263ffb7cafbc421a508153 +a50a5125768001a3ea263ffb7cafbc421a508153 a50a5125768001a3ea263ffb7cafbc421a508153 Jesse Duffield 1643370757 +1100 rebase -i (finish): returning to refs/heads/master diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/refs/heads/master b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/refs/heads/master new file mode 100644 index 000000000..b88258014 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 Jesse Duffield 1534792759 +0100 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleEnter/other_repo +fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1643370757 +1100 rebase -i (finish): refs/heads/master onto 42530e986dbb65877ed8d61ca0c816e425e5c62e +42530e986dbb65877ed8d61ca0c816e425e5c62e a50a5125768001a3ea263ffb7cafbc421a508153 Jesse Duffield 1643370757 +1100 rebase -i (finish): refs/heads/master onto a50a5125768001a3ea263ffb7cafbc421a508153 diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/refs/remotes/origin/HEAD b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/refs/remotes/origin/HEAD new file mode 100644 index 000000000..d08b838c1 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 Jesse Duffield 1534792759 +0100 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleEnter/other_repo diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e new file mode 100644 index 000000000..64d20cb1e Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/96/c588f28aac5a8ebd6430526697e82e46b3180c b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/96/c588f28aac5a8ebd6430526697e82e46b3180c new file mode 100644 index 000000000..56590efa1 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/96/c588f28aac5a8ebd6430526697e82e46b3180c differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 new file mode 100644 index 000000000..5dd5f3236 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 @@ -0,0 +1,2 @@ +xM +0@sJ&N  I޾/ZHR%d"r@X<-4dG5?fxN(_& 6]פ˟\ճ5, \ No newline at end of file diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/fc/4712e93d74ad4fb68e2fd219ac253ae03e19a4 b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/fc/4712e93d74ad4fb68e2fd219ac253ae03e19a4 new file mode 100644 index 000000000..95d1a3bc9 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/objects/fc/4712e93d74ad4fb68e2fd219ac253ae03e19a4 @@ -0,0 +1,2 @@ +xK +0@] L&PDH&S,4z{{㭵k¥":!(gLR*o!($` =:8Fa-bJ-gdQN-Dwn'=C>xkwm\NzNu3W;/x9 \ No newline at end of file diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/packed-refs b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/packed-refs new file mode 100644 index 000000000..ef4e386a6 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +fc4712e93d74ad4fb68e2fd219ac253ae03e19a4 refs/remotes/origin/master diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/refs/heads/master b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/refs/heads/master new file mode 100644 index 000000000..ab99dc85b --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/refs/heads/master @@ -0,0 +1 @@ +a50a5125768001a3ea263ffb7cafbc421a508153 diff --git a/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/refs/remotes/origin/HEAD b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/modules/other_repo/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/submoduleEnter/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/10/7f435787895be1068f01326df55c355a9d29b1 b/test/integration/submoduleEnter/expected/.git_keep/objects/10/7f435787895be1068f01326df55c355a9d29b1 new file mode 100644 index 000000000..199553fa6 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/10/7f435787895be1068f01326df55c355a9d29b1 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/submoduleEnter/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff b/test/integration/submoduleEnter/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff new file mode 100644 index 000000000..5bfb0cefc Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e b/test/integration/submoduleEnter/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e new file mode 100644 index 000000000..64d20cb1e Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/59/a9aee220657762e2d1c60799a0f5b03137d906 b/test/integration/submoduleEnter/expected/.git_keep/objects/59/a9aee220657762e2d1c60799a0f5b03137d906 new file mode 100644 index 000000000..e60d17459 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/59/a9aee220657762e2d1c60799a0f5b03137d906 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/96/c588f28aac5a8ebd6430526697e82e46b3180c b/test/integration/submoduleEnter/expected/.git_keep/objects/96/c588f28aac5a8ebd6430526697e82e46b3180c new file mode 100644 index 000000000..56590efa1 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/96/c588f28aac5a8ebd6430526697e82e46b3180c differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 b/test/integration/submoduleEnter/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 new file mode 100644 index 000000000..5dd5f3236 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 @@ -0,0 +1,2 @@ +xM +0@sJ&N  I޾/ZHR%d"r@X<-4dG5?fxN(_& 6]פ˟\ճ5, \ No newline at end of file diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/submoduleEnter/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/submoduleEnter/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/submoduleEnter/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/c8/3cc777cf98a8c0f3c0995d7c1b21db92a71c66 b/test/integration/submoduleEnter/expected/.git_keep/objects/c8/3cc777cf98a8c0f3c0995d7c1b21db92a71c66 new file mode 100644 index 000000000..7da492489 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/objects/c8/3cc777cf98a8c0f3c0995d7c1b21db92a71c66 @@ -0,0 +1,2 @@ +xK +0FaYE".#?([jo NYz ͑c4oֈ>D`r&RHu՚6#[Z!Z)riMjJJq_6}6_M}}T~1HuP?"9 \ No newline at end of file diff --git a/test/integration/submoduleEnter/expected/.git_keep/objects/e1/eb418c0ff98940d4ea817eebcff5dcdde645ce b/test/integration/submoduleEnter/expected/.git_keep/objects/e1/eb418c0ff98940d4ea817eebcff5dcdde645ce new file mode 100644 index 000000000..9a2e552b8 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/objects/e1/eb418c0ff98940d4ea817eebcff5dcdde645ce @@ -0,0 +1,4 @@ +xK +0@]d29 "tcL +-5ox^7U,,DCJ` :RafMjɒ+KuWZȲd +Nl0pƇ~^`)3ZDsc럹ROm)xkwm\NzNu3W;/x9 \ No newline at end of file diff --git a/test/integration/submoduleEnter/expected/.git_keep/refs/heads/master b/test/integration/submoduleEnter/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..d109221f5 --- /dev/null +++ b/test/integration/submoduleEnter/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +c83cc777cf98a8c0f3c0995d7c1b21db92a71c66 diff --git a/test/integration/submoduleEnter/expected/.gitmodules_keep b/test/integration/submoduleEnter/expected/.gitmodules_keep new file mode 100644 index 000000000..2b864257b --- /dev/null +++ b/test/integration/submoduleEnter/expected/.gitmodules_keep @@ -0,0 +1,3 @@ +[submodule "other_repo"] + path = other_repo + url = ../other_repo diff --git a/test/integration/submoduleEnter/expected/myfile1 b/test/integration/submoduleEnter/expected/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/submoduleEnter/expected/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/submoduleEnter/expected/myfile2 b/test/integration/submoduleEnter/expected/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/submoduleEnter/expected/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/submoduleEnter/expected/myfile3 b/test/integration/submoduleEnter/expected/myfile3 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/submoduleEnter/expected/myfile3 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/submoduleEnter/expected/other_repo/.git_keep b/test/integration/submoduleEnter/expected/other_repo/.git_keep new file mode 100644 index 000000000..32e5ae2e6 --- /dev/null +++ b/test/integration/submoduleEnter/expected/other_repo/.git_keep @@ -0,0 +1 @@ +gitdir: ../.git/modules/other_repo diff --git a/test/integration/submoduleEnter/expected/other_repo/myfile1 b/test/integration/submoduleEnter/expected/other_repo/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/submoduleEnter/expected/other_repo/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/submoduleEnter/recording.json b/test/integration/submoduleEnter/recording.json new file mode 100644 index 000000000..568b008b0 --- /dev/null +++ b/test/integration/submoduleEnter/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":737,"Mod":0,"Key":256,"Ch":93},{"Timestamp":1306,"Mod":0,"Key":13,"Ch":13},{"Timestamp":1937,"Mod":0,"Key":259,"Ch":0},{"Timestamp":2162,"Mod":0,"Key":259,"Ch":0},{"Timestamp":2462,"Mod":0,"Key":256,"Ch":100},{"Timestamp":2687,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3049,"Mod":0,"Key":256,"Ch":100},{"Timestamp":3242,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3862,"Mod":0,"Key":27,"Ch":0},{"Timestamp":5177,"Mod":0,"Key":256,"Ch":91},{"Timestamp":6272,"Mod":0,"Key":256,"Ch":32},{"Timestamp":6752,"Mod":0,"Key":256,"Ch":99},{"Timestamp":7007,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7051,"Mod":0,"Key":256,"Ch":101},{"Timestamp":7232,"Mod":0,"Key":256,"Ch":115},{"Timestamp":7276,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7547,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8251,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/submoduleEnter/setup.sh b/test/integration/submoduleEnter/setup.sh new file mode 100644 index 000000000..2f876d8ae --- /dev/null +++ b/test/integration/submoduleEnter/setup.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +set -e + +cd $1 + +export GIT_COMMITTER_DATE="Mon 20 Aug 2018 20:19:19 BST" +export GIT_AUTHOR_DATE="Mon 20 Aug 2018 20:19:19 BST" + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" +echo test2 > myfile2 +git add . +git commit -am "myfile2" +echo test2 > myfile3 +git add . +git commit -am "myfile3" + +cd .. +git clone --bare ./actual other_repo +cd actual + +git submodule add ../other_repo +git commit -am "add submodule" diff --git a/test/integration/submoduleEnter/test.json b/test/integration/submoduleEnter/test.json new file mode 100644 index 000000000..7bc6ff8ba --- /dev/null +++ b/test/integration/submoduleEnter/test.json @@ -0,0 +1,4 @@ +{ + "description": "Add submodule and enter. We enter the submodule, remove a couple of commits, and then stage the change in the parent repo", + "speed": 5 +} diff --git a/test/integration/submoduleRemove/expected/.git_keep/COMMIT_EDITMSG b/test/integration/submoduleRemove/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..5b256d7d3 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +remove submodule diff --git a/test/integration/submoduleRemove/expected/.git_keep/FETCH_HEAD b/test/integration/submoduleRemove/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/submoduleRemove/expected/.git_keep/HEAD b/test/integration/submoduleRemove/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/submoduleRemove/expected/.git_keep/config b/test/integration/submoduleRemove/expected/.git_keep/config new file mode 100644 index 000000000..8ae104545 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/submoduleRemove/expected/.git_keep/description b/test/integration/submoduleRemove/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/submoduleRemove/expected/.git_keep/index b/test/integration/submoduleRemove/expected/.git_keep/index new file mode 100644 index 000000000..5a4c6431f Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/index differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/info/exclude b/test/integration/submoduleRemove/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/submoduleRemove/expected/.git_keep/logs/HEAD b/test/integration/submoduleRemove/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..a38da0237 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/logs/HEAD @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e 9d10a5a0a21eb2cfdb6206f474ed57fd5cd51440 CI 1534792759 +0100 commit: add submodule +9d10a5a0a21eb2cfdb6206f474ed57fd5cd51440 40f121d7563ed318d461996b8d84e2ec8632687e CI 1643370773 +1100 commit: remove submodule diff --git a/test/integration/submoduleRemove/expected/.git_keep/logs/refs/heads/master b/test/integration/submoduleRemove/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..a38da0237 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e 9d10a5a0a21eb2cfdb6206f474ed57fd5cd51440 CI 1534792759 +0100 commit: add submodule +9d10a5a0a21eb2cfdb6206f474ed57fd5cd51440 40f121d7563ed318d461996b8d84e2ec8632687e CI 1643370773 +1100 commit: remove submodule diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/submoduleRemove/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/submoduleRemove/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff b/test/integration/submoduleRemove/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff new file mode 100644 index 000000000..5bfb0cefc Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/2e/eb2c1e6451d1318b506eecddf936b59a5f32b8 b/test/integration/submoduleRemove/expected/.git_keep/objects/2e/eb2c1e6451d1318b506eecddf936b59a5f32b8 new file mode 100644 index 000000000..acdda888b Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/2e/eb2c1e6451d1318b506eecddf936b59a5f32b8 differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/40/f121d7563ed318d461996b8d84e2ec8632687e b/test/integration/submoduleRemove/expected/.git_keep/objects/40/f121d7563ed318d461996b8d84e2ec8632687e new file mode 100644 index 000000000..3fed8203a --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/objects/40/f121d7563ed318d461996b8d84e2ec8632687e @@ -0,0 +1,2 @@ +xK +0@] 2U1LPhLx|{/ZCEtAgYfo#h"RaF:g,1Y5%#' &@(.:>S(ƣu=:wR]9Vo6BVn!Kmk"!>] \ No newline at end of file diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e b/test/integration/submoduleRemove/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e new file mode 100644 index 000000000..64d20cb1e Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/9d/10a5a0a21eb2cfdb6206f474ed57fd5cd51440 b/test/integration/submoduleRemove/expected/.git_keep/objects/9d/10a5a0a21eb2cfdb6206f474ed57fd5cd51440 new file mode 100644 index 000000000..d63e62eec Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/9d/10a5a0a21eb2cfdb6206f474ed57fd5cd51440 differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 b/test/integration/submoduleRemove/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 new file mode 100644 index 000000000..5dd5f3236 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 @@ -0,0 +1,2 @@ +xM +0@sJ&N  I޾/ZHR%d"r@X<-4dG5?fxN(_& 6]פ˟\ճ5, \ No newline at end of file diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/submoduleRemove/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/submoduleRemove/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/test/integration/submoduleRemove/expected/.git_keep/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 000000000..711223894 Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/objects/f1/43043bb53b17b5727a43b6cfbb1a8d1f5a222d b/test/integration/submoduleRemove/expected/.git_keep/objects/f1/43043bb53b17b5727a43b6cfbb1a8d1f5a222d new file mode 100644 index 000000000..8673162cc Binary files /dev/null and b/test/integration/submoduleRemove/expected/.git_keep/objects/f1/43043bb53b17b5727a43b6cfbb1a8d1f5a222d differ diff --git a/test/integration/submoduleRemove/expected/.git_keep/refs/heads/master b/test/integration/submoduleRemove/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..0c4653c44 --- /dev/null +++ b/test/integration/submoduleRemove/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +40f121d7563ed318d461996b8d84e2ec8632687e diff --git a/test/integration/submoduleRemove/expected/.gitmodules_keep b/test/integration/submoduleRemove/expected/.gitmodules_keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/submoduleRemove/expected/myfile1 b/test/integration/submoduleRemove/expected/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/submoduleRemove/expected/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/submoduleRemove/expected/myfile2 b/test/integration/submoduleRemove/expected/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/submoduleRemove/expected/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/submoduleRemove/recording.json b/test/integration/submoduleRemove/recording.json new file mode 100644 index 000000000..ad22a348d --- /dev/null +++ b/test/integration/submoduleRemove/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":912,"Mod":0,"Key":256,"Ch":93},{"Timestamp":1280,"Mod":0,"Key":256,"Ch":100},{"Timestamp":1696,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2544,"Mod":0,"Key":256,"Ch":91},{"Timestamp":2984,"Mod":0,"Key":256,"Ch":99},{"Timestamp":3680,"Mod":0,"Key":256,"Ch":114},{"Timestamp":3736,"Mod":0,"Key":256,"Ch":101},{"Timestamp":3824,"Mod":0,"Key":256,"Ch":109},{"Timestamp":3872,"Mod":0,"Key":256,"Ch":111},{"Timestamp":3944,"Mod":0,"Key":256,"Ch":118},{"Timestamp":3976,"Mod":0,"Key":256,"Ch":101},{"Timestamp":4064,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4152,"Mod":0,"Key":256,"Ch":115},{"Timestamp":4248,"Mod":0,"Key":256,"Ch":117},{"Timestamp":4320,"Mod":0,"Key":256,"Ch":98},{"Timestamp":4560,"Mod":0,"Key":256,"Ch":109},{"Timestamp":4632,"Mod":0,"Key":256,"Ch":111},{"Timestamp":4720,"Mod":0,"Key":256,"Ch":100},{"Timestamp":4824,"Mod":0,"Key":256,"Ch":117},{"Timestamp":4880,"Mod":0,"Key":256,"Ch":108},{"Timestamp":4944,"Mod":0,"Key":256,"Ch":101},{"Timestamp":5152,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5648,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/submoduleRemove/setup.sh b/test/integration/submoduleRemove/setup.sh new file mode 100644 index 000000000..250092ed1 --- /dev/null +++ b/test/integration/submoduleRemove/setup.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -e + +cd $1 + +export GIT_COMMITTER_DATE="Mon 20 Aug 2018 20:19:19 BST" +export GIT_AUTHOR_DATE="Mon 20 Aug 2018 20:19:19 BST" + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" +echo test2 > myfile2 +git add . +git commit -am "myfile2" + +cd .. +git clone --bare ./actual other_repo +cd actual + +git submodule add ../other_repo +git commit -am "add submodule" diff --git a/test/integration/submoduleRemove/test.json b/test/integration/submoduleRemove/test.json new file mode 100644 index 000000000..87daa5360 --- /dev/null +++ b/test/integration/submoduleRemove/test.json @@ -0,0 +1,4 @@ +{ + "description": "Remove a submodule", + "speed": 20 +} diff --git a/test/integration/submoduleReset/config/config.yml b/test/integration/submoduleReset/config/config.yml new file mode 100644 index 000000000..991cda34b --- /dev/null +++ b/test/integration/submoduleReset/config/config.yml @@ -0,0 +1,6 @@ +disableStartupPopups: true +customCommands: + - key: 'N' + description: 'Add file' + context: 'files' + command: 'echo "haha" > output.txt' diff --git a/test/integration/submoduleReset/expected/.git_keep/COMMIT_EDITMSG b/test/integration/submoduleReset/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..ad3cdfdd1 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +add submodule diff --git a/test/integration/submoduleReset/expected/.git_keep/FETCH_HEAD b/test/integration/submoduleReset/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/submoduleReset/expected/.git_keep/HEAD b/test/integration/submoduleReset/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/submoduleReset/expected/.git_keep/config b/test/integration/submoduleReset/expected/.git_keep/config new file mode 100644 index 000000000..30e8aeba3 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = CI@example.com + name = CI +[submodule "other_repo"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleReset/other_repo + active = true diff --git a/test/integration/submoduleReset/expected/.git_keep/description b/test/integration/submoduleReset/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/submoduleReset/expected/.git_keep/index b/test/integration/submoduleReset/expected/.git_keep/index new file mode 100644 index 000000000..8997e61f4 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/index differ diff --git a/test/integration/submoduleReset/expected/.git_keep/info/exclude b/test/integration/submoduleReset/expected/.git_keep/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/submoduleReset/expected/.git_keep/logs/HEAD b/test/integration/submoduleReset/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..d10fa88d0 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/logs/HEAD @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e 9d10a5a0a21eb2cfdb6206f474ed57fd5cd51440 CI 1534792759 +0100 commit: add submodule diff --git a/test/integration/submoduleReset/expected/.git_keep/logs/refs/heads/master b/test/integration/submoduleReset/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..d10fa88d0 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 a50a5125768001a3ea263ffb7cafbc421a508153 CI 1534792759 +0100 commit (initial): myfile1 +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e CI 1534792759 +0100 commit: myfile2 +42530e986dbb65877ed8d61ca0c816e425e5c62e 9d10a5a0a21eb2cfdb6206f474ed57fd5cd51440 CI 1534792759 +0100 commit: add submodule diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/HEAD b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/HEAD new file mode 100644 index 000000000..a70ff80c0 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/HEAD @@ -0,0 +1 @@ +42530e986dbb65877ed8d61ca0c816e425e5c62e diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/ORIG_HEAD b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/ORIG_HEAD new file mode 100644 index 000000000..ab99dc85b --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/ORIG_HEAD @@ -0,0 +1 @@ +a50a5125768001a3ea263ffb7cafbc421a508153 diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/config b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/config new file mode 100644 index 000000000..3ffcf6005 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/config @@ -0,0 +1,14 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true + worktree = ../../../other_repo +[remote "origin"] + url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleReset/other_repo + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/description b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/index b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/index new file mode 100644 index 000000000..407447e94 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/index differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/info/exclude b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/info/exclude new file mode 100644 index 000000000..8e9f2071f --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/info/exclude @@ -0,0 +1,7 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +.DS_Store diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/HEAD b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/HEAD new file mode 100644 index 000000000..2d343313b --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/HEAD @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1534792759 +0100 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleReset/other_repo +42530e986dbb65877ed8d61ca0c816e425e5c62e a50a5125768001a3ea263ffb7cafbc421a508153 Jesse Duffield 1643370783 +1100 rebase -i (start): checkout a50a5125768001a3ea263ffb7cafbc421a508153 +a50a5125768001a3ea263ffb7cafbc421a508153 a50a5125768001a3ea263ffb7cafbc421a508153 Jesse Duffield 1643370783 +1100 rebase -i (finish): returning to refs/heads/master +a50a5125768001a3ea263ffb7cafbc421a508153 a50a5125768001a3ea263ffb7cafbc421a508153 Jesse Duffield 1643370791 +1100 reset: moving to HEAD +a50a5125768001a3ea263ffb7cafbc421a508153 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1643370791 +1100 checkout: moving from master to 42530e986dbb65877ed8d61ca0c816e425e5c62e diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/heads/master b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/heads/master new file mode 100644 index 000000000..227bcfcb6 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1534792759 +0100 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleReset/other_repo +42530e986dbb65877ed8d61ca0c816e425e5c62e a50a5125768001a3ea263ffb7cafbc421a508153 Jesse Duffield 1643370783 +1100 rebase -i (finish): refs/heads/master onto a50a5125768001a3ea263ffb7cafbc421a508153 diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/remotes/origin/HEAD b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/remotes/origin/HEAD new file mode 100644 index 000000000..c0c3c89d8 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 42530e986dbb65877ed8d61ca0c816e425e5c62e Jesse Duffield 1534792759 +0100 clone: from /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/submoduleReset/other_repo diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/stash b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/stash new file mode 100644 index 000000000..4d43eb072 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/logs/refs/stash @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 f35aba17e85e3fe18f7b01c0f65306c9289c482e Jesse Duffield 1643370791 +1100 WIP on master: a50a512 myfile1 diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/17/defcd0e1f9ad96542aa66845e53cb46c91c30d b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/17/defcd0e1f9ad96542aa66845e53cb46c91c30d new file mode 100644 index 000000000..a908456d8 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/17/defcd0e1f9ad96542aa66845e53cb46c91c30d differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e new file mode 100644 index 000000000..64d20cb1e Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000..adf64119a Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/5a/d28e22767f979da2c198dc6c1003b25964e3da b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/5a/d28e22767f979da2c198dc6c1003b25964e3da new file mode 100644 index 000000000..a49384ff6 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/5a/d28e22767f979da2c198dc6c1003b25964e3da differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/84/69b6d9b0a33be075f9e0df61c5a3ebba3ecfd2 b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/84/69b6d9b0a33be075f9e0df61c5a3ebba3ecfd2 new file mode 100644 index 000000000..50202f0c4 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/84/69b6d9b0a33be075f9e0df61c5a3ebba3ecfd2 @@ -0,0 +1,2 @@ +xK E771 +$ܽmssC-%wBLbR[IILV<([pnKJ1(.PpģvW-S]xSGjP'(8NqM}J \ No newline at end of file diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/9d/13001fc1d98cd178f9e604f6f2c2e52794079e b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/9d/13001fc1d98cd178f9e604f6f2c2e52794079e new file mode 100644 index 000000000..8700d75ac Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/9d/13001fc1d98cd178f9e604f6f2c2e52794079e differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 new file mode 100644 index 000000000..5dd5f3236 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 @@ -0,0 +1,2 @@ +xM +0@sJ&N  I޾/ZHR%d"r@X<-4dG5?fxN(_& 6]פ˟\ճ5, \ No newline at end of file diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/f3/5aba17e85e3fe18f7b01c0f65306c9289c482e b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/f3/5aba17e85e3fe18f7b01c0f65306c9289c482e new file mode 100644 index 000000000..e48c17184 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/objects/f3/5aba17e85e3fe18f7b01c0f65306c9289c482e differ diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/packed-refs b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/packed-refs new file mode 100644 index 000000000..2991aad64 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +42530e986dbb65877ed8d61ca0c816e425e5c62e refs/remotes/origin/master diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/heads/master b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/heads/master new file mode 100644 index 000000000..ab99dc85b --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/heads/master @@ -0,0 +1 @@ +a50a5125768001a3ea263ffb7cafbc421a508153 diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/remotes/origin/HEAD b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/stash b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/stash new file mode 100644 index 000000000..4a83bf1dd --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/modules/other_repo/refs/stash @@ -0,0 +1 @@ +f35aba17e85e3fe18f7b01c0f65306c9289c482e diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/submoduleReset/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 new file mode 100644 index 000000000..7f2ebf4ee Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/submoduleReset/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 new file mode 100644 index 000000000..f74bf2335 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff b/test/integration/submoduleReset/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff new file mode 100644 index 000000000..5bfb0cefc Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/2b/864257bf2d49adbad8785540d85030a60852ff differ diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/2e/eb2c1e6451d1318b506eecddf936b59a5f32b8 b/test/integration/submoduleReset/expected/.git_keep/objects/2e/eb2c1e6451d1318b506eecddf936b59a5f32b8 new file mode 100644 index 000000000..acdda888b Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/2e/eb2c1e6451d1318b506eecddf936b59a5f32b8 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e b/test/integration/submoduleReset/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e new file mode 100644 index 000000000..64d20cb1e Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/42/530e986dbb65877ed8d61ca0c816e425e5c62e differ diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/9d/10a5a0a21eb2cfdb6206f474ed57fd5cd51440 b/test/integration/submoduleReset/expected/.git_keep/objects/9d/10a5a0a21eb2cfdb6206f474ed57fd5cd51440 new file mode 100644 index 000000000..d63e62eec Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/9d/10a5a0a21eb2cfdb6206f474ed57fd5cd51440 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 b/test/integration/submoduleReset/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 new file mode 100644 index 000000000..5dd5f3236 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/objects/a5/0a5125768001a3ea263ffb7cafbc421a508153 @@ -0,0 +1,2 @@ +xM +0@sJ&N  I޾/ZHR%d"r@X<-4dG5?fxN(_& 6]פ˟\ճ5, \ No newline at end of file diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/submoduleReset/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/submoduleReset/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 new file mode 100644 index 000000000..96d2e71a6 Binary files /dev/null and b/test/integration/submoduleReset/expected/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 differ diff --git a/test/integration/submoduleReset/expected/.git_keep/refs/heads/master b/test/integration/submoduleReset/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..ad2d893d7 --- /dev/null +++ b/test/integration/submoduleReset/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +9d10a5a0a21eb2cfdb6206f474ed57fd5cd51440 diff --git a/test/integration/submoduleReset/expected/.gitmodules_keep b/test/integration/submoduleReset/expected/.gitmodules_keep new file mode 100644 index 000000000..2b864257b --- /dev/null +++ b/test/integration/submoduleReset/expected/.gitmodules_keep @@ -0,0 +1,3 @@ +[submodule "other_repo"] + path = other_repo + url = ../other_repo diff --git a/test/integration/submoduleReset/expected/myfile1 b/test/integration/submoduleReset/expected/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/submoduleReset/expected/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/submoduleReset/expected/myfile2 b/test/integration/submoduleReset/expected/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/submoduleReset/expected/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/submoduleReset/expected/other_repo/.git_keep b/test/integration/submoduleReset/expected/other_repo/.git_keep new file mode 100644 index 000000000..32e5ae2e6 --- /dev/null +++ b/test/integration/submoduleReset/expected/other_repo/.git_keep @@ -0,0 +1 @@ +gitdir: ../.git/modules/other_repo diff --git a/test/integration/submoduleReset/expected/other_repo/myfile1 b/test/integration/submoduleReset/expected/other_repo/myfile1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/submoduleReset/expected/other_repo/myfile1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/submoduleReset/expected/other_repo/myfile2 b/test/integration/submoduleReset/expected/other_repo/myfile2 new file mode 100644 index 000000000..180cf8328 --- /dev/null +++ b/test/integration/submoduleReset/expected/other_repo/myfile2 @@ -0,0 +1 @@ +test2 diff --git a/test/integration/submoduleReset/recording.json b/test/integration/submoduleReset/recording.json new file mode 100644 index 000000000..2b272d7f5 --- /dev/null +++ b/test/integration/submoduleReset/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":653,"Mod":0,"Key":256,"Ch":93},{"Timestamp":1685,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2749,"Mod":0,"Key":259,"Ch":0},{"Timestamp":2893,"Mod":0,"Key":259,"Ch":0},{"Timestamp":3229,"Mod":0,"Key":256,"Ch":100},{"Timestamp":3661,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4069,"Mod":0,"Key":260,"Ch":0},{"Timestamp":4189,"Mod":0,"Key":260,"Ch":0},{"Timestamp":5180,"Mod":0,"Key":256,"Ch":78},{"Timestamp":6508,"Mod":0,"Key":256,"Ch":32},{"Timestamp":7311,"Mod":0,"Key":27,"Ch":0},{"Timestamp":8212,"Mod":0,"Key":256,"Ch":91},{"Timestamp":10268,"Mod":0,"Key":256,"Ch":100},{"Timestamp":12165,"Mod":0,"Key":13,"Ch":13},{"Timestamp":13741,"Mod":0,"Key":256,"Ch":93},{"Timestamp":14317,"Mod":0,"Key":13,"Ch":13},{"Timestamp":16645,"Mod":0,"Key":259,"Ch":0},{"Timestamp":16805,"Mod":0,"Key":259,"Ch":0},{"Timestamp":17005,"Mod":0,"Key":259,"Ch":0},{"Timestamp":18588,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/submoduleReset/setup.sh b/test/integration/submoduleReset/setup.sh new file mode 100644 index 000000000..250092ed1 --- /dev/null +++ b/test/integration/submoduleReset/setup.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -e + +cd $1 + +export GIT_COMMITTER_DATE="Mon 20 Aug 2018 20:19:19 BST" +export GIT_AUTHOR_DATE="Mon 20 Aug 2018 20:19:19 BST" + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test1 > myfile1 +git add . +git commit -am "myfile1" +echo test2 > myfile2 +git add . +git commit -am "myfile2" + +cd .. +git clone --bare ./actual other_repo +cd actual + +git submodule add ../other_repo +git commit -am "add submodule" diff --git a/test/integration/submoduleReset/test.json b/test/integration/submoduleReset/test.json new file mode 100644 index 000000000..ccac209e4 --- /dev/null +++ b/test/integration/submoduleReset/test.json @@ -0,0 +1,4 @@ +{ + "description": "After making some changes in a submodule, we reset the submodule from the parent repo", + "speed": 10 +} diff --git a/test/integration/switchTabFromMenu/setup.sh b/test/integration/switchTabFromMenu/setup.sh index 65cd6d129..e6f5ec24f 100644 --- a/test/integration/switchTabFromMenu/setup.sh +++ b/test/integration/switchTabFromMenu/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/tags/expected/.git_keep/index b/test/integration/tags/expected/.git_keep/index index 1893dde3a..8112f33fc 100644 Binary files a/test/integration/tags/expected/.git_keep/index and b/test/integration/tags/expected/.git_keep/index differ diff --git a/test/integration/tags/expected/.git_keep/logs/HEAD b/test/integration/tags/expected/.git_keep/logs/HEAD index 35076f038..fafa3e62d 100644 --- a/test/integration/tags/expected/.git_keep/logs/HEAD +++ b/test/integration/tags/expected/.git_keep/logs/HEAD @@ -1,2 +1,2 @@ -0000000000000000000000000000000000000000 1d2a22db54ab37b582dc5e3c4ca660f06cdb4692 CI 1617671537 +1000 commit (initial): file0 -1d2a22db54ab37b582dc5e3c4ca660f06cdb4692 9d577efe8be29a993269a0a737ca9c8321a2adc2 CI 1617671537 +1000 commit: file1 +0000000000000000000000000000000000000000 3e46e87f3ca37fad40d7dd6aca00223d7f49e424 CI 1631477191 -0300 commit (initial): file0 +3e46e87f3ca37fad40d7dd6aca00223d7f49e424 07b4cadb018ce914237e3f31ee264c9555acc1d1 CI 1631477191 -0300 commit: file1 diff --git a/test/integration/tags/expected/.git_keep/logs/refs/heads/master b/test/integration/tags/expected/.git_keep/logs/refs/heads/master index 35076f038..fafa3e62d 100644 --- a/test/integration/tags/expected/.git_keep/logs/refs/heads/master +++ b/test/integration/tags/expected/.git_keep/logs/refs/heads/master @@ -1,2 +1,2 @@ -0000000000000000000000000000000000000000 1d2a22db54ab37b582dc5e3c4ca660f06cdb4692 CI 1617671537 +1000 commit (initial): file0 -1d2a22db54ab37b582dc5e3c4ca660f06cdb4692 9d577efe8be29a993269a0a737ca9c8321a2adc2 CI 1617671537 +1000 commit: file1 +0000000000000000000000000000000000000000 3e46e87f3ca37fad40d7dd6aca00223d7f49e424 CI 1631477191 -0300 commit (initial): file0 +3e46e87f3ca37fad40d7dd6aca00223d7f49e424 07b4cadb018ce914237e3f31ee264c9555acc1d1 CI 1631477191 -0300 commit: file1 diff --git a/test/integration/tags/expected/.git_keep/objects/07/b4cadb018ce914237e3f31ee264c9555acc1d1 b/test/integration/tags/expected/.git_keep/objects/07/b4cadb018ce914237e3f31ee264c9555acc1d1 new file mode 100644 index 000000000..a1fbbc541 Binary files /dev/null and b/test/integration/tags/expected/.git_keep/objects/07/b4cadb018ce914237e3f31ee264c9555acc1d1 differ diff --git a/test/integration/tags/expected/.git_keep/objects/1d/2a22db54ab37b582dc5e3c4ca660f06cdb4692 b/test/integration/tags/expected/.git_keep/objects/1d/2a22db54ab37b582dc5e3c4ca660f06cdb4692 deleted file mode 100644 index a5426bded..000000000 Binary files a/test/integration/tags/expected/.git_keep/objects/1d/2a22db54ab37b582dc5e3c4ca660f06cdb4692 and /dev/null differ diff --git a/test/integration/tags/expected/.git_keep/objects/3e/46e87f3ca37fad40d7dd6aca00223d7f49e424 b/test/integration/tags/expected/.git_keep/objects/3e/46e87f3ca37fad40d7dd6aca00223d7f49e424 new file mode 100644 index 000000000..7c9547938 --- /dev/null +++ b/test/integration/tags/expected/.git_keep/objects/3e/46e87f3ca37fad40d7dd6aca00223d7f49e424 @@ -0,0 +1,2 @@ +xɮ@E+zo%4$ +`aG3?Ɔ(,RˣҽIfԧy2@dTư{S" r:$JshI,~e7I_%G۾ɾ$] E,K )7}}1UkDYl4,@媛($R2iy B"ؗcUeVVݒD1 {>uPVvmQ)Q\"qLVp7]D5ͩ|V#J,B̕ :7WkգhgˢTvEY/!#H1vJVӵ`2z\P5 VC{oqQO!{rnhDVL)rժm21Z熩%C!8/LVZ^WfẎ+7wOA-e|<&>د:\I].q>jӡզx{KC[Za3sd!j) lư-nnC5[a_!{4:C|7 Ѥa$:PMp2ISφe]7|R;CTw|LIGtF͉d"F{'⥶#e/j2AK \ No newline at end of file diff --git a/test/integration/tags/expected/.git_keep/objects/9d/577efe8be29a993269a0a737ca9c8321a2adc2 b/test/integration/tags/expected/.git_keep/objects/9d/577efe8be29a993269a0a737ca9c8321a2adc2 deleted file mode 100644 index 882b0e797..000000000 Binary files a/test/integration/tags/expected/.git_keep/objects/9d/577efe8be29a993269a0a737ca9c8321a2adc2 and /dev/null differ diff --git a/test/integration/tags/expected/.git_keep/packed-refs b/test/integration/tags/expected/.git_keep/packed-refs new file mode 100644 index 000000000..250f18738 --- /dev/null +++ b/test/integration/tags/expected/.git_keep/packed-refs @@ -0,0 +1 @@ +# pack-refs with: peeled fully-peeled sorted diff --git a/test/integration/tags/expected/.git_keep/refs/heads/master b/test/integration/tags/expected/.git_keep/refs/heads/master index 027894ce8..5fe2e1847 100644 --- a/test/integration/tags/expected/.git_keep/refs/heads/master +++ b/test/integration/tags/expected/.git_keep/refs/heads/master @@ -1 +1 @@ -9d577efe8be29a993269a0a737ca9c8321a2adc2 +07b4cadb018ce914237e3f31ee264c9555acc1d1 diff --git a/test/integration/tags/expected/.git_keep/refs/tags/tag1 b/test/integration/tags/expected/.git_keep/refs/tags/tag1 index 027894ce8..5fe2e1847 100644 --- a/test/integration/tags/expected/.git_keep/refs/tags/tag1 +++ b/test/integration/tags/expected/.git_keep/refs/tags/tag1 @@ -1 +1 @@ -9d577efe8be29a993269a0a737ca9c8321a2adc2 +07b4cadb018ce914237e3f31ee264c9555acc1d1 diff --git a/test/integration/tags/expected/.git_keep/refs/tags/tag3 b/test/integration/tags/expected/.git_keep/refs/tags/tag3 index 027894ce8..5fe2e1847 100644 --- a/test/integration/tags/expected/.git_keep/refs/tags/tag3 +++ b/test/integration/tags/expected/.git_keep/refs/tags/tag3 @@ -1 +1 @@ -9d577efe8be29a993269a0a737ca9c8321a2adc2 +07b4cadb018ce914237e3f31ee264c9555acc1d1 diff --git a/test/integration/tags/expected/.git_keep/refs/tags/tag4 b/test/integration/tags/expected/.git_keep/refs/tags/tag4 index 3ee82d743..34a63fe68 100644 --- a/test/integration/tags/expected/.git_keep/refs/tags/tag4 +++ b/test/integration/tags/expected/.git_keep/refs/tags/tag4 @@ -1 +1 @@ -1d2a22db54ab37b582dc5e3c4ca660f06cdb4692 +3e46e87f3ca37fad40d7dd6aca00223d7f49e424 diff --git a/test/integration/tags/recording.json b/test/integration/tags/recording.json index 06a606247..2388dddb9 100644 --- a/test/integration/tags/recording.json +++ b/test/integration/tags/recording.json @@ -1 +1 @@ -{"KeyEvents":[{"Timestamp":525,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1117,"Mod":0,"Key":256,"Ch":93},{"Timestamp":1245,"Mod":0,"Key":256,"Ch":93},{"Timestamp":1700,"Mod":0,"Key":256,"Ch":110},{"Timestamp":1908,"Mod":0,"Key":256,"Ch":116},{"Timestamp":1988,"Mod":0,"Key":256,"Ch":97},{"Timestamp":2100,"Mod":0,"Key":256,"Ch":103},{"Timestamp":2252,"Mod":0,"Key":256,"Ch":49},{"Timestamp":2486,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2796,"Mod":0,"Key":256,"Ch":110},{"Timestamp":3324,"Mod":0,"Key":256,"Ch":116},{"Timestamp":3412,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3532,"Mod":0,"Key":256,"Ch":103},{"Timestamp":3860,"Mod":0,"Key":256,"Ch":50},{"Timestamp":4036,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4340,"Mod":0,"Key":256,"Ch":110},{"Timestamp":4508,"Mod":0,"Key":256,"Ch":116},{"Timestamp":4564,"Mod":0,"Key":256,"Ch":97},{"Timestamp":4684,"Mod":0,"Key":256,"Ch":103},{"Timestamp":4908,"Mod":0,"Key":256,"Ch":51},{"Timestamp":5132,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5804,"Mod":0,"Key":257,"Ch":0},{"Timestamp":5996,"Mod":0,"Key":256,"Ch":100},{"Timestamp":6245,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6580,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6852,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7437,"Mod":0,"Key":256,"Ch":84},{"Timestamp":7749,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7820,"Mod":0,"Key":256,"Ch":97},{"Timestamp":7924,"Mod":0,"Key":256,"Ch":103},{"Timestamp":8483,"Mod":0,"Key":256,"Ch":52},{"Timestamp":8805,"Mod":0,"Key":13,"Ch":13},{"Timestamp":9357,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file +{"KeyEvents":[{"Timestamp":2009,"Mod":0,"Key":256,"Ch":52},{"Timestamp":2895,"Mod":0,"Key":256,"Ch":84},{"Timestamp":3851,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4233,"Mod":0,"Key":256,"Ch":116},{"Timestamp":4248,"Mod":0,"Key":256,"Ch":97},{"Timestamp":4436,"Mod":0,"Key":256,"Ch":103},{"Timestamp":4618,"Mod":0,"Key":256,"Ch":49},{"Timestamp":4751,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7572,"Mod":0,"Key":256,"Ch":84},{"Timestamp":7946,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8457,"Mod":0,"Key":256,"Ch":116},{"Timestamp":8491,"Mod":0,"Key":256,"Ch":97},{"Timestamp":8665,"Mod":0,"Key":256,"Ch":103},{"Timestamp":8884,"Mod":0,"Key":256,"Ch":51},{"Timestamp":9219,"Mod":0,"Key":13,"Ch":13},{"Timestamp":13521,"Mod":0,"Key":256,"Ch":84},{"Timestamp":15106,"Mod":0,"Key":13,"Ch":13},{"Timestamp":15579,"Mod":0,"Key":256,"Ch":116},{"Timestamp":15604,"Mod":0,"Key":256,"Ch":97},{"Timestamp":15858,"Mod":0,"Key":256,"Ch":103},{"Timestamp":16298,"Mod":0,"Key":256,"Ch":50},{"Timestamp":16600,"Mod":0,"Key":13,"Ch":13},{"Timestamp":19216,"Mod":0,"Key":258,"Ch":0},{"Timestamp":19753,"Mod":0,"Key":256,"Ch":84},{"Timestamp":20673,"Mod":0,"Key":13,"Ch":13},{"Timestamp":20918,"Mod":0,"Key":256,"Ch":116},{"Timestamp":20990,"Mod":0,"Key":256,"Ch":97},{"Timestamp":21124,"Mod":0,"Key":256,"Ch":103},{"Timestamp":21361,"Mod":0,"Key":256,"Ch":52},{"Timestamp":22463,"Mod":0,"Key":13,"Ch":13},{"Timestamp":23552,"Mod":0,"Key":256,"Ch":51},{"Timestamp":23909,"Mod":0,"Key":256,"Ch":93},{"Timestamp":24078,"Mod":0,"Key":256,"Ch":93},{"Timestamp":25170,"Mod":0,"Key":258,"Ch":0},{"Timestamp":25510,"Mod":0,"Key":256,"Ch":100},{"Timestamp":26316,"Mod":0,"Key":13,"Ch":13},{"Timestamp":28199,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":131,"Height":42}]} \ No newline at end of file diff --git a/test/integration/tags/setup.sh b/test/integration/tags/setup.sh index 75cd7a91b..e878a9ed5 100644 --- a/test/integration/tags/setup.sh +++ b/test/integration/tags/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/tags2/expected/.git_keep/HEAD b/test/integration/tags2/expected/.git_keep/HEAD index 12dd5a89a..1adee3887 100644 --- a/test/integration/tags2/expected/.git_keep/HEAD +++ b/test/integration/tags2/expected/.git_keep/HEAD @@ -1 +1 @@ -6ecb690d237f4196cb3ec881669c7e9e348939c0 +aefe968910ad84a58bfac631b56eb422968766fb diff --git a/test/integration/tags2/expected/.git_keep/ORIG_HEAD b/test/integration/tags2/expected/.git_keep/ORIG_HEAD index 2015962e4..1adee3887 100644 --- a/test/integration/tags2/expected/.git_keep/ORIG_HEAD +++ b/test/integration/tags2/expected/.git_keep/ORIG_HEAD @@ -1 +1 @@ -7d431518930a9fd2d08a09705645fe45d8507860 +aefe968910ad84a58bfac631b56eb422968766fb diff --git a/test/integration/tags2/expected/.git_keep/index b/test/integration/tags2/expected/.git_keep/index index 7fce3cbc0..6125b7d75 100644 Binary files a/test/integration/tags2/expected/.git_keep/index and b/test/integration/tags2/expected/.git_keep/index differ diff --git a/test/integration/tags2/expected/.git_keep/logs/HEAD b/test/integration/tags2/expected/.git_keep/logs/HEAD index 316fd2c81..0f088aab5 100644 --- a/test/integration/tags2/expected/.git_keep/logs/HEAD +++ b/test/integration/tags2/expected/.git_keep/logs/HEAD @@ -1,6 +1,5 @@ -0000000000000000000000000000000000000000 756089d39d55db6e11af06cb9c95f6d81f5d47ae CI 1617672030 +1000 commit (initial): file0 -756089d39d55db6e11af06cb9c95f6d81f5d47ae 6ecb690d237f4196cb3ec881669c7e9e348939c0 CI 1617672030 +1000 commit: file1 -6ecb690d237f4196cb3ec881669c7e9e348939c0 7d431518930a9fd2d08a09705645fe45d8507860 CI 1617672030 +1000 commit: file2 -7d431518930a9fd2d08a09705645fe45d8507860 dc46c2541c98c11cce0ec0a67f7d95f81686b1e4 CI 1617672030 +1000 commit: file4 -dc46c2541c98c11cce0ec0a67f7d95f81686b1e4 7d431518930a9fd2d08a09705645fe45d8507860 CI 1617672036 +1000 checkout: moving from master to one -7d431518930a9fd2d08a09705645fe45d8507860 6ecb690d237f4196cb3ec881669c7e9e348939c0 CI 1617672038 +1000 reset: moving to 6ecb690d237f4196cb3ec881669c7e9e348939c0 +0000000000000000000000000000000000000000 dcb11a2a23383bd5f4a1085bf3a64e73e5bd963a CI 1631477400 -0300 commit (initial): file0 +dcb11a2a23383bd5f4a1085bf3a64e73e5bd963a 56a89d6aebdfa4f2d717efc0d115656cc9b602e7 CI 1631477401 -0300 commit: file1 +56a89d6aebdfa4f2d717efc0d115656cc9b602e7 aefe968910ad84a58bfac631b56eb422968766fb CI 1631477401 -0300 commit: file2 +aefe968910ad84a58bfac631b56eb422968766fb 1750e9a4016c985ef97d002ae40ed554e3db6c87 CI 1631477401 -0300 commit: file4 +1750e9a4016c985ef97d002ae40ed554e3db6c87 aefe968910ad84a58bfac631b56eb422968766fb CI 1631477414 -0300 checkout: moving from master to one diff --git a/test/integration/tags2/expected/.git_keep/logs/refs/heads/master b/test/integration/tags2/expected/.git_keep/logs/refs/heads/master index e4a85e770..be76b12b4 100644 --- a/test/integration/tags2/expected/.git_keep/logs/refs/heads/master +++ b/test/integration/tags2/expected/.git_keep/logs/refs/heads/master @@ -1,4 +1,4 @@ -0000000000000000000000000000000000000000 756089d39d55db6e11af06cb9c95f6d81f5d47ae CI 1617672030 +1000 commit (initial): file0 -756089d39d55db6e11af06cb9c95f6d81f5d47ae 6ecb690d237f4196cb3ec881669c7e9e348939c0 CI 1617672030 +1000 commit: file1 -6ecb690d237f4196cb3ec881669c7e9e348939c0 7d431518930a9fd2d08a09705645fe45d8507860 CI 1617672030 +1000 commit: file2 -7d431518930a9fd2d08a09705645fe45d8507860 dc46c2541c98c11cce0ec0a67f7d95f81686b1e4 CI 1617672030 +1000 commit: file4 +0000000000000000000000000000000000000000 dcb11a2a23383bd5f4a1085bf3a64e73e5bd963a CI 1631477400 -0300 commit (initial): file0 +dcb11a2a23383bd5f4a1085bf3a64e73e5bd963a 56a89d6aebdfa4f2d717efc0d115656cc9b602e7 CI 1631477401 -0300 commit: file1 +56a89d6aebdfa4f2d717efc0d115656cc9b602e7 aefe968910ad84a58bfac631b56eb422968766fb CI 1631477401 -0300 commit: file2 +aefe968910ad84a58bfac631b56eb422968766fb 1750e9a4016c985ef97d002ae40ed554e3db6c87 CI 1631477401 -0300 commit: file4 diff --git a/test/integration/tags2/expected/.git_keep/objects/17/50e9a4016c985ef97d002ae40ed554e3db6c87 b/test/integration/tags2/expected/.git_keep/objects/17/50e9a4016c985ef97d002ae40ed554e3db6c87 new file mode 100644 index 000000000..9a8a3d3dd Binary files /dev/null and b/test/integration/tags2/expected/.git_keep/objects/17/50e9a4016c985ef97d002ae40ed554e3db6c87 differ diff --git a/test/integration/tags2/expected/.git_keep/objects/56/a89d6aebdfa4f2d717efc0d115656cc9b602e7 b/test/integration/tags2/expected/.git_keep/objects/56/a89d6aebdfa4f2d717efc0d115656cc9b602e7 new file mode 100644 index 000000000..848900716 Binary files /dev/null and b/test/integration/tags2/expected/.git_keep/objects/56/a89d6aebdfa4f2d717efc0d115656cc9b602e7 differ diff --git a/test/integration/tags2/expected/.git_keep/objects/6e/cb690d237f4196cb3ec881669c7e9e348939c0 b/test/integration/tags2/expected/.git_keep/objects/6e/cb690d237f4196cb3ec881669c7e9e348939c0 deleted file mode 100644 index d958fcc3a..000000000 Binary files a/test/integration/tags2/expected/.git_keep/objects/6e/cb690d237f4196cb3ec881669c7e9e348939c0 and /dev/null differ diff --git a/test/integration/tags2/expected/.git_keep/objects/75/6089d39d55db6e11af06cb9c95f6d81f5d47ae b/test/integration/tags2/expected/.git_keep/objects/75/6089d39d55db6e11af06cb9c95f6d81f5d47ae deleted file mode 100644 index 26d849eb5..000000000 Binary files a/test/integration/tags2/expected/.git_keep/objects/75/6089d39d55db6e11af06cb9c95f6d81f5d47ae and /dev/null differ diff --git a/test/integration/tags2/expected/.git_keep/objects/7d/431518930a9fd2d08a09705645fe45d8507860 b/test/integration/tags2/expected/.git_keep/objects/7d/431518930a9fd2d08a09705645fe45d8507860 deleted file mode 100644 index 8a7f2854b..000000000 Binary files a/test/integration/tags2/expected/.git_keep/objects/7d/431518930a9fd2d08a09705645fe45d8507860 and /dev/null differ diff --git a/test/integration/tags2/expected/.git_keep/objects/ae/fe968910ad84a58bfac631b56eb422968766fb b/test/integration/tags2/expected/.git_keep/objects/ae/fe968910ad84a58bfac631b56eb422968766fb new file mode 100644 index 000000000..58c8a404a Binary files /dev/null and b/test/integration/tags2/expected/.git_keep/objects/ae/fe968910ad84a58bfac631b56eb422968766fb differ diff --git a/test/integration/tags2/expected/.git_keep/objects/dc/46c2541c98c11cce0ec0a67f7d95f81686b1e4 b/test/integration/tags2/expected/.git_keep/objects/dc/46c2541c98c11cce0ec0a67f7d95f81686b1e4 deleted file mode 100644 index c8fd68546..000000000 --- a/test/integration/tags2/expected/.git_keep/objects/dc/46c2541c98c11cce0ec0a67f7d95f81686b1e4 +++ /dev/null @@ -1,4 +0,0 @@ -xM -0@a9E$3"BW=LR"x|{o歵[UK |U%DaOrX#c$ -$qUb -`_aЯ}ۼŀ1D'7uY7 \ No newline at end of file diff --git a/test/integration/tags2/expected/.git_keep/objects/dc/b11a2a23383bd5f4a1085bf3a64e73e5bd963a b/test/integration/tags2/expected/.git_keep/objects/dc/b11a2a23383bd5f4a1085bf3a64e73e5bd963a new file mode 100644 index 000000000..de87683d5 Binary files /dev/null and b/test/integration/tags2/expected/.git_keep/objects/dc/b11a2a23383bd5f4a1085bf3a64e73e5bd963a differ diff --git a/test/integration/tags2/expected/.git_keep/refs/heads/master b/test/integration/tags2/expected/.git_keep/refs/heads/master index efe1467dc..dae416f06 100644 --- a/test/integration/tags2/expected/.git_keep/refs/heads/master +++ b/test/integration/tags2/expected/.git_keep/refs/heads/master @@ -1 +1 @@ -dc46c2541c98c11cce0ec0a67f7d95f81686b1e4 +1750e9a4016c985ef97d002ae40ed554e3db6c87 diff --git a/test/integration/tags2/expected/.git_keep/refs/tags/one b/test/integration/tags2/expected/.git_keep/refs/tags/one index 2015962e4..1adee3887 100644 --- a/test/integration/tags2/expected/.git_keep/refs/tags/one +++ b/test/integration/tags2/expected/.git_keep/refs/tags/one @@ -1 +1 @@ -7d431518930a9fd2d08a09705645fe45d8507860 +aefe968910ad84a58bfac631b56eb422968766fb diff --git a/test/integration/tags2/expected/.git_keep/refs/tags/two b/test/integration/tags2/expected/.git_keep/refs/tags/two index 12dd5a89a..079680fa6 100644 --- a/test/integration/tags2/expected/.git_keep/refs/tags/two +++ b/test/integration/tags2/expected/.git_keep/refs/tags/two @@ -1 +1 @@ -6ecb690d237f4196cb3ec881669c7e9e348939c0 +56a89d6aebdfa4f2d717efc0d115656cc9b602e7 diff --git a/test/integration/tags2/recording.json b/test/integration/tags2/recording.json index b2ce8c043..49b66b6ea 100644 --- a/test/integration/tags2/recording.json +++ b/test/integration/tags2/recording.json @@ -1 +1 @@ -{"KeyEvents":[{"Timestamp":534,"Mod":0,"Key":259,"Ch":0},{"Timestamp":791,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1128,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1759,"Mod":0,"Key":256,"Ch":84},{"Timestamp":2062,"Mod":0,"Key":256,"Ch":111},{"Timestamp":2094,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2182,"Mod":0,"Key":256,"Ch":101},{"Timestamp":2368,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2711,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3006,"Mod":0,"Key":256,"Ch":84},{"Timestamp":3230,"Mod":0,"Key":256,"Ch":116},{"Timestamp":3367,"Mod":0,"Key":256,"Ch":119},{"Timestamp":3454,"Mod":0,"Key":256,"Ch":111},{"Timestamp":3759,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4127,"Mod":0,"Key":260,"Ch":0},{"Timestamp":4646,"Mod":0,"Key":256,"Ch":93},{"Timestamp":4815,"Mod":0,"Key":256,"Ch":93},{"Timestamp":5710,"Mod":0,"Key":256,"Ch":32},{"Timestamp":6511,"Mod":0,"Key":259,"Ch":0},{"Timestamp":6935,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7645,"Mod":0,"Key":256,"Ch":103},{"Timestamp":8079,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8534,"Mod":0,"Key":260,"Ch":0},{"Timestamp":8806,"Mod":0,"Key":260,"Ch":0},{"Timestamp":9422,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file +{"KeyEvents":[{"Timestamp":325,"Mod":0,"Key":256,"Ch":52},{"Timestamp":688,"Mod":0,"Key":256,"Ch":106},{"Timestamp":1081,"Mod":0,"Key":256,"Ch":84},{"Timestamp":1671,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2002,"Mod":0,"Key":256,"Ch":111},{"Timestamp":2100,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2164,"Mod":0,"Key":256,"Ch":101},{"Timestamp":2243,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2850,"Mod":0,"Key":256,"Ch":106},{"Timestamp":3524,"Mod":0,"Key":256,"Ch":84},{"Timestamp":4057,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4418,"Mod":0,"Key":256,"Ch":116},{"Timestamp":4482,"Mod":0,"Key":256,"Ch":119},{"Timestamp":4509,"Mod":0,"Key":256,"Ch":111},{"Timestamp":4836,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7822,"Mod":0,"Key":256,"Ch":107},{"Timestamp":8408,"Mod":0,"Key":256,"Ch":103},{"Timestamp":10049,"Mod":0,"Key":27,"Ch":0},{"Timestamp":10619,"Mod":0,"Key":256,"Ch":51},{"Timestamp":11391,"Mod":0,"Key":256,"Ch":93},{"Timestamp":11573,"Mod":0,"Key":256,"Ch":93},{"Timestamp":13390,"Mod":0,"Key":256,"Ch":32},{"Timestamp":16819,"Mod":0,"Key":256,"Ch":93},{"Timestamp":16985,"Mod":0,"Key":256,"Ch":93},{"Timestamp":17905,"Mod":0,"Key":256,"Ch":103},{"Timestamp":20817,"Mod":0,"Key":13,"Ch":13},{"Timestamp":25557,"Mod":0,"Key":256,"Ch":50},{"Timestamp":25951,"Mod":0,"Key":256,"Ch":49},{"Timestamp":26265,"Mod":0,"Key":256,"Ch":51},{"Timestamp":26990,"Mod":0,"Key":256,"Ch":91},{"Timestamp":27245,"Mod":0,"Key":256,"Ch":91},{"Timestamp":28363,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":131,"Height":42}]} \ No newline at end of file diff --git a/test/integration/tags2/setup.sh b/test/integration/tags2/setup.sh index d11e86c0a..8806332fd 100644 --- a/test/integration/tags2/setup.sh +++ b/test/integration/tags2/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/tags3/expected/.git_keep/index b/test/integration/tags3/expected/.git_keep/index index 1960aed9e..7bdcb3c68 100644 Binary files a/test/integration/tags3/expected/.git_keep/index and b/test/integration/tags3/expected/.git_keep/index differ diff --git a/test/integration/tags3/expected/.git_keep/logs/HEAD b/test/integration/tags3/expected/.git_keep/logs/HEAD index e39103406..51e82779a 100644 --- a/test/integration/tags3/expected/.git_keep/logs/HEAD +++ b/test/integration/tags3/expected/.git_keep/logs/HEAD @@ -1,5 +1,5 @@ -0000000000000000000000000000000000000000 54bd2b22db1e849928a333ebfe8f3fba1159adde CI 1617672309 +1000 commit (initial): file0 -54bd2b22db1e849928a333ebfe8f3fba1159adde b81aa63f3545be4f565c8a64a943517c8da94a6e CI 1617672309 +1000 commit: file1 -b81aa63f3545be4f565c8a64a943517c8da94a6e 29ff87a318d242acab116d17e421480b5d8002cd CI 1617672309 +1000 commit: file2 -29ff87a318d242acab116d17e421480b5d8002cd ecf6d101974303948e79d673ccff18c592f89650 CI 1617672309 +1000 commit: file4 -ecf6d101974303948e79d673ccff18c592f89650 b81aa63f3545be4f565c8a64a943517c8da94a6e CI 1617672318 +1000 checkout: moving from master to test +0000000000000000000000000000000000000000 46b4990797fac897fb135dd639a4cad3b0269f2d CI 1631477922 -0300 commit (initial): file0 +46b4990797fac897fb135dd639a4cad3b0269f2d 88d7a40883abd57297127b3777a2a7ec3696c33a CI 1631477922 -0300 commit: file1 +88d7a40883abd57297127b3777a2a7ec3696c33a 08c28e4e15f3de3b024524894d9235dfcdb48c19 CI 1631477922 -0300 commit: file2 +08c28e4e15f3de3b024524894d9235dfcdb48c19 2515eabac6791725f4a3326676a1491f09664afc CI 1631477922 -0300 commit: file4 +2515eabac6791725f4a3326676a1491f09664afc 88d7a40883abd57297127b3777a2a7ec3696c33a CI 1631477929 -0300 checkout: moving from master to test diff --git a/test/integration/tags3/expected/.git_keep/logs/refs/heads/master b/test/integration/tags3/expected/.git_keep/logs/refs/heads/master index 11174511b..313c3bbc0 100644 --- a/test/integration/tags3/expected/.git_keep/logs/refs/heads/master +++ b/test/integration/tags3/expected/.git_keep/logs/refs/heads/master @@ -1,4 +1,4 @@ -0000000000000000000000000000000000000000 54bd2b22db1e849928a333ebfe8f3fba1159adde CI 1617672309 +1000 commit (initial): file0 -54bd2b22db1e849928a333ebfe8f3fba1159adde b81aa63f3545be4f565c8a64a943517c8da94a6e CI 1617672309 +1000 commit: file1 -b81aa63f3545be4f565c8a64a943517c8da94a6e 29ff87a318d242acab116d17e421480b5d8002cd CI 1617672309 +1000 commit: file2 -29ff87a318d242acab116d17e421480b5d8002cd ecf6d101974303948e79d673ccff18c592f89650 CI 1617672309 +1000 commit: file4 +0000000000000000000000000000000000000000 46b4990797fac897fb135dd639a4cad3b0269f2d CI 1631477922 -0300 commit (initial): file0 +46b4990797fac897fb135dd639a4cad3b0269f2d 88d7a40883abd57297127b3777a2a7ec3696c33a CI 1631477922 -0300 commit: file1 +88d7a40883abd57297127b3777a2a7ec3696c33a 08c28e4e15f3de3b024524894d9235dfcdb48c19 CI 1631477922 -0300 commit: file2 +08c28e4e15f3de3b024524894d9235dfcdb48c19 2515eabac6791725f4a3326676a1491f09664afc CI 1631477922 -0300 commit: file4 diff --git a/test/integration/tags3/expected/.git_keep/logs/refs/heads/test b/test/integration/tags3/expected/.git_keep/logs/refs/heads/test index ba94eca60..6c2eada73 100644 --- a/test/integration/tags3/expected/.git_keep/logs/refs/heads/test +++ b/test/integration/tags3/expected/.git_keep/logs/refs/heads/test @@ -1 +1 @@ -0000000000000000000000000000000000000000 b81aa63f3545be4f565c8a64a943517c8da94a6e CI 1617672318 +1000 branch: Created from b81aa63f3545be4f565c8a64a943517c8da94a6e +0000000000000000000000000000000000000000 88d7a40883abd57297127b3777a2a7ec3696c33a CI 1631477929 -0300 branch: Created from 88d7a40883abd57297127b3777a2a7ec3696c33a diff --git a/test/integration/tags3/expected/.git_keep/objects/08/c28e4e15f3de3b024524894d9235dfcdb48c19 b/test/integration/tags3/expected/.git_keep/objects/08/c28e4e15f3de3b024524894d9235dfcdb48c19 new file mode 100644 index 000000000..76468e806 Binary files /dev/null and b/test/integration/tags3/expected/.git_keep/objects/08/c28e4e15f3de3b024524894d9235dfcdb48c19 differ diff --git a/test/integration/tags3/expected/.git_keep/objects/25/15eabac6791725f4a3326676a1491f09664afc b/test/integration/tags3/expected/.git_keep/objects/25/15eabac6791725f4a3326676a1491f09664afc new file mode 100644 index 000000000..79c619559 Binary files /dev/null and b/test/integration/tags3/expected/.git_keep/objects/25/15eabac6791725f4a3326676a1491f09664afc differ diff --git a/test/integration/tags3/expected/.git_keep/objects/29/ff87a318d242acab116d17e421480b5d8002cd b/test/integration/tags3/expected/.git_keep/objects/29/ff87a318d242acab116d17e421480b5d8002cd deleted file mode 100644 index fbf99e429..000000000 Binary files a/test/integration/tags3/expected/.git_keep/objects/29/ff87a318d242acab116d17e421480b5d8002cd and /dev/null differ diff --git a/test/integration/tags3/expected/.git_keep/objects/46/b4990797fac897fb135dd639a4cad3b0269f2d b/test/integration/tags3/expected/.git_keep/objects/46/b4990797fac897fb135dd639a4cad3b0269f2d new file mode 100644 index 000000000..f51f91225 Binary files /dev/null and b/test/integration/tags3/expected/.git_keep/objects/46/b4990797fac897fb135dd639a4cad3b0269f2d differ diff --git a/test/integration/tags3/expected/.git_keep/objects/88/d7a40883abd57297127b3777a2a7ec3696c33a b/test/integration/tags3/expected/.git_keep/objects/88/d7a40883abd57297127b3777a2a7ec3696c33a new file mode 100644 index 000000000..c4f33543e Binary files /dev/null and b/test/integration/tags3/expected/.git_keep/objects/88/d7a40883abd57297127b3777a2a7ec3696c33a differ diff --git a/test/integration/tags3/expected/.git_keep/objects/b8/1aa63f3545be4f565c8a64a943517c8da94a6e b/test/integration/tags3/expected/.git_keep/objects/b8/1aa63f3545be4f565c8a64a943517c8da94a6e deleted file mode 100644 index 28c2977cf..000000000 Binary files a/test/integration/tags3/expected/.git_keep/objects/b8/1aa63f3545be4f565c8a64a943517c8da94a6e and /dev/null differ diff --git a/test/integration/tags3/expected/.git_keep/objects/ec/f6d101974303948e79d673ccff18c592f89650 b/test/integration/tags3/expected/.git_keep/objects/ec/f6d101974303948e79d673ccff18c592f89650 deleted file mode 100644 index 108b94b62..000000000 --- a/test/integration/tags3/expected/.git_keep/objects/ec/f6d101974303948e79d673ccff18c592f89650 +++ /dev/null @@ -1,2 +0,0 @@ -xM -0@a9EL& "tcL Z[Jo-^u3]jC`a.&N@Cf0Is4rk)TILbT& J5hԯc +99Onڼ(8 \ No newline at end of file diff --git a/test/integration/tags3/expected/.git_keep/refs/heads/master b/test/integration/tags3/expected/.git_keep/refs/heads/master index 3e10d4f18..0bcc4f3ee 100644 --- a/test/integration/tags3/expected/.git_keep/refs/heads/master +++ b/test/integration/tags3/expected/.git_keep/refs/heads/master @@ -1 +1 @@ -ecf6d101974303948e79d673ccff18c592f89650 +2515eabac6791725f4a3326676a1491f09664afc diff --git a/test/integration/tags3/expected/.git_keep/refs/heads/test b/test/integration/tags3/expected/.git_keep/refs/heads/test index 9f242181c..6be0d41dc 100644 --- a/test/integration/tags3/expected/.git_keep/refs/heads/test +++ b/test/integration/tags3/expected/.git_keep/refs/heads/test @@ -1 +1 @@ -b81aa63f3545be4f565c8a64a943517c8da94a6e +88d7a40883abd57297127b3777a2a7ec3696c33a diff --git a/test/integration/tags3/expected/.git_keep/refs/tags/one b/test/integration/tags3/expected/.git_keep/refs/tags/one index 59d657fcd..d62cf9b9d 100644 --- a/test/integration/tags3/expected/.git_keep/refs/tags/one +++ b/test/integration/tags3/expected/.git_keep/refs/tags/one @@ -1 +1 @@ -29ff87a318d242acab116d17e421480b5d8002cd +08c28e4e15f3de3b024524894d9235dfcdb48c19 diff --git a/test/integration/tags3/recording.json b/test/integration/tags3/recording.json index 87c6bdd11..483f51ab7 100644 --- a/test/integration/tags3/recording.json +++ b/test/integration/tags3/recording.json @@ -1 +1 @@ -{"KeyEvents":[{"Timestamp":649,"Mod":0,"Key":259,"Ch":0},{"Timestamp":834,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1065,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1817,"Mod":0,"Key":256,"Ch":84},{"Timestamp":2609,"Mod":0,"Key":256,"Ch":111},{"Timestamp":2689,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2833,"Mod":0,"Key":256,"Ch":101},{"Timestamp":3105,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3578,"Mod":0,"Key":260,"Ch":0},{"Timestamp":4217,"Mod":0,"Key":256,"Ch":93},{"Timestamp":4529,"Mod":0,"Key":256,"Ch":93},{"Timestamp":6161,"Mod":0,"Key":13,"Ch":13},{"Timestamp":6985,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7898,"Mod":0,"Key":256,"Ch":110},{"Timestamp":8338,"Mod":0,"Key":256,"Ch":116},{"Timestamp":8385,"Mod":0,"Key":256,"Ch":101},{"Timestamp":8537,"Mod":0,"Key":256,"Ch":115},{"Timestamp":8577,"Mod":0,"Key":256,"Ch":116},{"Timestamp":8857,"Mod":0,"Key":13,"Ch":13},{"Timestamp":9849,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file +{"KeyEvents":[{"Timestamp":549,"Mod":0,"Key":256,"Ch":52},{"Timestamp":1242,"Mod":0,"Key":256,"Ch":106},{"Timestamp":1503,"Mod":0,"Key":256,"Ch":84},{"Timestamp":1820,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2800,"Mod":0,"Key":256,"Ch":111},{"Timestamp":2874,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2957,"Mod":0,"Key":256,"Ch":101},{"Timestamp":3448,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3962,"Mod":0,"Key":256,"Ch":51},{"Timestamp":4279,"Mod":0,"Key":256,"Ch":93},{"Timestamp":4429,"Mod":0,"Key":256,"Ch":93},{"Timestamp":4896,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5541,"Mod":0,"Key":256,"Ch":106},{"Timestamp":6284,"Mod":0,"Key":256,"Ch":110},{"Timestamp":6690,"Mod":0,"Key":256,"Ch":116},{"Timestamp":6755,"Mod":0,"Key":256,"Ch":101},{"Timestamp":6826,"Mod":0,"Key":256,"Ch":115},{"Timestamp":6904,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7213,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8557,"Mod":0,"Key":256,"Ch":106},{"Timestamp":8743,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":211,"Height":42}]} \ No newline at end of file diff --git a/test/integration/tags3/setup.sh b/test/integration/tags3/setup.sh index d11e86c0a..8806332fd 100644 --- a/test/integration/tags3/setup.sh +++ b/test/integration/tags3/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/tags4/expected/.git_keep/COMMIT_EDITMSG b/test/integration/tags4/expected/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..e2129701f --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +file1 diff --git a/test/integration/tags4/expected/.git_keep/FETCH_HEAD b/test/integration/tags4/expected/.git_keep/FETCH_HEAD new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/tags4/expected/.git_keep/HEAD b/test/integration/tags4/expected/.git_keep/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/integration/tags4/expected/.git_keep/config b/test/integration/tags4/expected/.git_keep/config new file mode 100644 index 000000000..596ebaeb3 --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true +[user] + email = CI@example.com + name = CI diff --git a/test/integration/tags4/expected/.git_keep/description b/test/integration/tags4/expected/.git_keep/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/integration/tags4/expected/.git_keep/index b/test/integration/tags4/expected/.git_keep/index new file mode 100644 index 000000000..48e219dd6 Binary files /dev/null and b/test/integration/tags4/expected/.git_keep/index differ diff --git a/test/integration/tags4/expected/.git_keep/info/exclude b/test/integration/tags4/expected/.git_keep/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/test/integration/tags4/expected/.git_keep/logs/HEAD b/test/integration/tags4/expected/.git_keep/logs/HEAD new file mode 100644 index 000000000..7544ecd9d --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 f04e94a59e6159acf554fc1268742df10fe6b0d3 CI 1631476697 -0300 commit (initial): file0 +f04e94a59e6159acf554fc1268742df10fe6b0d3 3a64c1649510c0dcaca3815291e3d43980f1bb99 CI 1631476697 -0300 commit: file1 diff --git a/test/integration/tags4/expected/.git_keep/logs/refs/heads/master b/test/integration/tags4/expected/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..7544ecd9d --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 f04e94a59e6159acf554fc1268742df10fe6b0d3 CI 1631476697 -0300 commit (initial): file0 +f04e94a59e6159acf554fc1268742df10fe6b0d3 3a64c1649510c0dcaca3815291e3d43980f1bb99 CI 1631476697 -0300 commit: file1 diff --git a/test/integration/tags4/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/tags4/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 new file mode 100644 index 000000000..79fcadf67 Binary files /dev/null and b/test/integration/tags4/expected/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 differ diff --git a/test/integration/tags4/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/tags4/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da new file mode 100644 index 000000000..06c9cb73d Binary files /dev/null and b/test/integration/tags4/expected/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da differ diff --git a/test/integration/tags4/expected/.git_keep/objects/3a/64c1649510c0dcaca3815291e3d43980f1bb99 b/test/integration/tags4/expected/.git_keep/objects/3a/64c1649510c0dcaca3815291e3d43980f1bb99 new file mode 100644 index 000000000..25a275337 Binary files /dev/null and b/test/integration/tags4/expected/.git_keep/objects/3a/64c1649510c0dcaca3815291e3d43980f1bb99 differ diff --git a/test/integration/tags4/expected/.git_keep/objects/56/18f31c7550111a878fb63f6079e8462ae94c42 b/test/integration/tags4/expected/.git_keep/objects/56/18f31c7550111a878fb63f6079e8462ae94c42 new file mode 100644 index 000000000..ffbba8d79 --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/objects/56/18f31c7550111a878fb63f6079e8462ae94c42 @@ -0,0 +1,2 @@ +xA +0E]&M1&1(6 -3P7T`;!bO(8 ہ.8RϪ K)jj6\o.KMyK.Nh?+h'i \ No newline at end of file diff --git a/test/integration/tags4/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/tags4/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 new file mode 100644 index 000000000..285df3e5f Binary files /dev/null and b/test/integration/tags4/expected/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 differ diff --git a/test/integration/tags4/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/tags4/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 new file mode 100644 index 000000000..2e9066287 --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 @@ -0,0 +1,2 @@ +x+)JMU03c040031QHI5`ֶww.hT[H + yW5Ɨ(| ^-W(x9 \ No newline at end of file diff --git a/test/integration/tags4/expected/.git_keep/objects/db/03048dbacea165536b49c030c9aaca108cc571 b/test/integration/tags4/expected/.git_keep/objects/db/03048dbacea165536b49c030c9aaca108cc571 new file mode 100644 index 000000000..59aa04b0e Binary files /dev/null and b/test/integration/tags4/expected/.git_keep/objects/db/03048dbacea165536b49c030c9aaca108cc571 differ diff --git a/test/integration/tags4/expected/.git_keep/objects/f0/4e94a59e6159acf554fc1268742df10fe6b0d3 b/test/integration/tags4/expected/.git_keep/objects/f0/4e94a59e6159acf554fc1268742df10fe6b0d3 new file mode 100644 index 000000000..e15c21c24 Binary files /dev/null and b/test/integration/tags4/expected/.git_keep/objects/f0/4e94a59e6159acf554fc1268742df10fe6b0d3 differ diff --git a/test/integration/tags4/expected/.git_keep/packed-refs b/test/integration/tags4/expected/.git_keep/packed-refs new file mode 100644 index 000000000..250f18738 --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/packed-refs @@ -0,0 +1 @@ +# pack-refs with: peeled fully-peeled sorted diff --git a/test/integration/tags4/expected/.git_keep/refs/heads/master b/test/integration/tags4/expected/.git_keep/refs/heads/master new file mode 100644 index 000000000..2a642862a --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/refs/heads/master @@ -0,0 +1 @@ +3a64c1649510c0dcaca3815291e3d43980f1bb99 diff --git a/test/integration/tags4/expected/.git_keep/refs/tags/atag2 b/test/integration/tags4/expected/.git_keep/refs/tags/atag2 new file mode 100644 index 000000000..131e3c98f --- /dev/null +++ b/test/integration/tags4/expected/.git_keep/refs/tags/atag2 @@ -0,0 +1 @@ +db03048dbacea165536b49c030c9aaca108cc571 diff --git a/test/integration/tags4/expected/file0 b/test/integration/tags4/expected/file0 new file mode 100644 index 000000000..38143ad4a --- /dev/null +++ b/test/integration/tags4/expected/file0 @@ -0,0 +1 @@ +test0 diff --git a/test/integration/tags4/expected/file1 b/test/integration/tags4/expected/file1 new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/integration/tags4/expected/file1 @@ -0,0 +1 @@ +test1 diff --git a/test/integration/tags4/recording.json b/test/integration/tags4/recording.json new file mode 100644 index 000000000..4c7e689c5 --- /dev/null +++ b/test/integration/tags4/recording.json @@ -0,0 +1 @@ +{"KeyEvents":[{"Timestamp":928,"Mod":0,"Key":256,"Ch":52},{"Timestamp":1864,"Mod":0,"Key":256,"Ch":84},{"Timestamp":2752,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2936,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5085,"Mod":0,"Key":256,"Ch":97},{"Timestamp":5306,"Mod":0,"Key":256,"Ch":116},{"Timestamp":5392,"Mod":0,"Key":256,"Ch":97},{"Timestamp":5526,"Mod":0,"Key":256,"Ch":103},{"Timestamp":5754,"Mod":0,"Key":256,"Ch":49},{"Timestamp":5983,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7385,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7489,"Mod":0,"Key":256,"Ch":97},{"Timestamp":7808,"Mod":0,"Key":256,"Ch":103},{"Timestamp":7911,"Mod":0,"Key":256,"Ch":32},{"Timestamp":8171,"Mod":0,"Key":256,"Ch":109},{"Timestamp":8240,"Mod":0,"Key":256,"Ch":101},{"Timestamp":8404,"Mod":0,"Key":256,"Ch":115},{"Timestamp":8572,"Mod":0,"Key":256,"Ch":115},{"Timestamp":8656,"Mod":0,"Key":256,"Ch":97},{"Timestamp":8749,"Mod":0,"Key":256,"Ch":103},{"Timestamp":8832,"Mod":0,"Key":256,"Ch":101},{"Timestamp":10162,"Mod":0,"Key":256,"Ch":33},{"Timestamp":10432,"Mod":0,"Key":13,"Ch":13},{"Timestamp":11766,"Mod":0,"Key":256,"Ch":106},{"Timestamp":13092,"Mod":0,"Key":256,"Ch":84},{"Timestamp":14177,"Mod":0,"Key":258,"Ch":0},{"Timestamp":14482,"Mod":0,"Key":13,"Ch":13},{"Timestamp":15660,"Mod":0,"Key":256,"Ch":97},{"Timestamp":15783,"Mod":0,"Key":256,"Ch":116},{"Timestamp":15857,"Mod":0,"Key":256,"Ch":97},{"Timestamp":15991,"Mod":0,"Key":256,"Ch":103},{"Timestamp":17243,"Mod":0,"Key":256,"Ch":50},{"Timestamp":17536,"Mod":0,"Key":13,"Ch":13},{"Timestamp":19574,"Mod":0,"Key":256,"Ch":115},{"Timestamp":19641,"Mod":0,"Key":256,"Ch":101},{"Timestamp":19834,"Mod":0,"Key":256,"Ch":99},{"Timestamp":19863,"Mod":0,"Key":256,"Ch":111},{"Timestamp":19958,"Mod":0,"Key":256,"Ch":110},{"Timestamp":20013,"Mod":0,"Key":256,"Ch":100},{"Timestamp":20092,"Mod":0,"Key":256,"Ch":32},{"Timestamp":20181,"Mod":0,"Key":256,"Ch":109},{"Timestamp":20265,"Mod":0,"Key":256,"Ch":101},{"Timestamp":20436,"Mod":0,"Key":256,"Ch":115},{"Timestamp":20590,"Mod":0,"Key":256,"Ch":115},{"Timestamp":20675,"Mod":0,"Key":256,"Ch":97},{"Timestamp":20706,"Mod":0,"Key":256,"Ch":103},{"Timestamp":20805,"Mod":0,"Key":256,"Ch":101},{"Timestamp":21237,"Mod":0,"Key":13,"Ch":13},{"Timestamp":22701,"Mod":0,"Key":256,"Ch":51},{"Timestamp":23066,"Mod":0,"Key":256,"Ch":93},{"Timestamp":23235,"Mod":0,"Key":256,"Ch":93},{"Timestamp":24842,"Mod":0,"Key":256,"Ch":106},{"Timestamp":25574,"Mod":0,"Key":256,"Ch":100},{"Timestamp":26166,"Mod":0,"Key":13,"Ch":13},{"Timestamp":27722,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":211,"Height":42}]} \ No newline at end of file diff --git a/test/integration/tags4/setup.sh b/test/integration/tags4/setup.sh new file mode 100644 index 000000000..e878a9ed5 --- /dev/null +++ b/test/integration/tags4/setup.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -e + +cd $1 + +git init + +git config user.email "CI@example.com" +git config user.name "CI" + +echo test0 > file0 +git add . +git commit -am file0 + +echo test1 > file1 +git add . +git commit -am file1 diff --git a/test/integration/tags4/test.json b/test/integration/tags4/test.json new file mode 100644 index 000000000..26a2080c6 --- /dev/null +++ b/test/integration/tags4/test.json @@ -0,0 +1 @@ +{ "description": "annotated tag CRUD", "speed": 5 } diff --git a/test/integration/undo/setup.sh b/test/integration/undo/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/undo/setup.sh +++ b/test/integration/undo/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/integration/undo2/setup.sh b/test/integration/undo2/setup.sh index 327d4594f..4cd444a1f 100644 --- a/test/integration/undo2/setup.sh +++ b/test/integration/undo2/setup.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -e + cd $1 git init diff --git a/test/lazyintegration/main.go b/test/lazyintegration/main.go index dea8bb77e..2d8e4a4a9 100644 --- a/test/lazyintegration/main.go +++ b/test/lazyintegration/main.go @@ -106,7 +106,21 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true RECORD_EVENTS=true go run test/runner/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=record go run test/runner/main.go %s", currentTest.Name)) + app.runSubprocess(cmd) + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", nil, 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=sandbox go run test/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -128,13 +142,27 @@ func main() { log.Panicln(err) } - if err := g.SetKeybinding("list", nil, 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + if err := g.SetKeybinding("list", nil, 'u', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { currentTest := app.getCurrentTest() if currentTest == nil { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true UPDATE_SNAPSHOTS=true go run test/runner/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=updateSnapshot go run test/runner/main.go %s", currentTest.Name)) + app.runSubprocess(cmd) + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", nil, 't', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true SPEED=1 go run test/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -268,13 +296,11 @@ func main() { err = g.MainLoop() g.Close() - if err != nil { - switch err { - case gocui.ErrQuit: - return - default: - log.Panicln(err) - } + switch err { + case gocui.ErrQuit: + return + default: + log.Panicln(err) } } @@ -349,7 +375,7 @@ func (app *App) layout(g *gocui.Gui) error { keybindingsView.Title = "Keybindings" keybindingsView.Wrap = true keybindingsView.FgColor = gocui.ColorDefault - fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, s: run test and update snapshots, r: record test, o: open test config, n: duplicate test, m: rename test, d: delete test") + fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, u: run test and update snapshots, r: record test, s: sandbox, o: open test config, n: duplicate test, m: rename test, d: delete test, t: run test at original speed") } editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight) diff --git a/test/runner/main.go b/test/runner/main.go index b122568fe..af6195cbc 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -11,19 +11,18 @@ import ( "github.com/stretchr/testify/assert" ) +// see https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.md // This file can be invoked directly, but you might find it easier to go through -// test/lazyintegration/main.go, which provides a convenient gui wrapper to integration -// tests. +// test/lazyintegration/main.go, which provides a convenient gui wrapper to integration tests. // // If invoked directly, you can specify a test by passing it as the first argument. -// You can also specify that you want to record a test by passing RECORD_EVENTS=true +// You can also specify that you want to record a test by passing MODE=record // as an env var. func main() { - record := os.Getenv("RECORD_EVENTS") != "" - updateSnapshots := record || os.Getenv("UPDATE_SNAPSHOTS") != "" + mode := integration.GetModeFromEnv() speedEnv := os.Getenv("SPEED") - includeSkipped := os.Getenv("INCLUDE_SKIPPED") != "" + includeSkipped := os.Getenv("INCLUDE_SKIPPED") == "true" selectedTestName := os.Args[1] err := integration.RunTests( @@ -37,8 +36,7 @@ func main() { log.Print(err.Error()) } }, - updateSnapshots, - record, + mode, speedEnv, func(_t *testing.T, expected string, actual string, prefix string) { assert.Equal(MockTestingT{}, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) diff --git a/vendor/github.com/gdamore/tcell/v2/README.md b/vendor/github.com/gdamore/tcell/v2/README.md index b7727e081..1d422e984 100644 --- a/vendor/github.com/gdamore/tcell/v2/README.md +++ b/vendor/github.com/gdamore/tcell/v2/README.md @@ -4,14 +4,12 @@ _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. -[![Linux Status](https://img.shields.io/travis/gdamore/tcell.svg?label=linux)](https://travis-ci.org/gdamore/tcell) -[![Windows Status](https://img.shields.io/appveyor/ci/gdamore/tcell.svg?label=windows)](https://ci.appveyor.com/project/gdamore/tcell) -[![Apache License](https://img.shields.io/badge/license-APACHE2-blue.svg)](https://github.com/gdamore/tcell/blob/master/LICENSE) -[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/gdamore/tcell/v2) -[![Report Card](https://goreportcard.com/badge/gdamore/tcell)](http://goreportcard.com/report/gdamore/tcell/v2) -[![Discord](https://img.shields.io/discord/639503822733180969?label=discord)](https://discord.gg/urTTxDN) -[![Coverage](https://codecov.io/gh/gdamore/tcell/branch/master/graph/badge.svg)](https://codecov.io/gh/gdamore/tcell) - +[![Linux](https://img.shields.io/github/workflow/status/gdamore/tcell/linux?logoColor=grey&logo=linux&label=)](https://github.com/gdamore/tcell/actions/workflows/linux.yml) +[![Windows](https://img.shields.io/github/workflow/status/gdamore/tcell/windows?logoColor=grey&logo=windows&label=)](https://github.com/gdamore/tcell/actions/workflows/windows.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) 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`. @@ -55,6 +53,10 @@ A brief, and still somewhat rough, [tutorial](TUTORIAL.md) is available. * [tmux-wormhole](https://github.com/gcla/tmux-wormhole) - _tmux_ plugin to transfer files * [gruid-tcell](https://github.com/anaseto/gruid-tcell) - driver for the grid based UI and game framework * [aretext](https://github.com/aretext/aretext) - minimalist text editor with _vim_ key bindings +* [sync](https://github.com/kyprifog/sync) - github repo synchronization tool +* [statusbar](https://github.com/kyprifog/statusbar) - statusbar motivation tool for tracking periodic tasks/goals +* [todo](https://github.com/kyprifog/todo) - simple todo app + ## Pure Go Terminfo Database diff --git a/vendor/github.com/gdamore/tcell/v2/go.mod b/vendor/github.com/gdamore/tcell/v2/go.mod index af97fa00b..ac4b10db4 100644 --- a/vendor/github.com/gdamore/tcell/v2/go.mod +++ b/vendor/github.com/gdamore/tcell/v2/go.mod @@ -4,9 +4,9 @@ go 1.12 require ( github.com/gdamore/encoding v1.0.0 - github.com/lucasb-eyer/go-colorful v1.0.3 - github.com/mattn/go-runewidth v0.0.10 + github.com/lucasb-eyer/go-colorful v1.2.0 + github.com/mattn/go-runewidth v0.0.13 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf - golang.org/x/text v0.3.0 + golang.org/x/text v0.3.7 ) diff --git a/vendor/github.com/gdamore/tcell/v2/go.sum b/vendor/github.com/gdamore/tcell/v2/go.sum index 068fabd98..b25da2aa5 100644 --- a/vendor/github.com/gdamore/tcell/v2/go.sum +++ b/vendor/github.com/gdamore/tcell/v2/go.sum @@ -1,14 +1,16 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= -github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/vendor/github.com/gdamore/tcell/v2/screen.go b/vendor/github.com/gdamore/tcell/v2/screen.go index cbe505f9e..15cfbf022 100644 --- a/vendor/github.com/gdamore/tcell/v2/screen.go +++ b/vendor/github.com/gdamore/tcell/v2/screen.go @@ -76,7 +76,7 @@ type Screen interface { // Size returns the screen size as width, height. This changes in // response to a call to Clear or Flush. - Size() (int, int) + 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 diff --git a/vendor/github.com/gdamore/tcell/v2/tscreen.go b/vendor/github.com/gdamore/tcell/v2/tscreen.go index ebd61a258..d0f73e54e 100644 --- a/vendor/github.com/gdamore/tcell/v2/tscreen.go +++ b/vendor/github.com/gdamore/tcell/v2/tscreen.go @@ -46,19 +46,36 @@ func NewTerminfoScreen() (Screen, error) { return NewTerminfoScreenFromTty(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) { - ti, e := terminfo.LookupTerminfo(os.Getenv("TERM")) +// 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(os.Getenv("TERM")) + 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) @@ -77,6 +94,14 @@ func NewTerminfoScreenFromTty(tty Tty) (Screen, error) { return 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 @@ -844,7 +869,7 @@ func (t *tScreen) EnableMouse(flags ...MouseFlags) { flagsPresent = true } if !flagsPresent { - f = MouseMotionEvents + f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents } t.Lock() @@ -860,14 +885,19 @@ func (t *tScreen) enableMouse(f MouseFlags) { if len(t.mouse) != 0 { // start by disabling all tracking. t.TPuts("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l") - if f&MouseMotionEvents != 0 { - t.TPuts("\x1b[?1003h\x1b[?1006h") - } else if f&MouseDragEvents != 0 { - t.TPuts("\x1b[?1002h\x1b[?1006h") - } else if f&MouseButtonEvents != 0 { - t.TPuts("\x1b[?1000h\x1b[?1006h") + if f&MouseButtonEvents != 0 { + t.TPuts("\x1b[?1000h") } + if f&MouseDragEvents != 0 { + t.TPuts("\x1b[?1002h") + } + if f&MouseMotionEvents != 0 { + t.TPuts("\x1b[?1003h") + } + + t.TPuts("\x1b[?1006h") } + } func (t *tScreen) DisableMouse() { @@ -1547,7 +1577,12 @@ func (t *tScreen) inputLoop(stopQ chan struct{}) { switch e { case nil: default: - _ = t.PostEvent(NewEventError(e)) + t.Lock() + running := t.running + t.Unlock() + if running { + _ = t.PostEvent(NewEventError(e)) + } return } if n > 0 { diff --git a/vendor/github.com/gdamore/tcell/v2/tty_unix.go b/vendor/github.com/gdamore/tcell/v2/tty_unix.go index 0c6ea7849..dbd961bbf 100644 --- a/vendor/github.com/gdamore/tcell/v2/tty_unix.go +++ b/vendor/github.com/gdamore/tcell/v2/tty_unix.go @@ -72,7 +72,7 @@ func (tty *devTty) Start() error { if tty.f, err = os.OpenFile(tty.dev, os.O_RDWR, 0); err != nil { return err } - tty.fd = int(tty.of.Fd()) + tty.fd = int(tty.f.Fd()) if !term.IsTerminal(tty.fd) { return errors.New("device is not a terminal") diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 887a5c7fe..1316ced2e 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -431,6 +431,11 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { bgColor = v.BgColor ch = v.Mask } else if v.Highlight && ry == rcy { + // 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 bgColor = bgColor | v.SelBgColor } diff --git a/vendor/github.com/sanity-io/litter/.gitignore b/vendor/github.com/sanity-io/litter/.gitignore new file mode 100644 index 000000000..e43b0f988 --- /dev/null +++ b/vendor/github.com/sanity-io/litter/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/vendor/github.com/sanity-io/litter/CHANGELOG.md b/vendor/github.com/sanity-io/litter/CHANGELOG.md new file mode 100644 index 000000000..0be671fbc --- /dev/null +++ b/vendor/github.com/sanity-io/litter/CHANGELOG.md @@ -0,0 +1,7 @@ +# 1.1.0 (2017-11-1) + +A slight breaking change. The dump-method of the `Dumper` interface has changed from `Dump` to `LitterDump` to mitigate potential collisions. + +# 1.0.0 (2017-10-29) + +Tagged 1.0.0. diff --git a/vendor/github.com/sanity-io/litter/LICENSE b/vendor/github.com/sanity-io/litter/LICENSE new file mode 100644 index 000000000..6a27c654f --- /dev/null +++ b/vendor/github.com/sanity-io/litter/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016-2017 Sanity.io. + +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/sanity-io/litter/README.md b/vendor/github.com/sanity-io/litter/README.md new file mode 100644 index 000000000..3dd83bc7f --- /dev/null +++ b/vendor/github.com/sanity-io/litter/README.md @@ -0,0 +1,202 @@ +[![!Build Status](https://travis-ci.org/sanity-io/litter.svg?branch=master)](https://travis-ci.org/sanity-io/litter) + +# Litter + +**Litter is a pretty printer library for Go data structures to aid in debugging and testing.** + +--- + +Litter is provided by + + +
+ Sanity: The Headless CMS Construction Kit +
+ +--- + +Litter named for the fact that it outputs *literals*, which you *litter* your output with. As a side benefit, all Litter output is syntactically correct Go. You can use Litter to emit data during debug, and it's also really nice for "snapshot data" in unit tests, since it produces consistent, sorted output. Litter was inspired by [Spew](https://github.com/davecgh/go-spew), but focuses on terseness and readability. + +### Basic example + +This: + +```go +type Person struct { + Name string + Age int + Parent *Person +} + +litter.Dump(Person{ + Name: "Bob", + Age: 20, + Parent: &Person{ + Name: "Jane", + Age: 50, + }, +}) +``` + +will output: + +``` +Person{ + Name: "Bob", + Age: 20, + Parent: &Person{ + Name: "Jane", + Age: 50, + }, +} +``` + +### Use in tests + +Litter is a great alternative to JSON or YAML for providing "snapshots" or example data. For example: + +```go +func TestSearch(t *testing.T) { + result := DoSearch() + + actual := litterOpts.Sdump(result) + expected, err := ioutil.ReadFile("testdata.txt") + if err != nil { + // First run, write test data since it doesn't exist + if !os.IsNotExist(err) { + t.Error(err) + } + ioutil.Write("testdata.txt", actual, 0644) + actual = expected + } + if expected != actual { + t.Errorf("Expected %s, got %s", expected, actual) + } +} +``` + +The first run will use Litter to write the data to `testdata.txt`. On subsequent runs, the test will compare the data. Since Litter always provides a consistent view of a value, you can compare the strings directly. + +### Circular references + +Litter detects circular references or aliasing, and will replace additional references to the same object with aliases. For example: + +```go +type Circular struct { + Self *Circular +} + +selfref := Circular{} +selfref.Self = &selfref + +litter.Dump(selfref) +``` + +will output: + +``` +Circular { // p0 + Self: p0, +} +``` + +## Installation + +```bash +$ go get -u github.com/sanity-io/litter +``` + +## Quick start + +Add this import line to the file you're working in: + +```go +import "github.com/sanity-io/litter" +``` + +To dump a variable with full newlines, indentation, type, and aliasing information, use `Dump` or `Sdump`: + +```go +litter.Dump(myVar1) +str := litter.Sdump(myVar1) +``` + +### `litter.Dump(value, ...)` + +Dumps the data structure to STDOUT. + +### `litter.Sdump(value, ...)` + +Returns the dump as a string + +## Configuration + +You can configure litter globally by modifying the default `litter.Config` + +```go +// Strip all package names from types +litter.Config.StripPackageNames = true + +// Hide private struct fields from dumped structs +litter.Config.HidePrivateFields = true + +// Hide fields matched with given regexp if it is not nil. It is set up to hide fields generate with protoc-gen-go +litter.Config.FieldExclusions = regexp.MustCompile(`^(XXX_.*)$`) + +// Sets a "home" package. The package name will be stripped from all its types +litter.Config.HomePackage = "mypackage" + +// Sets separator used when multiple arguments are passed to Dump() or Sdump(). +litter.Config.Separator = "\n" + +// Use compact output: strip newlines and other unnecessary whitespace +litter.Config.Compact = true + +// Prevents duplicate pointers from being replaced by placeholder variable names (except in necessary, in the case +// of circular references) +litter.Config.DisablePointerReplacement = true +``` + +### `litter.Options` + +Allows you to configure a local configuration of litter to allow for proper compartmentalization of state at the expense of some comfort: + +``` go + sq := litter.Options { + HidePrivateFields: true, + HomePackage: "thispack", + Separator: " ", + } + + sq.Dump("dumped", "with", "local", "settings") +``` + +## Custom dumpers + +Implement the interface Dumper on your types to take control of how your type is dumped. + +``` go +type Dumper interface { + LitterDump(w io.Writer) +} +``` + +Just write your custom dump to the provided stream, using multiple lines divided by `"\n"` if you need. Litter +might indent your output according to context, and optionally decorate your first line with a pointer comment +where appropriate. + +A couple of examples from the test suite: + +``` go +type CustomMultiLineDumper struct {} + +func (cmld *CustomMultiLineDumper) LitterDump(w io.Writer) { + w.Write([]byte("{\n multi\n line\n}")) +} + +type CustomSingleLineDumper int + +func (csld CustomSingleLineDumper) LitterDump(w io.Writer) { + w.Write([]byte("")) +} +```` diff --git a/vendor/github.com/sanity-io/litter/dump.go b/vendor/github.com/sanity-io/litter/dump.go new file mode 100644 index 000000000..3dfbb9dbd --- /dev/null +++ b/vendor/github.com/sanity-io/litter/dump.go @@ -0,0 +1,528 @@ +package litter + +import ( + "bytes" + "fmt" + "io" + "os" + "reflect" + "regexp" + "runtime" + "sort" + "strconv" + "strings" +) + +var ( + packageNameStripperRegexp = regexp.MustCompile(`\b[a-zA-Z_]+[a-zA-Z_0-9]+\.`) + compactTypeRegexp = regexp.MustCompile(`\s*([,;{}()])\s*`) +) + +// Dumper is the interface for implementing custom dumper for your types. +type Dumper interface { + LitterDump(w io.Writer) +} + +// Options represents configuration options for litter +type Options struct { + Compact bool + StripPackageNames bool + HidePrivateFields bool + HideZeroValues bool + FieldExclusions *regexp.Regexp + FieldFilter func(reflect.StructField, reflect.Value) bool + HomePackage string + Separator string + StrictGo bool + DumpFunc func(reflect.Value, io.Writer) bool + + // DisablePointerReplacement, if true, disables the replacing of pointer data with variable names + // when it's safe. This is useful for diffing two structures, where pointer variables would cause + // false changes. However, circular graphs are still detected and elided to avoid infinite output. + DisablePointerReplacement bool +} + +// Config is the default config used when calling Dump +var Config = Options{ + StripPackageNames: false, + HidePrivateFields: true, + FieldExclusions: regexp.MustCompile(`^(XXX_.*)$`), // XXX_ is a prefix of fields generated by protoc-gen-go + Separator: " ", +} + +type dumpState struct { + w io.Writer + depth int + config *Options + pointers ptrmap + visitedPointers ptrmap + parentPointers ptrmap + currentPointer *ptrinfo + homePackageRegexp *regexp.Regexp +} + +func (s *dumpState) write(b []byte) { + if _, err := s.w.Write(b); err != nil { + panic(err) + } +} + +func (s *dumpState) writeString(str string) { + s.write([]byte(str)) +} + +func (s *dumpState) indent() { + if !s.config.Compact { + s.write(bytes.Repeat([]byte(" "), s.depth)) + } +} + +func (s *dumpState) newlineWithPointerNameComment() { + if ptr := s.currentPointer; ptr != nil { + if s.config.Compact { + s.write([]byte(fmt.Sprintf("/*%s*/", ptr.label()))) + } else { + s.write([]byte(fmt.Sprintf(" // %s\n", ptr.label()))) + } + s.currentPointer = nil + return + } + if !s.config.Compact { + s.write([]byte("\n")) + } +} + +func (s *dumpState) dumpType(v reflect.Value) { + typeName := v.Type().String() + if s.config.StripPackageNames { + typeName = packageNameStripperRegexp.ReplaceAllLiteralString(typeName, "") + } else if s.homePackageRegexp != nil { + typeName = s.homePackageRegexp.ReplaceAllLiteralString(typeName, "") + } + if s.config.Compact { + typeName = compactTypeRegexp.ReplaceAllString(typeName, "$1") + } + s.write([]byte(typeName)) +} + +func (s *dumpState) dumpSlice(v reflect.Value) { + s.dumpType(v) + numEntries := v.Len() + if numEntries == 0 { + s.write([]byte("{}")) + return + } + s.write([]byte("{")) + s.newlineWithPointerNameComment() + s.depth++ + for i := 0; i < numEntries; i++ { + s.indent() + s.dumpVal(v.Index(i)) + if !s.config.Compact || i < numEntries-1 { + s.write([]byte(",")) + } + s.newlineWithPointerNameComment() + } + s.depth-- + s.indent() + s.write([]byte("}")) +} + +func (s *dumpState) dumpStruct(v reflect.Value) { + dumpPreamble := func() { + s.dumpType(v) + s.write([]byte("{")) + s.newlineWithPointerNameComment() + s.depth++ + } + preambleDumped := false + vt := v.Type() + numFields := v.NumField() + for i := 0; i < numFields; i++ { + vtf := vt.Field(i) + if s.config.HidePrivateFields && vtf.PkgPath != "" || s.config.FieldExclusions != nil && s.config.FieldExclusions.MatchString(vtf.Name) { + continue + } + if s.config.FieldFilter != nil && !s.config.FieldFilter(vtf, v.Field(i)) { + continue + } + if s.config.HideZeroValues && isZeroValue(v.Field(i)) { + continue + } + if !preambleDumped { + dumpPreamble() + preambleDumped = true + } + s.indent() + s.write([]byte(vtf.Name)) + if s.config.Compact { + s.write([]byte(":")) + } else { + s.write([]byte(": ")) + } + s.dumpVal(v.Field(i)) + if !s.config.Compact || i < numFields-1 { + s.write([]byte(",")) + } + s.newlineWithPointerNameComment() + } + if preambleDumped { + s.depth-- + s.indent() + s.write([]byte("}")) + } else { + // There were no fields dumped + s.dumpType(v) + s.write([]byte("{}")) + } +} + +func (s *dumpState) dumpMap(v reflect.Value) { + if v.IsNil() { + s.dumpType(v) + s.writeString("(nil)") + return + } + + s.dumpType(v) + + keys := v.MapKeys() + if len(keys) == 0 { + s.write([]byte("{}")) + return + } + + s.write([]byte("{")) + s.newlineWithPointerNameComment() + s.depth++ + sort.Sort(mapKeySorter{ + keys: keys, + options: s.config, + }) + numKeys := len(keys) + for i, key := range keys { + s.indent() + s.dumpVal(key) + if s.config.Compact { + s.write([]byte(":")) + } else { + s.write([]byte(": ")) + } + s.dumpVal(v.MapIndex(key)) + if !s.config.Compact || i < numKeys-1 { + s.write([]byte(",")) + } + s.newlineWithPointerNameComment() + } + s.depth-- + s.indent() + s.write([]byte("}")) +} + +func (s *dumpState) dumpFunc(v reflect.Value) { + parts := strings.Split(runtime.FuncForPC(v.Pointer()).Name(), "/") + name := parts[len(parts)-1] + + // Anonymous function + if strings.Count(name, ".") > 1 { + s.dumpType(v) + } else { + if s.config.StripPackageNames { + name = packageNameStripperRegexp.ReplaceAllLiteralString(name, "") + } else if s.homePackageRegexp != nil { + name = s.homePackageRegexp.ReplaceAllLiteralString(name, "") + } + if s.config.Compact { + name = compactTypeRegexp.ReplaceAllString(name, "$1") + } + s.write([]byte(name)) + } +} + +func (s *dumpState) dumpChan(v reflect.Value) { + vType := v.Type() + res := []byte(vType.String()) + s.write(res) +} + +func (s *dumpState) dumpCustom(v reflect.Value, buf *bytes.Buffer) { + + // Dump the type + s.dumpType(v) + + if s.config.Compact { + s.write(buf.Bytes()) + return + } + + // Now output the dump taking care to apply the current indentation-level + // and pointer name comments. + var err error + firstLine := true + for err == nil { + var lineBytes []byte + lineBytes, err = buf.ReadBytes('\n') + line := strings.TrimRight(string(lineBytes), " \n") + + if err != nil && err != io.EOF { + break + } + // Do not indent first line + if firstLine { + firstLine = false + } else { + s.indent() + } + s.write([]byte(line)) + + // At EOF we're done + if err == io.EOF { + return + } + s.newlineWithPointerNameComment() + } + panic(err) +} + +func (s *dumpState) dump(value interface{}) { + if value == nil { + printNil(s.w) + return + } + v := reflect.ValueOf(value) + s.dumpVal(v) +} + +func (s *dumpState) descendIntoPossiblePointer(value reflect.Value, f func()) { + canonicalize := true + if isPointerValue(value) { + // If elision disabled, and this is not a circular reference, don't canonicalize + if s.config.DisablePointerReplacement && s.parentPointers.add(value) { + canonicalize = false + } + + // Add to stack of pointers we're recursively descending into + s.parentPointers.add(value) + defer s.parentPointers.remove(value) + } + + if !canonicalize { + ptr, _ := s.pointerFor(value) + s.currentPointer = ptr + f() + return + } + + ptr, firstVisit := s.pointerFor(value) + if ptr == nil { + f() + return + } + if firstVisit { + s.currentPointer = ptr + f() + return + } + s.write([]byte(ptr.label())) +} + +func (s *dumpState) dumpVal(value reflect.Value) { + if value.Kind() == reflect.Ptr && value.IsNil() { + s.write([]byte("nil")) + return + } + + v := deInterface(value) + kind := v.Kind() + + // Try to handle with dump func + if s.config.DumpFunc != nil { + buf := new(bytes.Buffer) + if s.config.DumpFunc(v, buf) { + s.dumpCustom(v, buf) + return + } + } + + // Handle custom dumpers + dumperType := reflect.TypeOf((*Dumper)(nil)).Elem() + if v.Type().Implements(dumperType) { + s.descendIntoPossiblePointer(v, func() { + // Run the custom dumper buffering the output + buf := new(bytes.Buffer) + dumpFunc := v.MethodByName("LitterDump") + dumpFunc.Call([]reflect.Value{reflect.ValueOf(buf)}) + s.dumpCustom(v, buf) + }) + return + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + s.write([]byte("")) + + case reflect.Bool: + printBool(s.w, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(s.w, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(s.w, v.Uint(), 10) + + case reflect.Float32: + printFloat(s.w, v.Float(), 32) + + case reflect.Float64: + printFloat(s.w, v.Float(), 64) + + case reflect.Complex64: + printComplex(s.w, v.Complex(), 32) + + case reflect.Complex128: + printComplex(s.w, v.Complex(), 64) + + case reflect.String: + s.write([]byte(strconv.Quote(v.String()))) + + case reflect.Slice: + if v.IsNil() { + printNil(s.w) + break + } + fallthrough + + case reflect.Array: + s.descendIntoPossiblePointer(v, func() { + s.dumpSlice(v) + }) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + printNil(s.w) + } + + case reflect.Ptr: + s.descendIntoPossiblePointer(v, func() { + if s.config.StrictGo { + s.writeString(fmt.Sprintf("(func(v %s) *%s { return &v })(", v.Elem().Type(), v.Elem().Type())) + s.dumpVal(v.Elem()) + s.writeString(")") + } else { + s.writeString("&") + s.dumpVal(v.Elem()) + } + }) + + case reflect.Map: + s.descendIntoPossiblePointer(v, func() { + s.dumpMap(v) + }) + + case reflect.Struct: + s.dumpStruct(v) + + case reflect.Func: + s.dumpFunc(v) + + case reflect.Chan: + s.dumpChan(v) + + default: + if v.CanInterface() { + s.writeString(fmt.Sprintf("%v", v.Interface())) + } else { + s.writeString(fmt.Sprintf("%v", v.String())) + } + } +} + +// registers that the value has been visited and checks to see if it is one of the +// pointers we will see multiple times. If it is, it returns a temporary name for this +// pointer. It also returns a boolean value indicating whether this is the first time +// this name is returned so the caller can decide whether the contents of the pointer +// has been dumped before or not. +func (s *dumpState) pointerFor(v reflect.Value) (*ptrinfo, bool) { + if isPointerValue(v) { + if info, ok := s.pointers.get(v); ok { + firstVisit := s.visitedPointers.add(v) + return info, firstVisit + } + } + return nil, false +} + +// prepares a new state object for dumping the provided value +func newDumpState(value interface{}, options *Options, writer io.Writer) *dumpState { + result := &dumpState{ + config: options, + pointers: mapReusedPointers(reflect.ValueOf(value)), + w: writer, + } + + if options.HomePackage != "" { + result.homePackageRegexp = regexp.MustCompile(fmt.Sprintf("\\b%s\\.", options.HomePackage)) + } + + return result +} + +// Dump a value to stdout +func Dump(value ...interface{}) { + (&Config).Dump(value...) +} + +// Sdump dumps a value to a string +func Sdump(value ...interface{}) string { + return (&Config).Sdump(value...) +} + +// Dump a value to stdout according to the options +func (o Options) Dump(values ...interface{}) { + for i, value := range values { + state := newDumpState(value, &o, os.Stdout) + if i > 0 { + state.write([]byte(o.Separator)) + } + state.dump(value) + } + _, _ = os.Stdout.Write([]byte("\n")) +} + +// Sdump dumps a value to a string according to the options +func (o Options) Sdump(values ...interface{}) string { + buf := new(bytes.Buffer) + for i, value := range values { + if i > 0 { + _, _ = buf.Write([]byte(o.Separator)) + } + state := newDumpState(value, &o, buf) + state.dump(value) + } + return buf.String() +} + +type mapKeySorter struct { + keys []reflect.Value + options *Options +} + +func (s mapKeySorter) Len() int { + return len(s.keys) +} + +func (s mapKeySorter) Swap(i, j int) { + s.keys[i], s.keys[j] = s.keys[j], s.keys[i] +} + +func (s mapKeySorter) Less(i, j int) bool { + ibuf := new(bytes.Buffer) + jbuf := new(bytes.Buffer) + newDumpState(s.keys[i], s.options, ibuf).dumpVal(s.keys[i]) + newDumpState(s.keys[j], s.options, jbuf).dumpVal(s.keys[j]) + return ibuf.String() < jbuf.String() +} diff --git a/vendor/github.com/sanity-io/litter/go.mod b/vendor/github.com/sanity-io/litter/go.mod new file mode 100644 index 000000000..c1c20c939 --- /dev/null +++ b/vendor/github.com/sanity-io/litter/go.mod @@ -0,0 +1,9 @@ +module github.com/sanity-io/litter + +go 1.14 + +require ( + github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b // indirect + github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect + github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 +) diff --git a/vendor/github.com/sanity-io/litter/go.sum b/vendor/github.com/sanity-io/litter/go.sum new file mode 100644 index 000000000..800ae0053 --- /dev/null +++ b/vendor/github.com/sanity-io/litter/go.sum @@ -0,0 +1,6 @@ +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b h1:XxMZvQZtTXpWMNWK82vdjCLCe7uGMFXdTsJH0v3Hkvw= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 h1:UsFdQ3ZmlzS0BqZYGxvYaXvFGUbCmPGy8DM7qWJJiIQ= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/vendor/github.com/sanity-io/litter/pointers.go b/vendor/github.com/sanity-io/litter/pointers.go new file mode 100644 index 000000000..2c57d79ae --- /dev/null +++ b/vendor/github.com/sanity-io/litter/pointers.go @@ -0,0 +1,167 @@ +package litter + +import ( + "fmt" + "reflect" + "sort" +) + +// mapReusedPointers takes a structure, and recursively maps all pointers mentioned in the tree, +// detecting circular references, and providing a list of all pointers that was referenced at +// least twice by the provided structure. +func mapReusedPointers(v reflect.Value) ptrmap { + pm := &pointerVisitor{} + pm.consider(v) + return pm.reused +} + +// A map of pointers. +type ptrinfo struct { + id int + parent *ptrmap +} + +func (p *ptrinfo) label() string { + if p.id == -1 { + p.id = p.parent.count + p.parent.count++ + } + return fmt.Sprintf("p%d", p.id) +} + +type ptrkey struct { + p uintptr + t reflect.Type +} + +func ptrkeyFor(v reflect.Value) (k ptrkey) { + k.p = v.Pointer() + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.IsValid() { + k.t = v.Type() + } + return +} + +type ptrmap struct { + m map[ptrkey]*ptrinfo + count int +} + +// Returns true if contains a pointer. +func (pm *ptrmap) contains(v reflect.Value) bool { + if pm.m != nil { + _, ok := pm.m[ptrkeyFor(v)] + return ok + } + return false +} + +// Gets a pointer. +func (pm *ptrmap) get(v reflect.Value) (*ptrinfo, bool) { + if pm.m != nil { + p, ok := pm.m[ptrkeyFor(v)] + return p, ok + } + return nil, false +} + +// Removes a pointer. +func (pm *ptrmap) remove(v reflect.Value) { + if pm.m != nil { + delete(pm.m, ptrkeyFor(v)) + } +} + +// Adds a pointer. +func (pm *ptrmap) add(p reflect.Value) bool { + if pm.contains(p) { + return false + } + pm.put(p) + return true +} + +// Adds a pointer (slow path). +func (pm *ptrmap) put(v reflect.Value) { + if pm.m == nil { + pm.m = make(map[ptrkey]*ptrinfo, 31) + } + + key := ptrkeyFor(v) + if _, ok := pm.m[key]; !ok { + pm.m[key] = &ptrinfo{id: -1, parent: pm} + } +} + +type pointerVisitor struct { + pointers ptrmap + reused ptrmap +} + +// Recursively consider v and each of its children, updating the map according to the +// semantics of MapReusedPointers +func (pv *pointerVisitor) consider(v reflect.Value) { + if v.Kind() == reflect.Invalid { + return + } + if isPointerValue(v) { // pointer is 0 for unexported fields + if pv.tryAddPointer(v) { + // No use descending inside this value, since it have been seen before and all its descendants + // have been considered + return + } + } + + // Now descend into any children of this value + switch v.Kind() { + case reflect.Slice, reflect.Array: + numEntries := v.Len() + for i := 0; i < numEntries; i++ { + pv.consider(v.Index(i)) + } + + case reflect.Interface: + pv.consider(v.Elem()) + + case reflect.Ptr: + pv.consider(v.Elem()) + + case reflect.Map: + keys := v.MapKeys() + sort.Sort(mapKeySorter{ + keys: keys, + options: &Config, + }) + for _, key := range keys { + pv.consider(v.MapIndex(key)) + } + + case reflect.Struct: + numFields := v.NumField() + for i := 0; i < numFields; i++ { + pv.consider(v.Field(i)) + } + } +} + +// addPointer to the pointerMap, update reusedPointers. Returns true if pointer was reused +func (pv *pointerVisitor) tryAddPointer(v reflect.Value) bool { + // Is this allready known to be reused? + if pv.reused.contains(v) { + return true + } + + // Have we seen it once before? + if pv.pointers.contains(v) { + // Add it to the register of pointers we have seen more than once + pv.reused.add(v) + return true + } + + // This pointer was new to us + pv.pointers.add(v) + return false +} diff --git a/vendor/github.com/sanity-io/litter/print.go b/vendor/github.com/sanity-io/litter/print.go new file mode 100644 index 000000000..88adf5b41 --- /dev/null +++ b/vendor/github.com/sanity-io/litter/print.go @@ -0,0 +1,50 @@ +package litter + +import ( + "io" + "math" + "strconv" +) + +func printBool(w io.Writer, value bool) { + if value { + w.Write([]byte("true")) + return + } + w.Write([]byte("false")) +} + +func printInt(w io.Writer, val int64, base int) { + w.Write([]byte(strconv.FormatInt(val, base))) +} + +func printUint(w io.Writer, val uint64, base int) { + w.Write([]byte(strconv.FormatUint(val, base))) +} + +func printFloat(w io.Writer, val float64, precision int) { + if math.Trunc(val) == val { + // Ensure that floats like 1.0 are always printed with a decimal point + w.Write([]byte(strconv.FormatFloat(val, 'f', 1, precision))) + } else { + w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) + } +} + +func printComplex(w io.Writer, c complex128, floatPrecision int) { + w.Write([]byte("complex")) + printInt(w, int64(floatPrecision*2), 10) + r := real(c) + w.Write([]byte("(")) + w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) + i := imag(c) + if i >= 0 { + w.Write([]byte("+")) + } + w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) + w.Write([]byte("i)")) +} + +func printNil(w io.Writer) { + w.Write([]byte("nil")) +} diff --git a/vendor/github.com/sanity-io/litter/util.go b/vendor/github.com/sanity-io/litter/util.go new file mode 100644 index 000000000..58be4751e --- /dev/null +++ b/vendor/github.com/sanity-io/litter/util.go @@ -0,0 +1,28 @@ +package litter + +import ( + "reflect" +) + +// deInterface 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 deInterface(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } + return v +} + +func isPointerValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: + return true + } + return false +} + +func isZeroValue(v reflect.Value) bool { + return (isPointerValue(v) && v.IsNil()) || + (v.IsValid() && v.CanInterface() && reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())) +} diff --git a/vendor/golang.org/x/sys/cpu/cpu_gc_x86.go b/vendor/golang.org/x/sys/cpu/cpu_gc_x86.go index 3298a87e9..fa7cdb9bc 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_gc_x86.go +++ b/vendor/golang.org/x/sys/cpu/cpu_gc_x86.go @@ -15,7 +15,3 @@ func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) // xgetbv with ecx = 0 is implemented in cpu_x86.s for gc compiler // and in cpu_gccgo.c for gccgo. func xgetbv() (eax, edx uint32) - -// darwinSupportsAVX512 is implemented in cpu_x86.s for gc compiler -// and in cpu_gccgo_x86.go for gccgo. -func darwinSupportsAVX512() bool diff --git a/vendor/golang.org/x/sys/cpu/cpu_x86.go b/vendor/golang.org/x/sys/cpu/cpu_x86.go index 5ea287b7e..f5aacfc82 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_x86.go +++ b/vendor/golang.org/x/sys/cpu/cpu_x86.go @@ -90,9 +90,10 @@ func archInit() { osSupportsAVX = isSet(1, eax) && isSet(2, eax) if runtime.GOOS == "darwin" { - // Check darwin commpage for AVX512 support. Necessary because: - // https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/osfmk/i386/fpu.c#L175-L201 - osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512() + // Darwin doesn't save/restore AVX-512 mask registers correctly across signal handlers. + // Since users can't rely on mask register contents, let's not advertise AVX-512 support. + // See issue 49233. + osSupportsAVX512 = false } else { // Check if OPMASK and ZMM registers have OS support. osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax) diff --git a/vendor/golang.org/x/sys/cpu/cpu_x86.s b/vendor/golang.org/x/sys/cpu/cpu_x86.s index b748ba52f..39acab2ff 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_x86.s +++ b/vendor/golang.org/x/sys/cpu/cpu_x86.s @@ -26,27 +26,3 @@ TEXT ·xgetbv(SB),NOSPLIT,$0-8 MOVL AX, eax+0(FP) MOVL DX, edx+4(FP) RET - -// func darwinSupportsAVX512() bool -TEXT ·darwinSupportsAVX512(SB), NOSPLIT, $0-1 - MOVB $0, ret+0(FP) // default to false -#ifdef GOOS_darwin // return if not darwin -#ifdef GOARCH_amd64 // return if not amd64 -// These values from: -// https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/osfmk/i386/cpu_capabilities.h -#define commpage64_base_address 0x00007fffffe00000 -#define commpage64_cpu_capabilities64 (commpage64_base_address+0x010) -#define commpage64_version (commpage64_base_address+0x01E) -#define hasAVX512F 0x0000004000000000 - MOVQ $commpage64_version, BX - CMPW (BX), $13 // cpu_capabilities64 undefined in versions < 13 - JL no_avx512 - MOVQ $commpage64_cpu_capabilities64, BX - MOVQ $hasAVX512F, CX - TESTQ (BX), CX - JZ no_avx512 - MOVB $1, ret+0(FP) -no_avx512: -#endif -#endif - RET diff --git a/vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go b/vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go index 87ae9d2a3..c9b69937a 100644 --- a/vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go +++ b/vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build go1.5 // +build go1.5 package plan9 diff --git a/vendor/golang.org/x/sys/plan9/pwd_plan9.go b/vendor/golang.org/x/sys/plan9/pwd_plan9.go index c07c798bc..98bf56b73 100644 --- a/vendor/golang.org/x/sys/plan9/pwd_plan9.go +++ b/vendor/golang.org/x/sys/plan9/pwd_plan9.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !go1.5 // +build !go1.5 package plan9 diff --git a/vendor/golang.org/x/sys/plan9/race.go b/vendor/golang.org/x/sys/plan9/race.go index 42edd93ef..62377d2ff 100644 --- a/vendor/golang.org/x/sys/plan9/race.go +++ b/vendor/golang.org/x/sys/plan9/race.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 && race // +build plan9,race package plan9 diff --git a/vendor/golang.org/x/sys/plan9/race0.go b/vendor/golang.org/x/sys/plan9/race0.go index c89cf8fc0..f8da30876 100644 --- a/vendor/golang.org/x/sys/plan9/race0.go +++ b/vendor/golang.org/x/sys/plan9/race0.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 && !race // +build plan9,!race package plan9 diff --git a/vendor/golang.org/x/sys/plan9/str.go b/vendor/golang.org/x/sys/plan9/str.go index 4f7f9ad7c..55fa8d025 100644 --- a/vendor/golang.org/x/sys/plan9/str.go +++ b/vendor/golang.org/x/sys/plan9/str.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 // +build plan9 package plan9 diff --git a/vendor/golang.org/x/sys/plan9/syscall.go b/vendor/golang.org/x/sys/plan9/syscall.go index e7363a2f5..602473cba 100644 --- a/vendor/golang.org/x/sys/plan9/syscall.go +++ b/vendor/golang.org/x/sys/plan9/syscall.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 // +build plan9 // Package plan9 contains an interface to the low-level operating system diff --git a/vendor/golang.org/x/sys/plan9/syscall_plan9.go b/vendor/golang.org/x/sys/plan9/syscall_plan9.go index 84e147148..723b1f400 100644 --- a/vendor/golang.org/x/sys/plan9/syscall_plan9.go +++ b/vendor/golang.org/x/sys/plan9/syscall_plan9.go @@ -132,8 +132,10 @@ func Pipe(p []int) (err error) { } var pp [2]int32 err = pipe(&pp) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return } diff --git a/vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go b/vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go index 6819bc209..3f40b9bd7 100644 --- a/vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go +++ b/vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go @@ -1,6 +1,7 @@ // go run mksyscall.go -l32 -plan9 -tags plan9,386 syscall_plan9.go // Code generated by the command above; see README.md. DO NOT EDIT. +//go:build plan9 && 386 // +build plan9,386 package plan9 diff --git a/vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go b/vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go index 418abbbfc..0e6a96aa4 100644 --- a/vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go +++ b/vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go @@ -1,6 +1,7 @@ // go run mksyscall.go -l32 -plan9 -tags plan9,amd64 syscall_plan9.go // Code generated by the command above; see README.md. DO NOT EDIT. +//go:build plan9 && amd64 // +build plan9,amd64 package plan9 diff --git a/vendor/golang.org/x/sys/plan9/zsyscall_plan9_arm.go b/vendor/golang.org/x/sys/plan9/zsyscall_plan9_arm.go index 3e8a1a58c..244c501b7 100644 --- a/vendor/golang.org/x/sys/plan9/zsyscall_plan9_arm.go +++ b/vendor/golang.org/x/sys/plan9/zsyscall_plan9_arm.go @@ -1,6 +1,7 @@ // go run mksyscall.go -l32 -plan9 -tags plan9,arm syscall_plan9.go // Code generated by the command above; see README.md. DO NOT EDIT. +//go:build plan9 && arm // +build plan9,arm package plan9 diff --git a/vendor/golang.org/x/sys/unix/README.md b/vendor/golang.org/x/sys/unix/README.md index 474efad0e..7d3c060e1 100644 --- a/vendor/golang.org/x/sys/unix/README.md +++ b/vendor/golang.org/x/sys/unix/README.md @@ -149,7 +149,7 @@ To add a constant, add the header that includes it to the appropriate variable. Then, edit the regex (if necessary) to match the desired constant. Avoid making the regex too broad to avoid matching unintended constants. -### mkmerge.go +### internal/mkmerge This program is used to extract duplicate const, func, and type declarations from the generated architecture-specific files listed below, and merge these diff --git a/vendor/golang.org/x/sys/unix/mkall.sh b/vendor/golang.org/x/sys/unix/mkall.sh index 396aadf86..ee7362348 100644 --- a/vendor/golang.org/x/sys/unix/mkall.sh +++ b/vendor/golang.org/x/sys/unix/mkall.sh @@ -50,7 +50,7 @@ if [[ "$GOOS" = "linux" ]]; then # Use the Docker-based build system # Files generated through docker (use $cmd so you can Ctl-C the build or run) $cmd docker build --tag generate:$GOOS $GOOS - $cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")" && /bin/pwd):/build generate:$GOOS + $cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && /bin/pwd):/build generate:$GOOS exit fi diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index a74ef58f8..a47b035f9 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -239,6 +239,7 @@ struct ltchars { #include #include #include +#include #include #include #include @@ -260,6 +261,7 @@ struct ltchars { #include #include #include +#include #include #include @@ -520,7 +522,7 @@ ccflags="$@" $2 ~ /^HW_MACHINE$/ || $2 ~ /^SYSCTL_VERS/ || $2 !~ "MNT_BITS" && - $2 ~ /^(MS|MNT|UMOUNT)_/ || + $2 ~ /^(MS|MNT|MOUNT|UMOUNT)_/ || $2 ~ /^NS_GET_/ || $2 ~ /^TUN(SET|GET|ATTACH|DETACH)/ || $2 ~ /^(O|F|[ES]?FD|NAME|S|PTRACE|PT|TFD)_/ || @@ -605,6 +607,7 @@ ccflags="$@" $2 ~ /^MTD/ || $2 ~ /^OTP/ || $2 ~ /^MEM/ || + $2 ~ /^WG/ || $2 ~ /^BLK[A-Z]*(GET$|SET$|BUF$|PART$|SIZE)/ {printf("\t%s = C.%s\n", $2, $2)} $2 ~ /^__WCOREFLAG$/ {next} $2 ~ /^__W[A-Z0-9]+$/ {printf("\t%s = C.%s\n", substr($2,3), $2)} diff --git a/vendor/golang.org/x/sys/unix/sockcmsg_linux.go b/vendor/golang.org/x/sys/unix/sockcmsg_linux.go index 326fb04a5..5f63147e0 100644 --- a/vendor/golang.org/x/sys/unix/sockcmsg_linux.go +++ b/vendor/golang.org/x/sys/unix/sockcmsg_linux.go @@ -67,9 +67,7 @@ func ParseOrigDstAddr(m *SocketControlMessage) (Sockaddr, error) { sa := new(SockaddrInet4) p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil case m.Header.Level == SOL_IPV6 && m.Header.Type == IPV6_ORIGDSTADDR: @@ -78,9 +76,7 @@ func ParseOrigDstAddr(m *SocketControlMessage) (Sockaddr, error) { p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil default: diff --git a/vendor/golang.org/x/sys/unix/syscall_aix.go b/vendor/golang.org/x/sys/unix/syscall_aix.go index d8efb715f..4f55c8d99 100644 --- a/vendor/golang.org/x/sys/unix/syscall_aix.go +++ b/vendor/golang.org/x/sys/unix/syscall_aix.go @@ -70,9 +70,7 @@ func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) { p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrInet4, nil } @@ -85,9 +83,7 @@ func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, _Socklen, error) { p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) sa.raw.Scope_id = sa.ZoneId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrInet6, nil } @@ -261,9 +257,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { sa := new(SockaddrInet4) p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil case AF_INET6: @@ -272,9 +266,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil } return nil, EAFNOSUPPORT @@ -385,6 +377,11 @@ func (w WaitStatus) TrapCause() int { return -1 } //sys fcntl(fd int, cmd int, arg int) (val int, err error) +//sys fsyncRange(fd int, how int, start int64, length int64) (err error) = fsync_range +func Fsync(fd int) error { + return fsyncRange(fd, O_SYNC, 0, 0) +} + /* * Direct access */ @@ -401,7 +398,6 @@ func (w WaitStatus) TrapCause() int { return -1 } //sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) //sys Fchownat(dirfd int, path string, uid int, gid int, flags int) (err error) //sys Fdatasync(fd int) (err error) -//sys Fsync(fd int) (err error) // readdir_r //sysnb Getpgid(pid int) (pgid int, err error) @@ -523,8 +519,10 @@ func Pipe(p []int) (err error) { } var pp [2]_C_int err = pipe(&pp) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_bsd.go b/vendor/golang.org/x/sys/unix/syscall_bsd.go index 95ac3946b..0ce452326 100644 --- a/vendor/golang.org/x/sys/unix/syscall_bsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_bsd.go @@ -163,9 +163,7 @@ func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) { p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), _Socklen(sa.raw.Len), nil } @@ -179,9 +177,7 @@ func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, _Socklen, error) { p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) sa.raw.Scope_id = sa.ZoneId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), _Socklen(sa.raw.Len), nil } @@ -210,9 +206,7 @@ func (sa *SockaddrDatalink) sockaddr() (unsafe.Pointer, _Socklen, error) { sa.raw.Nlen = sa.Nlen sa.raw.Alen = sa.Alen sa.raw.Slen = sa.Slen - for i := 0; i < len(sa.raw.Data); i++ { - sa.raw.Data[i] = sa.Data[i] - } + sa.raw.Data = sa.Data return unsafe.Pointer(&sa.raw), SizeofSockaddrDatalink, nil } @@ -228,9 +222,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { sa.Nlen = pp.Nlen sa.Alen = pp.Alen sa.Slen = pp.Slen - for i := 0; i < len(sa.Data); i++ { - sa.Data[i] = pp.Data[i] - } + sa.Data = pp.Data return sa, nil case AF_UNIX: @@ -262,9 +254,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { sa := new(SockaddrInet4) p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil case AF_INET6: @@ -273,9 +263,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil } return anyToSockaddrGOOS(fd, rsa) diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index 8826f4143..0eaab9131 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -159,8 +159,10 @@ func Pipe(p []int) (err error) { } var x [2]int32 err = pipe(&x) - p[0] = int(x[0]) - p[1] = int(x[1]) + if err == nil { + p[0] = int(x[0]) + p[1] = int(x[1]) + } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go index 5af108a50..2e37c3167 100644 --- a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go +++ b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go @@ -101,7 +101,10 @@ func Pipe(p []int) (err error) { if len(p) != 2 { return EINVAL } - p[0], p[1], err = pipe() + r, w, err := pipe() + if err == nil { + p[0], p[1] = r, w + } return } @@ -114,7 +117,10 @@ func Pipe2(p []int, flags int) (err error) { var pp [2]_C_int // pipe2 on dragonfly takes an fds array as an argument, but still // returns the file descriptors. - p[0], p[1], err = pipe2(&pp, flags) + r, w, err := pipe2(&pp, flags) + if err == nil { + p[0], p[1] = r, w + } return err } diff --git a/vendor/golang.org/x/sys/unix/syscall_freebsd.go b/vendor/golang.org/x/sys/unix/syscall_freebsd.go index 18c392cf3..2f650ae66 100644 --- a/vendor/golang.org/x/sys/unix/syscall_freebsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_freebsd.go @@ -110,8 +110,10 @@ func Pipe2(p []int, flags int) error { } var pp [2]_C_int err := pipe2(&pp, flags) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return err } diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index fff38a84c..f432b0684 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -131,8 +131,10 @@ func Pipe2(p []int, flags int) error { } var pp [2]_C_int err := pipe2(&pp, flags) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return err } @@ -372,9 +374,7 @@ func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) { p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrInet4, nil } @@ -387,9 +387,7 @@ func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, _Socklen, error) { p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) sa.raw.Scope_id = sa.ZoneId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrInet6, nil } @@ -438,9 +436,7 @@ func (sa *SockaddrLinklayer) sockaddr() (unsafe.Pointer, _Socklen, error) { sa.raw.Hatype = sa.Hatype sa.raw.Pkttype = sa.Pkttype sa.raw.Halen = sa.Halen - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrLinklayer, nil } @@ -855,12 +851,10 @@ func (sa *SockaddrTIPC) sockaddr() (unsafe.Pointer, _Socklen, error) { if sa.Addr == nil { return nil, 0, EINVAL } - sa.raw.Family = AF_TIPC sa.raw.Scope = int8(sa.Scope) sa.raw.Addrtype = sa.Addr.tipcAddrtype() sa.raw.Addr = sa.Addr.tipcAddr() - return unsafe.Pointer(&sa.raw), SizeofSockaddrTIPC, nil } @@ -874,9 +868,7 @@ type SockaddrL2TPIP struct { func (sa *SockaddrL2TPIP) sockaddr() (unsafe.Pointer, _Socklen, error) { sa.raw.Family = AF_INET sa.raw.Conn_id = sa.ConnId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrL2TPIP, nil } @@ -892,9 +884,7 @@ func (sa *SockaddrL2TPIP6) sockaddr() (unsafe.Pointer, _Socklen, error) { sa.raw.Family = AF_INET6 sa.raw.Conn_id = sa.ConnId sa.raw.Scope_id = sa.ZoneId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrL2TPIP6, nil } @@ -990,9 +980,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { sa.Hatype = pp.Hatype sa.Pkttype = pp.Pkttype sa.Halen = pp.Halen - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil case AF_UNIX: @@ -1031,18 +1019,14 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { pp := (*RawSockaddrL2TPIP)(unsafe.Pointer(rsa)) sa := new(SockaddrL2TPIP) sa.ConnId = pp.Conn_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil default: pp := (*RawSockaddrInet4)(unsafe.Pointer(rsa)) sa := new(SockaddrInet4) p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil } @@ -1058,9 +1042,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { sa := new(SockaddrL2TPIP6) sa.ConnId = pp.Conn_id sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil default: pp := (*RawSockaddrInet6)(unsafe.Pointer(rsa)) @@ -1068,9 +1050,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil } @@ -1797,6 +1777,16 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri return mount(source, target, fstype, flags, datap) } +//sys mountSetattr(dirfd int, pathname string, flags uint, attr *MountAttr, size uintptr) (err error) = SYS_MOUNT_SETATTR + +// MountSetattr is a wrapper for mount_setattr(2). +// https://man7.org/linux/man-pages/man2/mount_setattr.2.html +// +// Requires kernel >= 5.12. +func MountSetattr(dirfd int, pathname string, flags uint, attr *MountAttr) error { + return mountSetattr(dirfd, pathname, flags, attr, unsafe.Sizeof(*attr)) +} + func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) { if raceenabled { raceReleaseMerge(unsafe.Pointer(&ioSync)) diff --git a/vendor/golang.org/x/sys/unix/syscall_netbsd.go b/vendor/golang.org/x/sys/unix/syscall_netbsd.go index 853d5f0f4..696fed496 100644 --- a/vendor/golang.org/x/sys/unix/syscall_netbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_netbsd.go @@ -110,14 +110,8 @@ func direntNamlen(buf []byte) (uint64, bool) { return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen)) } -//sysnb pipe() (fd1 int, fd2 int, err error) - func Pipe(p []int) (err error) { - if len(p) != 2 { - return EINVAL - } - p[0], p[1], err = pipe() - return + return Pipe2(p, 0) } //sysnb pipe2(p *[2]_C_int, flags int) (err error) @@ -128,8 +122,10 @@ func Pipe2(p []int, flags int) error { } var pp [2]_C_int err := pipe2(&pp, flags) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return err } diff --git a/vendor/golang.org/x/sys/unix/syscall_openbsd.go b/vendor/golang.org/x/sys/unix/syscall_openbsd.go index 22b550385..11b1d419d 100644 --- a/vendor/golang.org/x/sys/unix/syscall_openbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_openbsd.go @@ -87,8 +87,10 @@ func Pipe2(p []int, flags int) error { } var pp [2]_C_int err := pipe2(&pp, flags) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return err } diff --git a/vendor/golang.org/x/sys/unix/syscall_solaris.go b/vendor/golang.org/x/sys/unix/syscall_solaris.go index d2a6495c7..5c813921e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_solaris.go +++ b/vendor/golang.org/x/sys/unix/syscall_solaris.go @@ -66,8 +66,10 @@ func Pipe(p []int) (err error) { if n != 0 { return err } - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return nil } @@ -79,8 +81,10 @@ func Pipe2(p []int, flags int) error { } var pp [2]_C_int err := pipe2(&pp, flags) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return err } @@ -92,9 +96,7 @@ func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) { p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrInet4, nil } @@ -107,9 +109,7 @@ func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, _Socklen, error) { p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) sa.raw.Scope_id = sa.ZoneId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), SizeofSockaddrInet6, nil } @@ -417,9 +417,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { sa := new(SockaddrInet4) p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil case AF_INET6: @@ -428,9 +426,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil } return nil, EAFNOSUPPORT diff --git a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go index 1ffd8bfcf..f8616f454 100644 --- a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go +++ b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go @@ -67,9 +67,7 @@ func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) { p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), _Socklen(sa.raw.Len), nil } @@ -83,9 +81,7 @@ func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, _Socklen, error) { p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) sa.raw.Scope_id = sa.ZoneId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), _Socklen(sa.raw.Len), nil } @@ -144,9 +140,7 @@ func anyToSockaddr(_ int, rsa *RawSockaddrAny) (Sockaddr, error) { sa := new(SockaddrInet4) p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil case AF_INET6: @@ -155,9 +149,7 @@ func anyToSockaddr(_ int, rsa *RawSockaddrAny) (Sockaddr, error) { p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil } return nil, EAFNOSUPPORT @@ -587,8 +579,10 @@ func Pipe(p []int) (err error) { } var pp [2]_C_int err = pipe(&pp) - p[0] = int(pp[0]) - p[1] = int(pp[1]) + if err == nil { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + } return } diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 3bbc52751..bcc45d108 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -1,4 +1,4 @@ -// Code generated by mkmerge.go; DO NOT EDIT. +// Code generated by mkmerge; DO NOT EDIT. //go:build linux // +build linux @@ -1476,6 +1476,18 @@ const ( MNT_FORCE = 0x1 MODULE_INIT_IGNORE_MODVERSIONS = 0x1 MODULE_INIT_IGNORE_VERMAGIC = 0x2 + MOUNT_ATTR_IDMAP = 0x100000 + MOUNT_ATTR_NOATIME = 0x10 + MOUNT_ATTR_NODEV = 0x4 + MOUNT_ATTR_NODIRATIME = 0x80 + MOUNT_ATTR_NOEXEC = 0x8 + MOUNT_ATTR_NOSUID = 0x2 + MOUNT_ATTR_NOSYMFOLLOW = 0x200000 + MOUNT_ATTR_RDONLY = 0x1 + MOUNT_ATTR_RELATIME = 0x0 + MOUNT_ATTR_SIZE_VER0 = 0x20 + MOUNT_ATTR_STRICTATIME = 0x20 + MOUNT_ATTR__ATIME = 0x70 MSDOS_SUPER_MAGIC = 0x4d44 MSG_BATCH = 0x40000 MSG_CMSG_CLOEXEC = 0x40000000 @@ -2814,6 +2826,13 @@ const ( WDIOS_TEMPPANIC = 0x4 WDIOS_UNKNOWN = -0x1 WEXITED = 0x4 + WGALLOWEDIP_A_MAX = 0x3 + WGDEVICE_A_MAX = 0x8 + WGPEER_A_MAX = 0xa + WG_CMD_MAX = 0x1 + WG_GENL_NAME = "wireguard" + WG_GENL_VERSION = 0x1 + WG_KEY_LEN = 0x20 WIN_ACKMEDIACHANGE = 0xdb WIN_CHECKPOWERMODE1 = 0xe5 WIN_CHECKPOWERMODE2 = 0x98 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 80c790840..3ca40ca7f 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -5,7 +5,7 @@ // +build 386,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m32 /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m32 /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index da55638a4..ead332091 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -5,7 +5,7 @@ // +build amd64,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m64 /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m64 /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index c3da063c7..39bdc9455 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -5,7 +5,7 @@ // +build arm,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index fd9f0d1db..9aec987db 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -5,7 +5,7 @@ // +build arm64,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index c358ada0d..a8bba9491 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -5,7 +5,7 @@ // +build mips,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 1dc1ee16b..ee9e7e202 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -5,7 +5,7 @@ // +build mips64,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 3ae187dd9..ba4b288a3 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -5,7 +5,7 @@ // +build mips64le,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 39895f0dd..bc93afc36 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -5,7 +5,7 @@ // +build mipsle,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index a98a45537..9295e6947 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -5,7 +5,7 @@ // +build ppc,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 0a8fbbffa..1fa081c9a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -5,7 +5,7 @@ // +build ppc64,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index cb835a144..74b321149 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -5,7 +5,7 @@ // +build ppc64le,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 73cf6554b..c91c8ac5b 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -5,7 +5,7 @@ // +build riscv64,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 04b6dfaf5..b66bf2228 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -5,7 +5,7 @@ // +build s390x,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index 8c87d979d..f7fb149b0 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -5,7 +5,7 @@ // +build sparc64,linux // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/_const.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/_const.go package unix diff --git a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go index 91a23cc72..85e0cc386 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go @@ -17,6 +17,7 @@ int getdirent(int, uintptr_t, size_t); int wait4(int, uintptr_t, int, uintptr_t); int ioctl(int, int, uintptr_t); int fcntl(uintptr_t, int, uintptr_t); +int fsync_range(int, int, long long, long long); int acct(uintptr_t); int chdir(uintptr_t); int chroot(uintptr_t); @@ -29,7 +30,6 @@ int fchmod(int, unsigned int); int fchmodat(int, uintptr_t, unsigned int, int); int fchownat(int, uintptr_t, int, int, int); int fdatasync(int); -int fsync(int); int getpgid(int); int getpgrp(); int getpid(); @@ -255,6 +255,16 @@ func fcntl(fd int, cmd int, arg int) (val int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func fsyncRange(fd int, how int, start int64, length int64) (err error) { + r0, er := C.fsync_range(C.int(fd), C.int(how), C.longlong(start), C.longlong(length)) + if r0 == -1 && er != nil { + err = er + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Acct(path string) (err error) { _p0 := uintptr(unsafe.Pointer(C.CString(path))) r0, er := C.acct(C.uintptr_t(_p0)) @@ -379,16 +389,6 @@ func Fdatasync(fd int) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func Fsync(fd int) (err error) { - r0, er := C.fsync(C.int(fd)) - if r0 == -1 && er != nil { - err = er - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Getpgid(pid int) (pgid int, err error) { r0, er := C.getpgid(C.int(pid)) pgid = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go index 33c2609b8..f1d4a73b0 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go @@ -135,6 +135,16 @@ func fcntl(fd int, cmd int, arg int) (val int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func fsyncRange(fd int, how int, start int64, length int64) (err error) { + _, e1 := callfsync_range(fd, how, start, length) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Acct(path string) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) @@ -283,16 +293,6 @@ func Fdatasync(fd int) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func Fsync(fd int) (err error) { - _, e1 := callfsync(fd) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Getpgid(pid int) (pgid int, err error) { r0, e1 := callgetpgid(pid) pgid = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go index 8b737fa97..2caa5adf9 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go @@ -18,6 +18,7 @@ import ( //go:cgo_import_dynamic libc_wait4 wait4 "libc.a/shr_64.o" //go:cgo_import_dynamic libc_ioctl ioctl "libc.a/shr_64.o" //go:cgo_import_dynamic libc_fcntl fcntl "libc.a/shr_64.o" +//go:cgo_import_dynamic libc_fsync_range fsync_range "libc.a/shr_64.o" //go:cgo_import_dynamic libc_acct acct "libc.a/shr_64.o" //go:cgo_import_dynamic libc_chdir chdir "libc.a/shr_64.o" //go:cgo_import_dynamic libc_chroot chroot "libc.a/shr_64.o" @@ -30,7 +31,6 @@ import ( //go:cgo_import_dynamic libc_fchmodat fchmodat "libc.a/shr_64.o" //go:cgo_import_dynamic libc_fchownat fchownat "libc.a/shr_64.o" //go:cgo_import_dynamic libc_fdatasync fdatasync "libc.a/shr_64.o" -//go:cgo_import_dynamic libc_fsync fsync "libc.a/shr_64.o" //go:cgo_import_dynamic libc_getpgid getpgid "libc.a/shr_64.o" //go:cgo_import_dynamic libc_getpgrp getpgrp "libc.a/shr_64.o" //go:cgo_import_dynamic libc_getpid getpid "libc.a/shr_64.o" @@ -136,6 +136,7 @@ import ( //go:linkname libc_wait4 libc_wait4 //go:linkname libc_ioctl libc_ioctl //go:linkname libc_fcntl libc_fcntl +//go:linkname libc_fsync_range libc_fsync_range //go:linkname libc_acct libc_acct //go:linkname libc_chdir libc_chdir //go:linkname libc_chroot libc_chroot @@ -148,7 +149,6 @@ import ( //go:linkname libc_fchmodat libc_fchmodat //go:linkname libc_fchownat libc_fchownat //go:linkname libc_fdatasync libc_fdatasync -//go:linkname libc_fsync libc_fsync //go:linkname libc_getpgid libc_getpgid //go:linkname libc_getpgrp libc_getpgrp //go:linkname libc_getpid libc_getpid @@ -257,6 +257,7 @@ var ( libc_wait4, libc_ioctl, libc_fcntl, + libc_fsync_range, libc_acct, libc_chdir, libc_chroot, @@ -269,7 +270,6 @@ var ( libc_fchmodat, libc_fchownat, libc_fdatasync, - libc_fsync, libc_getpgid, libc_getpgrp, libc_getpid, @@ -430,6 +430,13 @@ func callfcntl(fd uintptr, cmd int, arg uintptr) (r1 uintptr, e1 Errno) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func callfsync_range(fd int, how int, start int64, length int64) (r1 uintptr, e1 Errno) { + r1, _, e1 = syscall6(uintptr(unsafe.Pointer(&libc_fsync_range)), 4, uintptr(fd), uintptr(how), uintptr(start), uintptr(length), 0, 0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func callacct(_p0 uintptr) (r1 uintptr, e1 Errno) { r1, _, e1 = syscall6(uintptr(unsafe.Pointer(&libc_acct)), 1, _p0, 0, 0, 0, 0, 0) return @@ -514,13 +521,6 @@ func callfdatasync(fd int) (r1 uintptr, e1 Errno) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func callfsync(fd int) (r1 uintptr, e1 Errno) { - r1, _, e1 = syscall6(uintptr(unsafe.Pointer(&libc_fsync)), 1, uintptr(fd), 0, 0, 0, 0, 0) - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func callgetpgid(pid int) (r1 uintptr, e1 Errno) { r1, _, e1 = rawSyscall6(uintptr(unsafe.Pointer(&libc_getpgid)), 1, uintptr(pid), 0, 0, 0, 0, 0) return diff --git a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go index 3c260917e..944a714b1 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go @@ -16,6 +16,7 @@ int getdirent(int, uintptr_t, size_t); int wait4(int, uintptr_t, int, uintptr_t); int ioctl(int, int, uintptr_t); int fcntl(uintptr_t, int, uintptr_t); +int fsync_range(int, int, long long, long long); int acct(uintptr_t); int chdir(uintptr_t); int chroot(uintptr_t); @@ -28,7 +29,6 @@ int fchmod(int, unsigned int); int fchmodat(int, uintptr_t, unsigned int, int); int fchownat(int, uintptr_t, int, int, int); int fdatasync(int); -int fsync(int); int getpgid(int); int getpgrp(); int getpid(); @@ -199,6 +199,14 @@ func callfcntl(fd uintptr, cmd int, arg uintptr) (r1 uintptr, e1 Errno) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func callfsync_range(fd int, how int, start int64, length int64) (r1 uintptr, e1 Errno) { + r1 = uintptr(C.fsync_range(C.int(fd), C.int(how), C.longlong(start), C.longlong(length))) + e1 = syscall.GetErrno() + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func callacct(_p0 uintptr) (r1 uintptr, e1 Errno) { r1 = uintptr(C.acct(C.uintptr_t(_p0))) e1 = syscall.GetErrno() @@ -295,14 +303,6 @@ func callfdatasync(fd int) (r1 uintptr, e1 Errno) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func callfsync(fd int) (r1 uintptr, e1 Errno) { - r1 = uintptr(C.fsync(C.int(fd))) - e1 = syscall.GetErrno() - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func callgetpgid(pid int) (r1 uintptr, e1 Errno) { r1 = uintptr(C.getpgid(C.int(pid))) e1 = syscall.GetErrno() diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 4f5da1f54..93edda4c4 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -1,4 +1,4 @@ -// Code generated by mkmerge.go; DO NOT EDIT. +// Code generated by mkmerge; DO NOT EDIT. //go:build linux // +build linux @@ -409,6 +409,21 @@ func mount(source string, target string, fstype string, flags uintptr, data *byt // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func mountSetattr(dirfd int, pathname string, flags uint, attr *MountAttr, size uintptr) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(pathname) + if err != nil { + return + } + _, _, e1 := Syscall6(SYS_MOUNT_SETATTR, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(unsafe.Pointer(attr)), uintptr(size), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Acct(path string) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go index 4726ab30a..51d0c0742 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go @@ -351,18 +351,6 @@ func Munlockall() (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func pipe() (fd1 int, fd2 int, err error) { - r0, r1, e1 := RawSyscall(SYS_PIPE, 0, 0, 0) - fd1 = int(r0) - fd2 = int(r1) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func pipe2(p *[2]_C_int, flags int) (err error) { _, _, e1 := RawSyscall(SYS_PIPE2, uintptr(unsafe.Pointer(p)), uintptr(flags), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go index fe71456db..df2efb6db 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go @@ -351,18 +351,6 @@ func Munlockall() (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func pipe() (fd1 int, fd2 int, err error) { - r0, r1, e1 := RawSyscall(SYS_PIPE, 0, 0, 0) - fd1 = int(r0) - fd2 = int(r1) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func pipe2(p *[2]_C_int, flags int) (err error) { _, _, e1 := RawSyscall(SYS_PIPE2, uintptr(unsafe.Pointer(p)), uintptr(flags), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go index 0b5b2f014..c8536c2c9 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go @@ -351,18 +351,6 @@ func Munlockall() (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func pipe() (fd1 int, fd2 int, err error) { - r0, r1, e1 := RawSyscall(SYS_PIPE, 0, 0, 0) - fd1 = int(r0) - fd2 = int(r1) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func pipe2(p *[2]_C_int, flags int) (err error) { _, _, e1 := RawSyscall(SYS_PIPE2, uintptr(unsafe.Pointer(p)), uintptr(flags), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go index bfca28648..8b981bfc2 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go @@ -351,18 +351,6 @@ func Munlockall() (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func pipe() (fd1 int, fd2 int, err error) { - r0, r1, e1 := RawSyscall(SYS_PIPE, 0, 0, 0) - fd1 = int(r0) - fd2 = int(r1) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func pipe2(p *[2]_C_int, flags int) (err error) { _, _, e1 := RawSyscall(SYS_PIPE2, uintptr(unsafe.Pointer(p)), uintptr(flags), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 620a6702f..f6f0d79c4 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -1,4 +1,4 @@ -// Code generated by mkmerge.go; DO NOT EDIT. +// Code generated by mkmerge; DO NOT EDIT. //go:build linux // +build linux @@ -743,6 +743,8 @@ const ( AT_STATX_FORCE_SYNC = 0x2000 AT_STATX_DONT_SYNC = 0x4000 + AT_RECURSIVE = 0x8000 + AT_SYMLINK_FOLLOW = 0x400 AT_SYMLINK_NOFOLLOW = 0x100 @@ -865,6 +867,7 @@ const ( CTRL_CMD_NEWMCAST_GRP = 0x7 CTRL_CMD_DELMCAST_GRP = 0x8 CTRL_CMD_GETMCAST_GRP = 0x9 + CTRL_CMD_GETPOLICY = 0xa CTRL_ATTR_UNSPEC = 0x0 CTRL_ATTR_FAMILY_ID = 0x1 CTRL_ATTR_FAMILY_NAME = 0x2 @@ -873,12 +876,19 @@ const ( CTRL_ATTR_MAXATTR = 0x5 CTRL_ATTR_OPS = 0x6 CTRL_ATTR_MCAST_GROUPS = 0x7 + CTRL_ATTR_POLICY = 0x8 + CTRL_ATTR_OP_POLICY = 0x9 + CTRL_ATTR_OP = 0xa CTRL_ATTR_OP_UNSPEC = 0x0 CTRL_ATTR_OP_ID = 0x1 CTRL_ATTR_OP_FLAGS = 0x2 CTRL_ATTR_MCAST_GRP_UNSPEC = 0x0 CTRL_ATTR_MCAST_GRP_NAME = 0x1 CTRL_ATTR_MCAST_GRP_ID = 0x2 + CTRL_ATTR_POLICY_UNSPEC = 0x0 + CTRL_ATTR_POLICY_DO = 0x1 + CTRL_ATTR_POLICY_DUMP = 0x2 + CTRL_ATTR_POLICY_DUMP_MAX = 0x2 ) const ( @@ -3959,3 +3969,77 @@ const ( SHM_RDONLY = 0x1000 SHM_RND = 0x2000 ) + +type MountAttr struct { + Attr_set uint64 + Attr_clr uint64 + Propagation uint64 + Userns_fd uint64 +} + +const ( + WG_CMD_GET_DEVICE = 0x0 + WG_CMD_SET_DEVICE = 0x1 + WGDEVICE_F_REPLACE_PEERS = 0x1 + WGDEVICE_A_UNSPEC = 0x0 + WGDEVICE_A_IFINDEX = 0x1 + WGDEVICE_A_IFNAME = 0x2 + WGDEVICE_A_PRIVATE_KEY = 0x3 + WGDEVICE_A_PUBLIC_KEY = 0x4 + WGDEVICE_A_FLAGS = 0x5 + WGDEVICE_A_LISTEN_PORT = 0x6 + WGDEVICE_A_FWMARK = 0x7 + WGDEVICE_A_PEERS = 0x8 + WGPEER_F_REMOVE_ME = 0x1 + WGPEER_F_REPLACE_ALLOWEDIPS = 0x2 + WGPEER_F_UPDATE_ONLY = 0x4 + WGPEER_A_UNSPEC = 0x0 + WGPEER_A_PUBLIC_KEY = 0x1 + WGPEER_A_PRESHARED_KEY = 0x2 + WGPEER_A_FLAGS = 0x3 + WGPEER_A_ENDPOINT = 0x4 + WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL = 0x5 + WGPEER_A_LAST_HANDSHAKE_TIME = 0x6 + WGPEER_A_RX_BYTES = 0x7 + WGPEER_A_TX_BYTES = 0x8 + WGPEER_A_ALLOWEDIPS = 0x9 + WGPEER_A_PROTOCOL_VERSION = 0xa + WGALLOWEDIP_A_UNSPEC = 0x0 + WGALLOWEDIP_A_FAMILY = 0x1 + WGALLOWEDIP_A_IPADDR = 0x2 + WGALLOWEDIP_A_CIDR_MASK = 0x3 +) + +const ( + NL_ATTR_TYPE_INVALID = 0x0 + NL_ATTR_TYPE_FLAG = 0x1 + NL_ATTR_TYPE_U8 = 0x2 + NL_ATTR_TYPE_U16 = 0x3 + NL_ATTR_TYPE_U32 = 0x4 + NL_ATTR_TYPE_U64 = 0x5 + NL_ATTR_TYPE_S8 = 0x6 + NL_ATTR_TYPE_S16 = 0x7 + NL_ATTR_TYPE_S32 = 0x8 + NL_ATTR_TYPE_S64 = 0x9 + NL_ATTR_TYPE_BINARY = 0xa + NL_ATTR_TYPE_STRING = 0xb + NL_ATTR_TYPE_NUL_STRING = 0xc + NL_ATTR_TYPE_NESTED = 0xd + NL_ATTR_TYPE_NESTED_ARRAY = 0xe + NL_ATTR_TYPE_BITFIELD32 = 0xf + + NL_POLICY_TYPE_ATTR_UNSPEC = 0x0 + NL_POLICY_TYPE_ATTR_TYPE = 0x1 + NL_POLICY_TYPE_ATTR_MIN_VALUE_S = 0x2 + NL_POLICY_TYPE_ATTR_MAX_VALUE_S = 0x3 + NL_POLICY_TYPE_ATTR_MIN_VALUE_U = 0x4 + NL_POLICY_TYPE_ATTR_MAX_VALUE_U = 0x5 + NL_POLICY_TYPE_ATTR_MIN_LENGTH = 0x6 + NL_POLICY_TYPE_ATTR_MAX_LENGTH = 0x7 + NL_POLICY_TYPE_ATTR_POLICY_IDX = 0x8 + NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE = 0x9 + NL_POLICY_TYPE_ATTR_BITFIELD32_MASK = 0xa + NL_POLICY_TYPE_ATTR_PAD = 0xb + NL_POLICY_TYPE_ATTR_MASK = 0xc + NL_POLICY_TYPE_ATTR_MAX = 0xc +) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go index eeeb9aa39..bea254945 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m32 /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m32 /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build 386 && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go index d30e1155c..b8c8f2894 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m64 /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -m64 /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build amd64 && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go index 69d029752..4db443016 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build arm && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go index 28a0455bc..3ebcad8a8 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build arm64 && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go index 64a845483..3eb33e48a 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build mips && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go index a1b7dee41..79a944672 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build mips64 && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go index 936fa6a26..8f4b107ca 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build mips64le && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go index 5dd546fbf..e4eb21798 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build mipsle && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go index 947b32e43..d5b21f0f7 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build ppc && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go index 2a606151b..5188d142b 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build ppc64 && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go index d0d735d02..de4dd4c73 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build ppc64le && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go index 95e3d6d06..dccbf9b06 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build riscv64 && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go index cccf1ef26..635880610 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include -fsigned-char /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build s390x && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go index 44fcbe4e9..765edc13f 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go @@ -1,4 +1,4 @@ -// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/linux/types.go | go run mkpost.go +// cgo -godefs -- -Wall -Werror -static -I/tmp/include /build/unix/linux/types.go | go run mkpost.go // Code generated by the command above; see README.md. DO NOT EDIT. //go:build sparc64 && linux diff --git a/vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go b/vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go index 2a8b1e6f7..baf5fe650 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go @@ -564,12 +564,11 @@ type Uvmexp struct { Kmapent int32 } -const SizeofClockinfo = 0x14 +const SizeofClockinfo = 0x10 type Clockinfo struct { - Hz int32 - Tick int32 - Tickadj int32 - Stathz int32 - Profhz int32 + Hz int32 + Tick int32 + Stathz int32 + Profhz int32 } diff --git a/vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go index b1759cf70..e21ae8ecf 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go @@ -564,12 +564,11 @@ type Uvmexp struct { Kmapent int32 } -const SizeofClockinfo = 0x14 +const SizeofClockinfo = 0x10 type Clockinfo struct { - Hz int32 - Tick int32 - Tickadj int32 - Stathz int32 - Profhz int32 + Hz int32 + Tick int32 + Stathz int32 + Profhz int32 } diff --git a/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go b/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go index e807de206..f190651cd 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go @@ -565,12 +565,11 @@ type Uvmexp struct { Kmapent int32 } -const SizeofClockinfo = 0x14 +const SizeofClockinfo = 0x10 type Clockinfo struct { - Hz int32 - Tick int32 - Tickadj int32 - Stathz int32 - Profhz int32 + Hz int32 + Tick int32 + Stathz int32 + Profhz int32 } diff --git a/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go index ff3aecaee..84747c582 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go @@ -558,12 +558,11 @@ type Uvmexp struct { Kmapent int32 } -const SizeofClockinfo = 0x14 +const SizeofClockinfo = 0x10 type Clockinfo struct { - Hz int32 - Tick int32 - Tickadj int32 - Stathz int32 - Profhz int32 + Hz int32 + Tick int32 + Stathz int32 + Profhz int32 } diff --git a/vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go index 9ecda6917..ac5c8b637 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go @@ -558,12 +558,11 @@ type Uvmexp struct { Kmapent int32 } -const SizeofClockinfo = 0x14 +const SizeofClockinfo = 0x10 type Clockinfo struct { - Hz int32 - Tick int32 - Tickadj int32 - Stathz int32 - Profhz int32 + Hz int32 + Tick int32 + Stathz int32 + Profhz int32 } diff --git a/vendor/golang.org/x/sys/windows/exec_windows.go b/vendor/golang.org/x/sys/windows/exec_windows.go index 7a11e83b7..855698bb2 100644 --- a/vendor/golang.org/x/sys/windows/exec_windows.go +++ b/vendor/golang.org/x/sys/windows/exec_windows.go @@ -9,8 +9,6 @@ package windows import ( errorspkg "errors" "unsafe" - - "golang.org/x/sys/internal/unsafeheader" ) // EscapeArg rewrites command line argument s as prescribed @@ -147,8 +145,12 @@ func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListCo } return nil, err } + alloc, err := LocalAlloc(LMEM_FIXED, uint32(size)) + if err != nil { + return nil, err + } // size is guaranteed to be ≥1 by InitializeProcThreadAttributeList. - al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(&make([]byte, size)[0]))} + al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))} err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size) if err != nil { return nil, err @@ -157,36 +159,17 @@ func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListCo } // Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute. -// Note that the value passed to this function will be copied into memory -// allocated by LocalAlloc, the contents of which should not contain any -// Go-managed pointers, even if the passed value itself is a Go-managed -// pointer. func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error { - alloc, err := LocalAlloc(LMEM_FIXED, uint32(size)) - if err != nil { - return err - } - var src, dst []byte - hdr := (*unsafeheader.Slice)(unsafe.Pointer(&src)) - hdr.Data = value - hdr.Cap = int(size) - hdr.Len = int(size) - hdr = (*unsafeheader.Slice)(unsafe.Pointer(&dst)) - hdr.Data = unsafe.Pointer(alloc) - hdr.Cap = int(size) - hdr.Len = int(size) - copy(dst, src) - al.heapAllocations = append(al.heapAllocations, alloc) - return updateProcThreadAttribute(al.data, 0, attribute, unsafe.Pointer(alloc), size, nil, nil) + al.pointers = append(al.pointers, value) + return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil) } // Delete frees ProcThreadAttributeList's resources. func (al *ProcThreadAttributeListContainer) Delete() { deleteProcThreadAttributeList(al.data) - for i := range al.heapAllocations { - LocalFree(Handle(al.heapAllocations[i])) - } - al.heapAllocations = nil + LocalFree(Handle(unsafe.Pointer(al.data))) + al.data = nil + al.pointers = nil } // List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx. diff --git a/vendor/golang.org/x/sys/windows/mksyscall.go b/vendor/golang.org/x/sys/windows/mksyscall.go index 610291098..8563f79c5 100644 --- a/vendor/golang.org/x/sys/windows/mksyscall.go +++ b/vendor/golang.org/x/sys/windows/mksyscall.go @@ -7,4 +7,4 @@ package windows -//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go setupapi_windows.go diff --git a/vendor/golang.org/x/sys/windows/setupapi_windows.go b/vendor/golang.org/x/sys/windows/setupapi_windows.go new file mode 100644 index 000000000..14027da3f --- /dev/null +++ b/vendor/golang.org/x/sys/windows/setupapi_windows.go @@ -0,0 +1,1425 @@ +// Copyright 2021 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 windows + +import ( + "encoding/binary" + "errors" + "fmt" + "runtime" + "strings" + "syscall" + "unsafe" +) + +// This file contains functions that wrap SetupAPI.dll and CfgMgr32.dll, +// core system functions for managing hardware devices, drivers, and the PnP tree. +// Information about these APIs can be found at: +// https://docs.microsoft.com/en-us/windows-hardware/drivers/install/setupapi +// https://docs.microsoft.com/en-us/windows/win32/devinst/cfgmgr32- + +const ( + ERROR_EXPECTED_SECTION_NAME Errno = 0x20000000 | 0xC0000000 | 0 + ERROR_BAD_SECTION_NAME_LINE Errno = 0x20000000 | 0xC0000000 | 1 + ERROR_SECTION_NAME_TOO_LONG Errno = 0x20000000 | 0xC0000000 | 2 + ERROR_GENERAL_SYNTAX Errno = 0x20000000 | 0xC0000000 | 3 + ERROR_WRONG_INF_STYLE Errno = 0x20000000 | 0xC0000000 | 0x100 + ERROR_SECTION_NOT_FOUND Errno = 0x20000000 | 0xC0000000 | 0x101 + ERROR_LINE_NOT_FOUND Errno = 0x20000000 | 0xC0000000 | 0x102 + ERROR_NO_BACKUP Errno = 0x20000000 | 0xC0000000 | 0x103 + ERROR_NO_ASSOCIATED_CLASS Errno = 0x20000000 | 0xC0000000 | 0x200 + ERROR_CLASS_MISMATCH Errno = 0x20000000 | 0xC0000000 | 0x201 + ERROR_DUPLICATE_FOUND Errno = 0x20000000 | 0xC0000000 | 0x202 + ERROR_NO_DRIVER_SELECTED Errno = 0x20000000 | 0xC0000000 | 0x203 + ERROR_KEY_DOES_NOT_EXIST Errno = 0x20000000 | 0xC0000000 | 0x204 + ERROR_INVALID_DEVINST_NAME Errno = 0x20000000 | 0xC0000000 | 0x205 + ERROR_INVALID_CLASS Errno = 0x20000000 | 0xC0000000 | 0x206 + ERROR_DEVINST_ALREADY_EXISTS Errno = 0x20000000 | 0xC0000000 | 0x207 + ERROR_DEVINFO_NOT_REGISTERED Errno = 0x20000000 | 0xC0000000 | 0x208 + ERROR_INVALID_REG_PROPERTY Errno = 0x20000000 | 0xC0000000 | 0x209 + ERROR_NO_INF Errno = 0x20000000 | 0xC0000000 | 0x20A + ERROR_NO_SUCH_DEVINST Errno = 0x20000000 | 0xC0000000 | 0x20B + ERROR_CANT_LOAD_CLASS_ICON Errno = 0x20000000 | 0xC0000000 | 0x20C + ERROR_INVALID_CLASS_INSTALLER Errno = 0x20000000 | 0xC0000000 | 0x20D + ERROR_DI_DO_DEFAULT Errno = 0x20000000 | 0xC0000000 | 0x20E + ERROR_DI_NOFILECOPY Errno = 0x20000000 | 0xC0000000 | 0x20F + ERROR_INVALID_HWPROFILE Errno = 0x20000000 | 0xC0000000 | 0x210 + ERROR_NO_DEVICE_SELECTED Errno = 0x20000000 | 0xC0000000 | 0x211 + ERROR_DEVINFO_LIST_LOCKED Errno = 0x20000000 | 0xC0000000 | 0x212 + ERROR_DEVINFO_DATA_LOCKED Errno = 0x20000000 | 0xC0000000 | 0x213 + ERROR_DI_BAD_PATH Errno = 0x20000000 | 0xC0000000 | 0x214 + ERROR_NO_CLASSINSTALL_PARAMS Errno = 0x20000000 | 0xC0000000 | 0x215 + ERROR_FILEQUEUE_LOCKED Errno = 0x20000000 | 0xC0000000 | 0x216 + ERROR_BAD_SERVICE_INSTALLSECT Errno = 0x20000000 | 0xC0000000 | 0x217 + ERROR_NO_CLASS_DRIVER_LIST Errno = 0x20000000 | 0xC0000000 | 0x218 + ERROR_NO_ASSOCIATED_SERVICE Errno = 0x20000000 | 0xC0000000 | 0x219 + ERROR_NO_DEFAULT_DEVICE_INTERFACE Errno = 0x20000000 | 0xC0000000 | 0x21A + ERROR_DEVICE_INTERFACE_ACTIVE Errno = 0x20000000 | 0xC0000000 | 0x21B + ERROR_DEVICE_INTERFACE_REMOVED Errno = 0x20000000 | 0xC0000000 | 0x21C + ERROR_BAD_INTERFACE_INSTALLSECT Errno = 0x20000000 | 0xC0000000 | 0x21D + ERROR_NO_SUCH_INTERFACE_CLASS Errno = 0x20000000 | 0xC0000000 | 0x21E + ERROR_INVALID_REFERENCE_STRING Errno = 0x20000000 | 0xC0000000 | 0x21F + ERROR_INVALID_MACHINENAME Errno = 0x20000000 | 0xC0000000 | 0x220 + ERROR_REMOTE_COMM_FAILURE Errno = 0x20000000 | 0xC0000000 | 0x221 + ERROR_MACHINE_UNAVAILABLE Errno = 0x20000000 | 0xC0000000 | 0x222 + ERROR_NO_CONFIGMGR_SERVICES Errno = 0x20000000 | 0xC0000000 | 0x223 + ERROR_INVALID_PROPPAGE_PROVIDER Errno = 0x20000000 | 0xC0000000 | 0x224 + ERROR_NO_SUCH_DEVICE_INTERFACE Errno = 0x20000000 | 0xC0000000 | 0x225 + ERROR_DI_POSTPROCESSING_REQUIRED Errno = 0x20000000 | 0xC0000000 | 0x226 + ERROR_INVALID_COINSTALLER Errno = 0x20000000 | 0xC0000000 | 0x227 + ERROR_NO_COMPAT_DRIVERS Errno = 0x20000000 | 0xC0000000 | 0x228 + ERROR_NO_DEVICE_ICON Errno = 0x20000000 | 0xC0000000 | 0x229 + ERROR_INVALID_INF_LOGCONFIG Errno = 0x20000000 | 0xC0000000 | 0x22A + ERROR_DI_DONT_INSTALL Errno = 0x20000000 | 0xC0000000 | 0x22B + ERROR_INVALID_FILTER_DRIVER Errno = 0x20000000 | 0xC0000000 | 0x22C + ERROR_NON_WINDOWS_NT_DRIVER Errno = 0x20000000 | 0xC0000000 | 0x22D + ERROR_NON_WINDOWS_DRIVER Errno = 0x20000000 | 0xC0000000 | 0x22E + ERROR_NO_CATALOG_FOR_OEM_INF Errno = 0x20000000 | 0xC0000000 | 0x22F + ERROR_DEVINSTALL_QUEUE_NONNATIVE Errno = 0x20000000 | 0xC0000000 | 0x230 + ERROR_NOT_DISABLEABLE Errno = 0x20000000 | 0xC0000000 | 0x231 + ERROR_CANT_REMOVE_DEVINST Errno = 0x20000000 | 0xC0000000 | 0x232 + ERROR_INVALID_TARGET Errno = 0x20000000 | 0xC0000000 | 0x233 + ERROR_DRIVER_NONNATIVE Errno = 0x20000000 | 0xC0000000 | 0x234 + ERROR_IN_WOW64 Errno = 0x20000000 | 0xC0000000 | 0x235 + ERROR_SET_SYSTEM_RESTORE_POINT Errno = 0x20000000 | 0xC0000000 | 0x236 + ERROR_SCE_DISABLED Errno = 0x20000000 | 0xC0000000 | 0x238 + ERROR_UNKNOWN_EXCEPTION Errno = 0x20000000 | 0xC0000000 | 0x239 + ERROR_PNP_REGISTRY_ERROR Errno = 0x20000000 | 0xC0000000 | 0x23A + ERROR_REMOTE_REQUEST_UNSUPPORTED Errno = 0x20000000 | 0xC0000000 | 0x23B + ERROR_NOT_AN_INSTALLED_OEM_INF Errno = 0x20000000 | 0xC0000000 | 0x23C + ERROR_INF_IN_USE_BY_DEVICES Errno = 0x20000000 | 0xC0000000 | 0x23D + ERROR_DI_FUNCTION_OBSOLETE Errno = 0x20000000 | 0xC0000000 | 0x23E + ERROR_NO_AUTHENTICODE_CATALOG Errno = 0x20000000 | 0xC0000000 | 0x23F + ERROR_AUTHENTICODE_DISALLOWED Errno = 0x20000000 | 0xC0000000 | 0x240 + ERROR_AUTHENTICODE_TRUSTED_PUBLISHER Errno = 0x20000000 | 0xC0000000 | 0x241 + ERROR_AUTHENTICODE_TRUST_NOT_ESTABLISHED Errno = 0x20000000 | 0xC0000000 | 0x242 + ERROR_AUTHENTICODE_PUBLISHER_NOT_TRUSTED Errno = 0x20000000 | 0xC0000000 | 0x243 + ERROR_SIGNATURE_OSATTRIBUTE_MISMATCH Errno = 0x20000000 | 0xC0000000 | 0x244 + ERROR_ONLY_VALIDATE_VIA_AUTHENTICODE Errno = 0x20000000 | 0xC0000000 | 0x245 + ERROR_DEVICE_INSTALLER_NOT_READY Errno = 0x20000000 | 0xC0000000 | 0x246 + ERROR_DRIVER_STORE_ADD_FAILED Errno = 0x20000000 | 0xC0000000 | 0x247 + ERROR_DEVICE_INSTALL_BLOCKED Errno = 0x20000000 | 0xC0000000 | 0x248 + ERROR_DRIVER_INSTALL_BLOCKED Errno = 0x20000000 | 0xC0000000 | 0x249 + ERROR_WRONG_INF_TYPE Errno = 0x20000000 | 0xC0000000 | 0x24A + ERROR_FILE_HASH_NOT_IN_CATALOG Errno = 0x20000000 | 0xC0000000 | 0x24B + ERROR_DRIVER_STORE_DELETE_FAILED Errno = 0x20000000 | 0xC0000000 | 0x24C + ERROR_UNRECOVERABLE_STACK_OVERFLOW Errno = 0x20000000 | 0xC0000000 | 0x300 + EXCEPTION_SPAPI_UNRECOVERABLE_STACK_OVERFLOW Errno = ERROR_UNRECOVERABLE_STACK_OVERFLOW + ERROR_NO_DEFAULT_INTERFACE_DEVICE Errno = ERROR_NO_DEFAULT_DEVICE_INTERFACE + ERROR_INTERFACE_DEVICE_ACTIVE Errno = ERROR_DEVICE_INTERFACE_ACTIVE + ERROR_INTERFACE_DEVICE_REMOVED Errno = ERROR_DEVICE_INTERFACE_REMOVED + ERROR_NO_SUCH_INTERFACE_DEVICE Errno = ERROR_NO_SUCH_DEVICE_INTERFACE +) + +const ( + MAX_DEVICE_ID_LEN = 200 + MAX_DEVNODE_ID_LEN = MAX_DEVICE_ID_LEN + MAX_GUID_STRING_LEN = 39 // 38 chars + terminator null + MAX_CLASS_NAME_LEN = 32 + MAX_PROFILE_LEN = 80 + MAX_CONFIG_VALUE = 9999 + MAX_INSTANCE_VALUE = 9999 + CONFIGMG_VERSION = 0x0400 +) + +// Maximum string length constants +const ( + LINE_LEN = 256 // Windows 9x-compatible maximum for displayable strings coming from a device INF. + MAX_INF_STRING_LENGTH = 4096 // Actual maximum size of an INF string (including string substitutions). + MAX_INF_SECTION_NAME_LENGTH = 255 // For Windows 9x compatibility, INF section names should be constrained to 32 characters. + MAX_TITLE_LEN = 60 + MAX_INSTRUCTION_LEN = 256 + MAX_LABEL_LEN = 30 + MAX_SERVICE_NAME_LEN = 256 + MAX_SUBTITLE_LEN = 256 +) + +const ( + // SP_MAX_MACHINENAME_LENGTH defines maximum length of a machine name in the format expected by ConfigMgr32 CM_Connect_Machine (i.e., "\\\\MachineName\0"). + SP_MAX_MACHINENAME_LENGTH = MAX_PATH + 3 +) + +// HSPFILEQ is type for setup file queue +type HSPFILEQ uintptr + +// DevInfo holds reference to device information set +type DevInfo Handle + +// DEVINST is a handle usually recognized by cfgmgr32 APIs +type DEVINST uint32 + +// DevInfoData is a device information structure (references a device instance that is a member of a device information set) +type DevInfoData struct { + size uint32 + ClassGUID GUID + DevInst DEVINST + _ uintptr +} + +// DevInfoListDetailData is a structure for detailed information on a device information set (used for SetupDiGetDeviceInfoListDetail which supersedes the functionality of SetupDiGetDeviceInfoListClass). +type DevInfoListDetailData struct { + size uint32 // Use unsafeSizeOf method + ClassGUID GUID + RemoteMachineHandle Handle + remoteMachineName [SP_MAX_MACHINENAME_LENGTH]uint16 +} + +func (*DevInfoListDetailData) unsafeSizeOf() uint32 { + if unsafe.Sizeof(uintptr(0)) == 4 { + // Windows declares this with pshpack1.h + return uint32(unsafe.Offsetof(DevInfoListDetailData{}.remoteMachineName) + unsafe.Sizeof(DevInfoListDetailData{}.remoteMachineName)) + } + return uint32(unsafe.Sizeof(DevInfoListDetailData{})) +} + +func (data *DevInfoListDetailData) RemoteMachineName() string { + return UTF16ToString(data.remoteMachineName[:]) +} + +func (data *DevInfoListDetailData) SetRemoteMachineName(remoteMachineName string) error { + str, err := UTF16FromString(remoteMachineName) + if err != nil { + return err + } + copy(data.remoteMachineName[:], str) + return nil +} + +// DI_FUNCTION is function type for device installer +type DI_FUNCTION uint32 + +const ( + DIF_SELECTDEVICE DI_FUNCTION = 0x00000001 + DIF_INSTALLDEVICE DI_FUNCTION = 0x00000002 + DIF_ASSIGNRESOURCES DI_FUNCTION = 0x00000003 + DIF_PROPERTIES DI_FUNCTION = 0x00000004 + DIF_REMOVE DI_FUNCTION = 0x00000005 + DIF_FIRSTTIMESETUP DI_FUNCTION = 0x00000006 + DIF_FOUNDDEVICE DI_FUNCTION = 0x00000007 + DIF_SELECTCLASSDRIVERS DI_FUNCTION = 0x00000008 + DIF_VALIDATECLASSDRIVERS DI_FUNCTION = 0x00000009 + DIF_INSTALLCLASSDRIVERS DI_FUNCTION = 0x0000000A + DIF_CALCDISKSPACE DI_FUNCTION = 0x0000000B + DIF_DESTROYPRIVATEDATA DI_FUNCTION = 0x0000000C + DIF_VALIDATEDRIVER DI_FUNCTION = 0x0000000D + DIF_DETECT DI_FUNCTION = 0x0000000F + DIF_INSTALLWIZARD DI_FUNCTION = 0x00000010 + DIF_DESTROYWIZARDDATA DI_FUNCTION = 0x00000011 + DIF_PROPERTYCHANGE DI_FUNCTION = 0x00000012 + DIF_ENABLECLASS DI_FUNCTION = 0x00000013 + DIF_DETECTVERIFY DI_FUNCTION = 0x00000014 + DIF_INSTALLDEVICEFILES DI_FUNCTION = 0x00000015 + DIF_UNREMOVE DI_FUNCTION = 0x00000016 + DIF_SELECTBESTCOMPATDRV DI_FUNCTION = 0x00000017 + DIF_ALLOW_INSTALL DI_FUNCTION = 0x00000018 + DIF_REGISTERDEVICE DI_FUNCTION = 0x00000019 + DIF_NEWDEVICEWIZARD_PRESELECT DI_FUNCTION = 0x0000001A + DIF_NEWDEVICEWIZARD_SELECT DI_FUNCTION = 0x0000001B + DIF_NEWDEVICEWIZARD_PREANALYZE DI_FUNCTION = 0x0000001C + DIF_NEWDEVICEWIZARD_POSTANALYZE DI_FUNCTION = 0x0000001D + DIF_NEWDEVICEWIZARD_FINISHINSTALL DI_FUNCTION = 0x0000001E + DIF_INSTALLINTERFACES DI_FUNCTION = 0x00000020 + DIF_DETECTCANCEL DI_FUNCTION = 0x00000021 + DIF_REGISTER_COINSTALLERS DI_FUNCTION = 0x00000022 + DIF_ADDPROPERTYPAGE_ADVANCED DI_FUNCTION = 0x00000023 + DIF_ADDPROPERTYPAGE_BASIC DI_FUNCTION = 0x00000024 + DIF_TROUBLESHOOTER DI_FUNCTION = 0x00000026 + DIF_POWERMESSAGEWAKE DI_FUNCTION = 0x00000027 + DIF_ADDREMOTEPROPERTYPAGE_ADVANCED DI_FUNCTION = 0x00000028 + DIF_UPDATEDRIVER_UI DI_FUNCTION = 0x00000029 + DIF_FINISHINSTALL_ACTION DI_FUNCTION = 0x0000002A +) + +// DevInstallParams is device installation parameters structure (associated with a particular device information element, or globally with a device information set) +type DevInstallParams struct { + size uint32 + Flags DI_FLAGS + FlagsEx DI_FLAGSEX + hwndParent uintptr + InstallMsgHandler uintptr + InstallMsgHandlerContext uintptr + FileQueue HSPFILEQ + _ uintptr + _ uint32 + driverPath [MAX_PATH]uint16 +} + +func (params *DevInstallParams) DriverPath() string { + return UTF16ToString(params.driverPath[:]) +} + +func (params *DevInstallParams) SetDriverPath(driverPath string) error { + str, err := UTF16FromString(driverPath) + if err != nil { + return err + } + copy(params.driverPath[:], str) + return nil +} + +// DI_FLAGS is SP_DEVINSTALL_PARAMS.Flags values +type DI_FLAGS uint32 + +const ( + // Flags for choosing a device + DI_SHOWOEM DI_FLAGS = 0x00000001 // support Other... button + DI_SHOWCOMPAT DI_FLAGS = 0x00000002 // show compatibility list + DI_SHOWCLASS DI_FLAGS = 0x00000004 // show class list + DI_SHOWALL DI_FLAGS = 0x00000007 // both class & compat list shown + DI_NOVCP DI_FLAGS = 0x00000008 // don't create a new copy queue--use caller-supplied FileQueue + DI_DIDCOMPAT DI_FLAGS = 0x00000010 // Searched for compatible devices + DI_DIDCLASS DI_FLAGS = 0x00000020 // Searched for class devices + DI_AUTOASSIGNRES DI_FLAGS = 0x00000040 // No UI for resources if possible + + // Flags returned by DiInstallDevice to indicate need to reboot/restart + DI_NEEDRESTART DI_FLAGS = 0x00000080 // Reboot required to take effect + DI_NEEDREBOOT DI_FLAGS = 0x00000100 // "" + + // Flags for device installation + DI_NOBROWSE DI_FLAGS = 0x00000200 // no Browse... in InsertDisk + + // Flags set by DiBuildDriverInfoList + DI_MULTMFGS DI_FLAGS = 0x00000400 // Set if multiple manufacturers in class driver list + + // Flag indicates that device is disabled + DI_DISABLED DI_FLAGS = 0x00000800 // Set if device disabled + + // Flags for Device/Class Properties + DI_GENERALPAGE_ADDED DI_FLAGS = 0x00001000 + DI_RESOURCEPAGE_ADDED DI_FLAGS = 0x00002000 + + // Flag to indicate the setting properties for this Device (or class) caused a change so the Dev Mgr UI probably needs to be updated. + DI_PROPERTIES_CHANGE DI_FLAGS = 0x00004000 + + // Flag to indicate that the sorting from the INF file should be used. + DI_INF_IS_SORTED DI_FLAGS = 0x00008000 + + // Flag to indicate that only the the INF specified by SP_DEVINSTALL_PARAMS.DriverPath should be searched. + DI_ENUMSINGLEINF DI_FLAGS = 0x00010000 + + // Flag that prevents ConfigMgr from removing/re-enumerating devices during device + // registration, installation, and deletion. + DI_DONOTCALLCONFIGMG DI_FLAGS = 0x00020000 + + // The following flag can be used to install a device disabled + DI_INSTALLDISABLED DI_FLAGS = 0x00040000 + + // Flag that causes SetupDiBuildDriverInfoList to build a device's compatible driver + // list from its existing class driver list, instead of the normal INF search. + DI_COMPAT_FROM_CLASS DI_FLAGS = 0x00080000 + + // This flag is set if the Class Install params should be used. + DI_CLASSINSTALLPARAMS DI_FLAGS = 0x00100000 + + // This flag is set if the caller of DiCallClassInstaller does NOT want the internal default action performed if the Class installer returns ERROR_DI_DO_DEFAULT. + DI_NODI_DEFAULTACTION DI_FLAGS = 0x00200000 + + // Flags for device installation + DI_QUIETINSTALL DI_FLAGS = 0x00800000 // don't confuse the user with questions or excess info + DI_NOFILECOPY DI_FLAGS = 0x01000000 // No file Copy necessary + DI_FORCECOPY DI_FLAGS = 0x02000000 // Force files to be copied from install path + DI_DRIVERPAGE_ADDED DI_FLAGS = 0x04000000 // Prop provider added Driver page. + DI_USECI_SELECTSTRINGS DI_FLAGS = 0x08000000 // Use Class Installer Provided strings in the Select Device Dlg + DI_OVERRIDE_INFFLAGS DI_FLAGS = 0x10000000 // Override INF flags + DI_PROPS_NOCHANGEUSAGE DI_FLAGS = 0x20000000 // No Enable/Disable in General Props + + DI_NOSELECTICONS DI_FLAGS = 0x40000000 // No small icons in select device dialogs + + DI_NOWRITE_IDS DI_FLAGS = 0x80000000 // Don't write HW & Compat IDs on install +) + +// DI_FLAGSEX is SP_DEVINSTALL_PARAMS.FlagsEx values +type DI_FLAGSEX uint32 + +const ( + DI_FLAGSEX_CI_FAILED DI_FLAGSEX = 0x00000004 // Failed to Load/Call class installer + DI_FLAGSEX_FINISHINSTALL_ACTION DI_FLAGSEX = 0x00000008 // Class/co-installer wants to get a DIF_FINISH_INSTALL action in client context. + DI_FLAGSEX_DIDINFOLIST DI_FLAGSEX = 0x00000010 // Did the Class Info List + DI_FLAGSEX_DIDCOMPATINFO DI_FLAGSEX = 0x00000020 // Did the Compat Info List + DI_FLAGSEX_FILTERCLASSES DI_FLAGSEX = 0x00000040 + DI_FLAGSEX_SETFAILEDINSTALL DI_FLAGSEX = 0x00000080 + DI_FLAGSEX_DEVICECHANGE DI_FLAGSEX = 0x00000100 + DI_FLAGSEX_ALWAYSWRITEIDS DI_FLAGSEX = 0x00000200 + DI_FLAGSEX_PROPCHANGE_PENDING DI_FLAGSEX = 0x00000400 // One or more device property sheets have had changes made to them, and need to have a DIF_PROPERTYCHANGE occur. + DI_FLAGSEX_ALLOWEXCLUDEDDRVS DI_FLAGSEX = 0x00000800 + DI_FLAGSEX_NOUIONQUERYREMOVE DI_FLAGSEX = 0x00001000 + DI_FLAGSEX_USECLASSFORCOMPAT DI_FLAGSEX = 0x00002000 // Use the device's class when building compat drv list. (Ignored if DI_COMPAT_FROM_CLASS flag is specified.) + DI_FLAGSEX_NO_DRVREG_MODIFY DI_FLAGSEX = 0x00008000 // Don't run AddReg and DelReg for device's software (driver) key. + DI_FLAGSEX_IN_SYSTEM_SETUP DI_FLAGSEX = 0x00010000 // Installation is occurring during initial system setup. + DI_FLAGSEX_INET_DRIVER DI_FLAGSEX = 0x00020000 // Driver came from Windows Update + DI_FLAGSEX_APPENDDRIVERLIST DI_FLAGSEX = 0x00040000 // Cause SetupDiBuildDriverInfoList to append a new driver list to an existing list. + DI_FLAGSEX_PREINSTALLBACKUP DI_FLAGSEX = 0x00080000 // not used + DI_FLAGSEX_BACKUPONREPLACE DI_FLAGSEX = 0x00100000 // not used + DI_FLAGSEX_DRIVERLIST_FROM_URL DI_FLAGSEX = 0x00200000 // build driver list from INF(s) retrieved from URL specified in SP_DEVINSTALL_PARAMS.DriverPath (empty string means Windows Update website) + DI_FLAGSEX_EXCLUDE_OLD_INET_DRIVERS DI_FLAGSEX = 0x00800000 // Don't include old Internet drivers when building a driver list. Ignored on Windows Vista and later. + DI_FLAGSEX_POWERPAGE_ADDED DI_FLAGSEX = 0x01000000 // class installer added their own power page + DI_FLAGSEX_FILTERSIMILARDRIVERS DI_FLAGSEX = 0x02000000 // only include similar drivers in class list + DI_FLAGSEX_INSTALLEDDRIVER DI_FLAGSEX = 0x04000000 // only add the installed driver to the class or compat driver list. Used in calls to SetupDiBuildDriverInfoList + DI_FLAGSEX_NO_CLASSLIST_NODE_MERGE DI_FLAGSEX = 0x08000000 // Don't remove identical driver nodes from the class list + DI_FLAGSEX_ALTPLATFORM_DRVSEARCH DI_FLAGSEX = 0x10000000 // Build driver list based on alternate platform information specified in associated file queue + DI_FLAGSEX_RESTART_DEVICE_ONLY DI_FLAGSEX = 0x20000000 // only restart the device drivers are being installed on as opposed to restarting all devices using those drivers. + DI_FLAGSEX_RECURSIVESEARCH DI_FLAGSEX = 0x40000000 // Tell SetupDiBuildDriverInfoList to do a recursive search + DI_FLAGSEX_SEARCH_PUBLISHED_INFS DI_FLAGSEX = 0x80000000 // Tell SetupDiBuildDriverInfoList to do a "published INF" search +) + +// ClassInstallHeader is the first member of any class install parameters structure. It contains the device installation request code that defines the format of the rest of the install parameters structure. +type ClassInstallHeader struct { + size uint32 + InstallFunction DI_FUNCTION +} + +func MakeClassInstallHeader(installFunction DI_FUNCTION) *ClassInstallHeader { + hdr := &ClassInstallHeader{InstallFunction: installFunction} + hdr.size = uint32(unsafe.Sizeof(*hdr)) + return hdr +} + +// DICS_STATE specifies values indicating a change in a device's state +type DICS_STATE uint32 + +const ( + DICS_ENABLE DICS_STATE = 0x00000001 // The device is being enabled. + DICS_DISABLE DICS_STATE = 0x00000002 // The device is being disabled. + DICS_PROPCHANGE DICS_STATE = 0x00000003 // The properties of the device have changed. + DICS_START DICS_STATE = 0x00000004 // The device is being started (if the request is for the currently active hardware profile). + DICS_STOP DICS_STATE = 0x00000005 // The device is being stopped. The driver stack will be unloaded and the CSCONFIGFLAG_DO_NOT_START flag will be set for the device. +) + +// DICS_FLAG specifies the scope of a device property change +type DICS_FLAG uint32 + +const ( + DICS_FLAG_GLOBAL DICS_FLAG = 0x00000001 // make change in all hardware profiles + DICS_FLAG_CONFIGSPECIFIC DICS_FLAG = 0x00000002 // make change in specified profile only + DICS_FLAG_CONFIGGENERAL DICS_FLAG = 0x00000004 // 1 or more hardware profile-specific changes to follow (obsolete) +) + +// PropChangeParams is a structure corresponding to a DIF_PROPERTYCHANGE install function. +type PropChangeParams struct { + ClassInstallHeader ClassInstallHeader + StateChange DICS_STATE + Scope DICS_FLAG + HwProfile uint32 +} + +// DI_REMOVEDEVICE specifies the scope of the device removal +type DI_REMOVEDEVICE uint32 + +const ( + DI_REMOVEDEVICE_GLOBAL DI_REMOVEDEVICE = 0x00000001 // Make this change in all hardware profiles. Remove information about the device from the registry. + DI_REMOVEDEVICE_CONFIGSPECIFIC DI_REMOVEDEVICE = 0x00000002 // Make this change to only the hardware profile specified by HwProfile. this flag only applies to root-enumerated devices. When Windows removes the device from the last hardware profile in which it was configured, Windows performs a global removal. +) + +// RemoveDeviceParams is a structure corresponding to a DIF_REMOVE install function. +type RemoveDeviceParams struct { + ClassInstallHeader ClassInstallHeader + Scope DI_REMOVEDEVICE + HwProfile uint32 +} + +// DrvInfoData is driver information structure (member of a driver info list that may be associated with a particular device instance, or (globally) with a device information set) +type DrvInfoData struct { + size uint32 + DriverType uint32 + _ uintptr + description [LINE_LEN]uint16 + mfgName [LINE_LEN]uint16 + providerName [LINE_LEN]uint16 + DriverDate Filetime + DriverVersion uint64 +} + +func (data *DrvInfoData) Description() string { + return UTF16ToString(data.description[:]) +} + +func (data *DrvInfoData) SetDescription(description string) error { + str, err := UTF16FromString(description) + if err != nil { + return err + } + copy(data.description[:], str) + return nil +} + +func (data *DrvInfoData) MfgName() string { + return UTF16ToString(data.mfgName[:]) +} + +func (data *DrvInfoData) SetMfgName(mfgName string) error { + str, err := UTF16FromString(mfgName) + if err != nil { + return err + } + copy(data.mfgName[:], str) + return nil +} + +func (data *DrvInfoData) ProviderName() string { + return UTF16ToString(data.providerName[:]) +} + +func (data *DrvInfoData) SetProviderName(providerName string) error { + str, err := UTF16FromString(providerName) + if err != nil { + return err + } + copy(data.providerName[:], str) + return nil +} + +// IsNewer method returns true if DrvInfoData date and version is newer than supplied parameters. +func (data *DrvInfoData) IsNewer(driverDate Filetime, driverVersion uint64) bool { + if data.DriverDate.HighDateTime > driverDate.HighDateTime { + return true + } + if data.DriverDate.HighDateTime < driverDate.HighDateTime { + return false + } + + if data.DriverDate.LowDateTime > driverDate.LowDateTime { + return true + } + if data.DriverDate.LowDateTime < driverDate.LowDateTime { + return false + } + + if data.DriverVersion > driverVersion { + return true + } + if data.DriverVersion < driverVersion { + return false + } + + return false +} + +// DrvInfoDetailData is driver information details structure (provides detailed information about a particular driver information structure) +type DrvInfoDetailData struct { + size uint32 // Use unsafeSizeOf method + InfDate Filetime + compatIDsOffset uint32 + compatIDsLength uint32 + _ uintptr + sectionName [LINE_LEN]uint16 + infFileName [MAX_PATH]uint16 + drvDescription [LINE_LEN]uint16 + hardwareID [1]uint16 +} + +func (*DrvInfoDetailData) unsafeSizeOf() uint32 { + if unsafe.Sizeof(uintptr(0)) == 4 { + // Windows declares this with pshpack1.h + return uint32(unsafe.Offsetof(DrvInfoDetailData{}.hardwareID) + unsafe.Sizeof(DrvInfoDetailData{}.hardwareID)) + } + return uint32(unsafe.Sizeof(DrvInfoDetailData{})) +} + +func (data *DrvInfoDetailData) SectionName() string { + return UTF16ToString(data.sectionName[:]) +} + +func (data *DrvInfoDetailData) InfFileName() string { + return UTF16ToString(data.infFileName[:]) +} + +func (data *DrvInfoDetailData) DrvDescription() string { + return UTF16ToString(data.drvDescription[:]) +} + +func (data *DrvInfoDetailData) HardwareID() string { + if data.compatIDsOffset > 1 { + bufW := data.getBuf() + return UTF16ToString(bufW[:wcslen(bufW)]) + } + + return "" +} + +func (data *DrvInfoDetailData) CompatIDs() []string { + a := make([]string, 0) + + if data.compatIDsLength > 0 { + bufW := data.getBuf() + bufW = bufW[data.compatIDsOffset : data.compatIDsOffset+data.compatIDsLength] + for i := 0; i < len(bufW); { + j := i + wcslen(bufW[i:]) + if i < j { + a = append(a, UTF16ToString(bufW[i:j])) + } + i = j + 1 + } + } + + return a +} + +func (data *DrvInfoDetailData) getBuf() []uint16 { + len := (data.size - uint32(unsafe.Offsetof(data.hardwareID))) / 2 + sl := struct { + addr *uint16 + len int + cap int + }{&data.hardwareID[0], int(len), int(len)} + return *(*[]uint16)(unsafe.Pointer(&sl)) +} + +// IsCompatible method tests if given hardware ID matches the driver or is listed on the compatible ID list. +func (data *DrvInfoDetailData) IsCompatible(hwid string) bool { + hwidLC := strings.ToLower(hwid) + if strings.ToLower(data.HardwareID()) == hwidLC { + return true + } + a := data.CompatIDs() + for i := range a { + if strings.ToLower(a[i]) == hwidLC { + return true + } + } + + return false +} + +// DICD flags control SetupDiCreateDeviceInfo +type DICD uint32 + +const ( + DICD_GENERATE_ID DICD = 0x00000001 + DICD_INHERIT_CLASSDRVS DICD = 0x00000002 +) + +// SUOI flags control SetupUninstallOEMInf +type SUOI uint32 + +const ( + SUOI_FORCEDELETE SUOI = 0x0001 +) + +// SPDIT flags to distinguish between class drivers and +// device drivers. (Passed in 'DriverType' parameter of +// driver information list APIs) +type SPDIT uint32 + +const ( + SPDIT_NODRIVER SPDIT = 0x00000000 + SPDIT_CLASSDRIVER SPDIT = 0x00000001 + SPDIT_COMPATDRIVER SPDIT = 0x00000002 +) + +// DIGCF flags control what is included in the device information set built by SetupDiGetClassDevs +type DIGCF uint32 + +const ( + DIGCF_DEFAULT DIGCF = 0x00000001 // only valid with DIGCF_DEVICEINTERFACE + DIGCF_PRESENT DIGCF = 0x00000002 + DIGCF_ALLCLASSES DIGCF = 0x00000004 + DIGCF_PROFILE DIGCF = 0x00000008 + DIGCF_DEVICEINTERFACE DIGCF = 0x00000010 +) + +// DIREG specifies values for SetupDiCreateDevRegKey, SetupDiOpenDevRegKey, and SetupDiDeleteDevRegKey. +type DIREG uint32 + +const ( + DIREG_DEV DIREG = 0x00000001 // Open/Create/Delete device key + DIREG_DRV DIREG = 0x00000002 // Open/Create/Delete driver key + DIREG_BOTH DIREG = 0x00000004 // Delete both driver and Device key +) + +// SPDRP specifies device registry property codes +// (Codes marked as read-only (R) may only be used for +// SetupDiGetDeviceRegistryProperty) +// +// These values should cover the same set of registry properties +// as defined by the CM_DRP codes in cfgmgr32.h. +// +// Note that SPDRP codes are zero based while CM_DRP codes are one based! +type SPDRP uint32 + +const ( + SPDRP_DEVICEDESC SPDRP = 0x00000000 // DeviceDesc (R/W) + SPDRP_HARDWAREID SPDRP = 0x00000001 // HardwareID (R/W) + SPDRP_COMPATIBLEIDS SPDRP = 0x00000002 // CompatibleIDs (R/W) + SPDRP_SERVICE SPDRP = 0x00000004 // Service (R/W) + SPDRP_CLASS SPDRP = 0x00000007 // Class (R--tied to ClassGUID) + SPDRP_CLASSGUID SPDRP = 0x00000008 // ClassGUID (R/W) + SPDRP_DRIVER SPDRP = 0x00000009 // Driver (R/W) + SPDRP_CONFIGFLAGS SPDRP = 0x0000000A // ConfigFlags (R/W) + SPDRP_MFG SPDRP = 0x0000000B // Mfg (R/W) + SPDRP_FRIENDLYNAME SPDRP = 0x0000000C // FriendlyName (R/W) + SPDRP_LOCATION_INFORMATION SPDRP = 0x0000000D // LocationInformation (R/W) + SPDRP_PHYSICAL_DEVICE_OBJECT_NAME SPDRP = 0x0000000E // PhysicalDeviceObjectName (R) + SPDRP_CAPABILITIES SPDRP = 0x0000000F // Capabilities (R) + SPDRP_UI_NUMBER SPDRP = 0x00000010 // UiNumber (R) + SPDRP_UPPERFILTERS SPDRP = 0x00000011 // UpperFilters (R/W) + SPDRP_LOWERFILTERS SPDRP = 0x00000012 // LowerFilters (R/W) + SPDRP_BUSTYPEGUID SPDRP = 0x00000013 // BusTypeGUID (R) + SPDRP_LEGACYBUSTYPE SPDRP = 0x00000014 // LegacyBusType (R) + SPDRP_BUSNUMBER SPDRP = 0x00000015 // BusNumber (R) + SPDRP_ENUMERATOR_NAME SPDRP = 0x00000016 // Enumerator Name (R) + SPDRP_SECURITY SPDRP = 0x00000017 // Security (R/W, binary form) + SPDRP_SECURITY_SDS SPDRP = 0x00000018 // Security (W, SDS form) + SPDRP_DEVTYPE SPDRP = 0x00000019 // Device Type (R/W) + SPDRP_EXCLUSIVE SPDRP = 0x0000001A // Device is exclusive-access (R/W) + SPDRP_CHARACTERISTICS SPDRP = 0x0000001B // Device Characteristics (R/W) + SPDRP_ADDRESS SPDRP = 0x0000001C // Device Address (R) + SPDRP_UI_NUMBER_DESC_FORMAT SPDRP = 0x0000001D // UiNumberDescFormat (R/W) + SPDRP_DEVICE_POWER_DATA SPDRP = 0x0000001E // Device Power Data (R) + SPDRP_REMOVAL_POLICY SPDRP = 0x0000001F // Removal Policy (R) + SPDRP_REMOVAL_POLICY_HW_DEFAULT SPDRP = 0x00000020 // Hardware Removal Policy (R) + SPDRP_REMOVAL_POLICY_OVERRIDE SPDRP = 0x00000021 // Removal Policy Override (RW) + SPDRP_INSTALL_STATE SPDRP = 0x00000022 // Device Install State (R) + SPDRP_LOCATION_PATHS SPDRP = 0x00000023 // Device Location Paths (R) + SPDRP_BASE_CONTAINERID SPDRP = 0x00000024 // Base ContainerID (R) + + SPDRP_MAXIMUM_PROPERTY SPDRP = 0x00000025 // Upper bound on ordinals +) + +// DEVPROPTYPE represents the property-data-type identifier that specifies the +// data type of a device property value in the unified device property model. +type DEVPROPTYPE uint32 + +const ( + DEVPROP_TYPEMOD_ARRAY DEVPROPTYPE = 0x00001000 + DEVPROP_TYPEMOD_LIST DEVPROPTYPE = 0x00002000 + + DEVPROP_TYPE_EMPTY DEVPROPTYPE = 0x00000000 + DEVPROP_TYPE_NULL DEVPROPTYPE = 0x00000001 + DEVPROP_TYPE_SBYTE DEVPROPTYPE = 0x00000002 + DEVPROP_TYPE_BYTE DEVPROPTYPE = 0x00000003 + DEVPROP_TYPE_INT16 DEVPROPTYPE = 0x00000004 + DEVPROP_TYPE_UINT16 DEVPROPTYPE = 0x00000005 + DEVPROP_TYPE_INT32 DEVPROPTYPE = 0x00000006 + DEVPROP_TYPE_UINT32 DEVPROPTYPE = 0x00000007 + DEVPROP_TYPE_INT64 DEVPROPTYPE = 0x00000008 + DEVPROP_TYPE_UINT64 DEVPROPTYPE = 0x00000009 + DEVPROP_TYPE_FLOAT DEVPROPTYPE = 0x0000000A + DEVPROP_TYPE_DOUBLE DEVPROPTYPE = 0x0000000B + DEVPROP_TYPE_DECIMAL DEVPROPTYPE = 0x0000000C + DEVPROP_TYPE_GUID DEVPROPTYPE = 0x0000000D + DEVPROP_TYPE_CURRENCY DEVPROPTYPE = 0x0000000E + DEVPROP_TYPE_DATE DEVPROPTYPE = 0x0000000F + DEVPROP_TYPE_FILETIME DEVPROPTYPE = 0x00000010 + DEVPROP_TYPE_BOOLEAN DEVPROPTYPE = 0x00000011 + DEVPROP_TYPE_STRING DEVPROPTYPE = 0x00000012 + DEVPROP_TYPE_STRING_LIST DEVPROPTYPE = DEVPROP_TYPE_STRING | DEVPROP_TYPEMOD_LIST + DEVPROP_TYPE_SECURITY_DESCRIPTOR DEVPROPTYPE = 0x00000013 + DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING DEVPROPTYPE = 0x00000014 + DEVPROP_TYPE_DEVPROPKEY DEVPROPTYPE = 0x00000015 + DEVPROP_TYPE_DEVPROPTYPE DEVPROPTYPE = 0x00000016 + DEVPROP_TYPE_BINARY DEVPROPTYPE = DEVPROP_TYPE_BYTE | DEVPROP_TYPEMOD_ARRAY + DEVPROP_TYPE_ERROR DEVPROPTYPE = 0x00000017 + DEVPROP_TYPE_NTSTATUS DEVPROPTYPE = 0x00000018 + DEVPROP_TYPE_STRING_INDIRECT DEVPROPTYPE = 0x00000019 + + MAX_DEVPROP_TYPE DEVPROPTYPE = 0x00000019 + MAX_DEVPROP_TYPEMOD DEVPROPTYPE = 0x00002000 + + DEVPROP_MASK_TYPE DEVPROPTYPE = 0x00000FFF + DEVPROP_MASK_TYPEMOD DEVPROPTYPE = 0x0000F000 +) + +// DEVPROPGUID specifies a property category. +type DEVPROPGUID GUID + +// DEVPROPID uniquely identifies the property within the property category. +type DEVPROPID uint32 + +const DEVPROPID_FIRST_USABLE DEVPROPID = 2 + +// DEVPROPKEY represents a device property key for a device property in the +// unified device property model. +type DEVPROPKEY struct { + FmtID DEVPROPGUID + PID DEVPROPID +} + +// CONFIGRET is a return value or error code from cfgmgr32 APIs +type CONFIGRET uint32 + +func (ret CONFIGRET) Error() string { + if win32Error, ok := ret.Unwrap().(Errno); ok { + return fmt.Sprintf("%s (CfgMgr error: 0x%08x)", win32Error.Error(), uint32(ret)) + } + return fmt.Sprintf("CfgMgr error: 0x%08x", uint32(ret)) +} + +func (ret CONFIGRET) Win32Error(defaultError Errno) Errno { + return cm_MapCrToWin32Err(ret, defaultError) +} + +func (ret CONFIGRET) Unwrap() error { + const noMatch = Errno(^uintptr(0)) + win32Error := ret.Win32Error(noMatch) + if win32Error == noMatch { + return nil + } + return win32Error +} + +const ( + CR_SUCCESS CONFIGRET = 0x00000000 + CR_DEFAULT CONFIGRET = 0x00000001 + CR_OUT_OF_MEMORY CONFIGRET = 0x00000002 + CR_INVALID_POINTER CONFIGRET = 0x00000003 + CR_INVALID_FLAG CONFIGRET = 0x00000004 + CR_INVALID_DEVNODE CONFIGRET = 0x00000005 + CR_INVALID_DEVINST = CR_INVALID_DEVNODE + CR_INVALID_RES_DES CONFIGRET = 0x00000006 + CR_INVALID_LOG_CONF CONFIGRET = 0x00000007 + CR_INVALID_ARBITRATOR CONFIGRET = 0x00000008 + CR_INVALID_NODELIST CONFIGRET = 0x00000009 + CR_DEVNODE_HAS_REQS CONFIGRET = 0x0000000A + CR_DEVINST_HAS_REQS = CR_DEVNODE_HAS_REQS + CR_INVALID_RESOURCEID CONFIGRET = 0x0000000B + CR_DLVXD_NOT_FOUND CONFIGRET = 0x0000000C + CR_NO_SUCH_DEVNODE CONFIGRET = 0x0000000D + CR_NO_SUCH_DEVINST = CR_NO_SUCH_DEVNODE + CR_NO_MORE_LOG_CONF CONFIGRET = 0x0000000E + CR_NO_MORE_RES_DES CONFIGRET = 0x0000000F + CR_ALREADY_SUCH_DEVNODE CONFIGRET = 0x00000010 + CR_ALREADY_SUCH_DEVINST = CR_ALREADY_SUCH_DEVNODE + CR_INVALID_RANGE_LIST CONFIGRET = 0x00000011 + CR_INVALID_RANGE CONFIGRET = 0x00000012 + CR_FAILURE CONFIGRET = 0x00000013 + CR_NO_SUCH_LOGICAL_DEV CONFIGRET = 0x00000014 + CR_CREATE_BLOCKED CONFIGRET = 0x00000015 + CR_NOT_SYSTEM_VM CONFIGRET = 0x00000016 + CR_REMOVE_VETOED CONFIGRET = 0x00000017 + CR_APM_VETOED CONFIGRET = 0x00000018 + CR_INVALID_LOAD_TYPE CONFIGRET = 0x00000019 + CR_BUFFER_SMALL CONFIGRET = 0x0000001A + CR_NO_ARBITRATOR CONFIGRET = 0x0000001B + CR_NO_REGISTRY_HANDLE CONFIGRET = 0x0000001C + CR_REGISTRY_ERROR CONFIGRET = 0x0000001D + CR_INVALID_DEVICE_ID CONFIGRET = 0x0000001E + CR_INVALID_DATA CONFIGRET = 0x0000001F + CR_INVALID_API CONFIGRET = 0x00000020 + CR_DEVLOADER_NOT_READY CONFIGRET = 0x00000021 + CR_NEED_RESTART CONFIGRET = 0x00000022 + CR_NO_MORE_HW_PROFILES CONFIGRET = 0x00000023 + CR_DEVICE_NOT_THERE CONFIGRET = 0x00000024 + CR_NO_SUCH_VALUE CONFIGRET = 0x00000025 + CR_WRONG_TYPE CONFIGRET = 0x00000026 + CR_INVALID_PRIORITY CONFIGRET = 0x00000027 + CR_NOT_DISABLEABLE CONFIGRET = 0x00000028 + CR_FREE_RESOURCES CONFIGRET = 0x00000029 + CR_QUERY_VETOED CONFIGRET = 0x0000002A + CR_CANT_SHARE_IRQ CONFIGRET = 0x0000002B + CR_NO_DEPENDENT CONFIGRET = 0x0000002C + CR_SAME_RESOURCES CONFIGRET = 0x0000002D + CR_NO_SUCH_REGISTRY_KEY CONFIGRET = 0x0000002E + CR_INVALID_MACHINENAME CONFIGRET = 0x0000002F + CR_REMOTE_COMM_FAILURE CONFIGRET = 0x00000030 + CR_MACHINE_UNAVAILABLE CONFIGRET = 0x00000031 + CR_NO_CM_SERVICES CONFIGRET = 0x00000032 + CR_ACCESS_DENIED CONFIGRET = 0x00000033 + CR_CALL_NOT_IMPLEMENTED CONFIGRET = 0x00000034 + CR_INVALID_PROPERTY CONFIGRET = 0x00000035 + CR_DEVICE_INTERFACE_ACTIVE CONFIGRET = 0x00000036 + CR_NO_SUCH_DEVICE_INTERFACE CONFIGRET = 0x00000037 + CR_INVALID_REFERENCE_STRING CONFIGRET = 0x00000038 + CR_INVALID_CONFLICT_LIST CONFIGRET = 0x00000039 + CR_INVALID_INDEX CONFIGRET = 0x0000003A + CR_INVALID_STRUCTURE_SIZE CONFIGRET = 0x0000003B + NUM_CR_RESULTS CONFIGRET = 0x0000003C +) + +const ( + CM_GET_DEVICE_INTERFACE_LIST_PRESENT = 0 // only currently 'live' device interfaces + CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES = 1 // all registered device interfaces, live or not +) + +const ( + DN_ROOT_ENUMERATED = 0x00000001 // Was enumerated by ROOT + DN_DRIVER_LOADED = 0x00000002 // Has Register_Device_Driver + DN_ENUM_LOADED = 0x00000004 // Has Register_Enumerator + DN_STARTED = 0x00000008 // Is currently configured + DN_MANUAL = 0x00000010 // Manually installed + DN_NEED_TO_ENUM = 0x00000020 // May need reenumeration + DN_NOT_FIRST_TIME = 0x00000040 // Has received a config + DN_HARDWARE_ENUM = 0x00000080 // Enum generates hardware ID + DN_LIAR = 0x00000100 // Lied about can reconfig once + DN_HAS_MARK = 0x00000200 // Not CM_Create_DevInst lately + DN_HAS_PROBLEM = 0x00000400 // Need device installer + DN_FILTERED = 0x00000800 // Is filtered + DN_MOVED = 0x00001000 // Has been moved + DN_DISABLEABLE = 0x00002000 // Can be disabled + DN_REMOVABLE = 0x00004000 // Can be removed + DN_PRIVATE_PROBLEM = 0x00008000 // Has a private problem + DN_MF_PARENT = 0x00010000 // Multi function parent + DN_MF_CHILD = 0x00020000 // Multi function child + DN_WILL_BE_REMOVED = 0x00040000 // DevInst is being removed + DN_NOT_FIRST_TIMEE = 0x00080000 // Has received a config enumerate + DN_STOP_FREE_RES = 0x00100000 // When child is stopped, free resources + DN_REBAL_CANDIDATE = 0x00200000 // Don't skip during rebalance + DN_BAD_PARTIAL = 0x00400000 // This devnode's log_confs do not have same resources + DN_NT_ENUMERATOR = 0x00800000 // This devnode's is an NT enumerator + DN_NT_DRIVER = 0x01000000 // This devnode's is an NT driver + DN_NEEDS_LOCKING = 0x02000000 // Devnode need lock resume processing + DN_ARM_WAKEUP = 0x04000000 // Devnode can be the wakeup device + DN_APM_ENUMERATOR = 0x08000000 // APM aware enumerator + DN_APM_DRIVER = 0x10000000 // APM aware driver + DN_SILENT_INSTALL = 0x20000000 // Silent install + DN_NO_SHOW_IN_DM = 0x40000000 // No show in device manager + DN_BOOT_LOG_PROB = 0x80000000 // Had a problem during preassignment of boot log conf + DN_NEED_RESTART = DN_LIAR // System needs to be restarted for this Devnode to work properly + DN_DRIVER_BLOCKED = DN_NOT_FIRST_TIME // One or more drivers are blocked from loading for this Devnode + DN_LEGACY_DRIVER = DN_MOVED // This device is using a legacy driver + DN_CHILD_WITH_INVALID_ID = DN_HAS_MARK // One or more children have invalid IDs + DN_DEVICE_DISCONNECTED = DN_NEEDS_LOCKING // The function driver for a device reported that the device is not connected. Typically this means a wireless device is out of range. + DN_QUERY_REMOVE_PENDING = DN_MF_PARENT // Device is part of a set of related devices collectively pending query-removal + DN_QUERY_REMOVE_ACTIVE = DN_MF_CHILD // Device is actively engaged in a query-remove IRP + DN_CHANGEABLE_FLAGS = DN_NOT_FIRST_TIME | DN_HARDWARE_ENUM | DN_HAS_MARK | DN_DISABLEABLE | DN_REMOVABLE | DN_MF_CHILD | DN_MF_PARENT | DN_NOT_FIRST_TIMEE | DN_STOP_FREE_RES | DN_REBAL_CANDIDATE | DN_NT_ENUMERATOR | DN_NT_DRIVER | DN_SILENT_INSTALL | DN_NO_SHOW_IN_DM +) + +//sys setupDiCreateDeviceInfoListEx(classGUID *GUID, hwndParent uintptr, machineName *uint16, reserved uintptr) (handle DevInfo, err error) [failretval==DevInfo(InvalidHandle)] = setupapi.SetupDiCreateDeviceInfoListExW + +// SetupDiCreateDeviceInfoListEx function creates an empty device information set on a remote or a local computer and optionally associates the set with a device setup class. +func SetupDiCreateDeviceInfoListEx(classGUID *GUID, hwndParent uintptr, machineName string) (deviceInfoSet DevInfo, err error) { + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = UTF16PtrFromString(machineName) + if err != nil { + return + } + } + return setupDiCreateDeviceInfoListEx(classGUID, hwndParent, machineNameUTF16, 0) +} + +//sys setupDiGetDeviceInfoListDetail(deviceInfoSet DevInfo, deviceInfoSetDetailData *DevInfoListDetailData) (err error) = setupapi.SetupDiGetDeviceInfoListDetailW + +// SetupDiGetDeviceInfoListDetail function retrieves information associated with a device information set including the class GUID, remote computer handle, and remote computer name. +func SetupDiGetDeviceInfoListDetail(deviceInfoSet DevInfo) (deviceInfoSetDetailData *DevInfoListDetailData, err error) { + data := &DevInfoListDetailData{} + data.size = data.unsafeSizeOf() + + return data, setupDiGetDeviceInfoListDetail(deviceInfoSet, data) +} + +// DeviceInfoListDetail method retrieves information associated with a device information set including the class GUID, remote computer handle, and remote computer name. +func (deviceInfoSet DevInfo) DeviceInfoListDetail() (*DevInfoListDetailData, error) { + return SetupDiGetDeviceInfoListDetail(deviceInfoSet) +} + +//sys setupDiCreateDeviceInfo(deviceInfoSet DevInfo, DeviceName *uint16, classGUID *GUID, DeviceDescription *uint16, hwndParent uintptr, CreationFlags DICD, deviceInfoData *DevInfoData) (err error) = setupapi.SetupDiCreateDeviceInfoW + +// SetupDiCreateDeviceInfo function creates a new device information element and adds it as a new member to the specified device information set. +func SetupDiCreateDeviceInfo(deviceInfoSet DevInfo, deviceName string, classGUID *GUID, deviceDescription string, hwndParent uintptr, creationFlags DICD) (deviceInfoData *DevInfoData, err error) { + deviceNameUTF16, err := UTF16PtrFromString(deviceName) + if err != nil { + return + } + + var deviceDescriptionUTF16 *uint16 + if deviceDescription != "" { + deviceDescriptionUTF16, err = UTF16PtrFromString(deviceDescription) + if err != nil { + return + } + } + + data := &DevInfoData{} + data.size = uint32(unsafe.Sizeof(*data)) + + return data, setupDiCreateDeviceInfo(deviceInfoSet, deviceNameUTF16, classGUID, deviceDescriptionUTF16, hwndParent, creationFlags, data) +} + +// CreateDeviceInfo method creates a new device information element and adds it as a new member to the specified device information set. +func (deviceInfoSet DevInfo) CreateDeviceInfo(deviceName string, classGUID *GUID, deviceDescription string, hwndParent uintptr, creationFlags DICD) (*DevInfoData, error) { + return SetupDiCreateDeviceInfo(deviceInfoSet, deviceName, classGUID, deviceDescription, hwndParent, creationFlags) +} + +//sys setupDiEnumDeviceInfo(deviceInfoSet DevInfo, memberIndex uint32, deviceInfoData *DevInfoData) (err error) = setupapi.SetupDiEnumDeviceInfo + +// SetupDiEnumDeviceInfo function returns a DevInfoData structure that specifies a device information element in a device information set. +func SetupDiEnumDeviceInfo(deviceInfoSet DevInfo, memberIndex int) (*DevInfoData, error) { + data := &DevInfoData{} + data.size = uint32(unsafe.Sizeof(*data)) + + return data, setupDiEnumDeviceInfo(deviceInfoSet, uint32(memberIndex), data) +} + +// EnumDeviceInfo method returns a DevInfoData structure that specifies a device information element in a device information set. +func (deviceInfoSet DevInfo) EnumDeviceInfo(memberIndex int) (*DevInfoData, error) { + return SetupDiEnumDeviceInfo(deviceInfoSet, memberIndex) +} + +// SetupDiDestroyDeviceInfoList function deletes a device information set and frees all associated memory. +//sys SetupDiDestroyDeviceInfoList(deviceInfoSet DevInfo) (err error) = setupapi.SetupDiDestroyDeviceInfoList + +// Close method deletes a device information set and frees all associated memory. +func (deviceInfoSet DevInfo) Close() error { + return SetupDiDestroyDeviceInfoList(deviceInfoSet) +} + +//sys SetupDiBuildDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverType SPDIT) (err error) = setupapi.SetupDiBuildDriverInfoList + +// BuildDriverInfoList method builds a list of drivers that is associated with a specific device or with the global class driver list for a device information set. +func (deviceInfoSet DevInfo) BuildDriverInfoList(deviceInfoData *DevInfoData, driverType SPDIT) error { + return SetupDiBuildDriverInfoList(deviceInfoSet, deviceInfoData, driverType) +} + +//sys SetupDiCancelDriverInfoSearch(deviceInfoSet DevInfo) (err error) = setupapi.SetupDiCancelDriverInfoSearch + +// CancelDriverInfoSearch method cancels a driver list search that is currently in progress in a different thread. +func (deviceInfoSet DevInfo) CancelDriverInfoSearch() error { + return SetupDiCancelDriverInfoSearch(deviceInfoSet) +} + +//sys setupDiEnumDriverInfo(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverType SPDIT, memberIndex uint32, driverInfoData *DrvInfoData) (err error) = setupapi.SetupDiEnumDriverInfoW + +// SetupDiEnumDriverInfo function enumerates the members of a driver list. +func SetupDiEnumDriverInfo(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverType SPDIT, memberIndex int) (*DrvInfoData, error) { + data := &DrvInfoData{} + data.size = uint32(unsafe.Sizeof(*data)) + + return data, setupDiEnumDriverInfo(deviceInfoSet, deviceInfoData, driverType, uint32(memberIndex), data) +} + +// EnumDriverInfo method enumerates the members of a driver list. +func (deviceInfoSet DevInfo) EnumDriverInfo(deviceInfoData *DevInfoData, driverType SPDIT, memberIndex int) (*DrvInfoData, error) { + return SetupDiEnumDriverInfo(deviceInfoSet, deviceInfoData, driverType, memberIndex) +} + +//sys setupDiGetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverInfoData *DrvInfoData) (err error) = setupapi.SetupDiGetSelectedDriverW + +// SetupDiGetSelectedDriver function retrieves the selected driver for a device information set or a particular device information element. +func SetupDiGetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (*DrvInfoData, error) { + data := &DrvInfoData{} + data.size = uint32(unsafe.Sizeof(*data)) + + return data, setupDiGetSelectedDriver(deviceInfoSet, deviceInfoData, data) +} + +// SelectedDriver method retrieves the selected driver for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) SelectedDriver(deviceInfoData *DevInfoData) (*DrvInfoData, error) { + return SetupDiGetSelectedDriver(deviceInfoSet, deviceInfoData) +} + +//sys SetupDiSetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverInfoData *DrvInfoData) (err error) = setupapi.SetupDiSetSelectedDriverW + +// SetSelectedDriver method sets, or resets, the selected driver for a device information element or the selected class driver for a device information set. +func (deviceInfoSet DevInfo) SetSelectedDriver(deviceInfoData *DevInfoData, driverInfoData *DrvInfoData) error { + return SetupDiSetSelectedDriver(deviceInfoSet, deviceInfoData, driverInfoData) +} + +//sys setupDiGetDriverInfoDetail(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverInfoData *DrvInfoData, driverInfoDetailData *DrvInfoDetailData, driverInfoDetailDataSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetDriverInfoDetailW + +// SetupDiGetDriverInfoDetail function retrieves driver information detail for a device information set or a particular device information element in the device information set. +func SetupDiGetDriverInfoDetail(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverInfoData *DrvInfoData) (*DrvInfoDetailData, error) { + reqSize := uint32(2048) + for { + buf := make([]byte, reqSize) + data := (*DrvInfoDetailData)(unsafe.Pointer(&buf[0])) + data.size = data.unsafeSizeOf() + err := setupDiGetDriverInfoDetail(deviceInfoSet, deviceInfoData, driverInfoData, data, uint32(len(buf)), &reqSize) + if err == ERROR_INSUFFICIENT_BUFFER { + continue + } + if err != nil { + return nil, err + } + data.size = reqSize + return data, nil + } +} + +// DriverInfoDetail method retrieves driver information detail for a device information set or a particular device information element in the device information set. +func (deviceInfoSet DevInfo) DriverInfoDetail(deviceInfoData *DevInfoData, driverInfoData *DrvInfoData) (*DrvInfoDetailData, error) { + return SetupDiGetDriverInfoDetail(deviceInfoSet, deviceInfoData, driverInfoData) +} + +//sys SetupDiDestroyDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverType SPDIT) (err error) = setupapi.SetupDiDestroyDriverInfoList + +// DestroyDriverInfoList method deletes a driver list. +func (deviceInfoSet DevInfo) DestroyDriverInfoList(deviceInfoData *DevInfoData, driverType SPDIT) error { + return SetupDiDestroyDriverInfoList(deviceInfoSet, deviceInfoData, driverType) +} + +//sys setupDiGetClassDevsEx(classGUID *GUID, Enumerator *uint16, hwndParent uintptr, Flags DIGCF, deviceInfoSet DevInfo, machineName *uint16, reserved uintptr) (handle DevInfo, err error) [failretval==DevInfo(InvalidHandle)] = setupapi.SetupDiGetClassDevsExW + +// SetupDiGetClassDevsEx function returns a handle to a device information set that contains requested device information elements for a local or a remote computer. +func SetupDiGetClassDevsEx(classGUID *GUID, enumerator string, hwndParent uintptr, flags DIGCF, deviceInfoSet DevInfo, machineName string) (handle DevInfo, err error) { + var enumeratorUTF16 *uint16 + if enumerator != "" { + enumeratorUTF16, err = UTF16PtrFromString(enumerator) + if err != nil { + return + } + } + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = UTF16PtrFromString(machineName) + if err != nil { + return + } + } + return setupDiGetClassDevsEx(classGUID, enumeratorUTF16, hwndParent, flags, deviceInfoSet, machineNameUTF16, 0) +} + +// SetupDiCallClassInstaller function calls the appropriate class installer, and any registered co-installers, with the specified installation request (DIF code). +//sys SetupDiCallClassInstaller(installFunction DI_FUNCTION, deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (err error) = setupapi.SetupDiCallClassInstaller + +// CallClassInstaller member calls the appropriate class installer, and any registered co-installers, with the specified installation request (DIF code). +func (deviceInfoSet DevInfo) CallClassInstaller(installFunction DI_FUNCTION, deviceInfoData *DevInfoData) error { + return SetupDiCallClassInstaller(installFunction, deviceInfoSet, deviceInfoData) +} + +// SetupDiOpenDevRegKey function opens a registry key for device-specific configuration information. +//sys SetupDiOpenDevRegKey(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, Scope DICS_FLAG, HwProfile uint32, KeyType DIREG, samDesired uint32) (key Handle, err error) [failretval==InvalidHandle] = setupapi.SetupDiOpenDevRegKey + +// OpenDevRegKey method opens a registry key for device-specific configuration information. +func (deviceInfoSet DevInfo) OpenDevRegKey(DeviceInfoData *DevInfoData, Scope DICS_FLAG, HwProfile uint32, KeyType DIREG, samDesired uint32) (Handle, error) { + return SetupDiOpenDevRegKey(deviceInfoSet, DeviceInfoData, Scope, HwProfile, KeyType, samDesired) +} + +//sys setupDiGetDeviceProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, propertyKey *DEVPROPKEY, propertyType *DEVPROPTYPE, propertyBuffer *byte, propertyBufferSize uint32, requiredSize *uint32, flags uint32) (err error) = setupapi.SetupDiGetDevicePropertyW + +// SetupDiGetDeviceProperty function retrieves a specified device instance property. +func SetupDiGetDeviceProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, propertyKey *DEVPROPKEY) (value interface{}, err error) { + reqSize := uint32(256) + for { + var dataType DEVPROPTYPE + buf := make([]byte, reqSize) + err = setupDiGetDeviceProperty(deviceInfoSet, deviceInfoData, propertyKey, &dataType, &buf[0], uint32(len(buf)), &reqSize, 0) + if err == ERROR_INSUFFICIENT_BUFFER { + continue + } + if err != nil { + return + } + switch dataType { + case DEVPROP_TYPE_STRING: + ret := UTF16ToString(bufToUTF16(buf)) + runtime.KeepAlive(buf) + return ret, nil + } + return nil, errors.New("unimplemented property type") + } +} + +//sys setupDiGetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, property SPDRP, propertyRegDataType *uint32, propertyBuffer *byte, propertyBufferSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetDeviceRegistryPropertyW + +// SetupDiGetDeviceRegistryProperty function retrieves a specified Plug and Play device property. +func SetupDiGetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, property SPDRP) (value interface{}, err error) { + reqSize := uint32(256) + for { + var dataType uint32 + buf := make([]byte, reqSize) + err = setupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, &dataType, &buf[0], uint32(len(buf)), &reqSize) + if err == ERROR_INSUFFICIENT_BUFFER { + continue + } + if err != nil { + return + } + return getRegistryValue(buf[:reqSize], dataType) + } +} + +func getRegistryValue(buf []byte, dataType uint32) (interface{}, error) { + switch dataType { + case REG_SZ: + ret := UTF16ToString(bufToUTF16(buf)) + runtime.KeepAlive(buf) + return ret, nil + case REG_EXPAND_SZ: + value := UTF16ToString(bufToUTF16(buf)) + if value == "" { + return "", nil + } + p, err := syscall.UTF16PtrFromString(value) + if err != nil { + return "", err + } + ret := make([]uint16, 100) + for { + n, err := ExpandEnvironmentStrings(p, &ret[0], uint32(len(ret))) + if err != nil { + return "", err + } + if n <= uint32(len(ret)) { + return UTF16ToString(ret[:n]), nil + } + ret = make([]uint16, n) + } + case REG_BINARY: + return buf, nil + case REG_DWORD_LITTLE_ENDIAN: + return binary.LittleEndian.Uint32(buf), nil + case REG_DWORD_BIG_ENDIAN: + return binary.BigEndian.Uint32(buf), nil + case REG_MULTI_SZ: + bufW := bufToUTF16(buf) + a := []string{} + for i := 0; i < len(bufW); { + j := i + wcslen(bufW[i:]) + if i < j { + a = append(a, UTF16ToString(bufW[i:j])) + } + i = j + 1 + } + runtime.KeepAlive(buf) + return a, nil + case REG_QWORD_LITTLE_ENDIAN: + return binary.LittleEndian.Uint64(buf), nil + default: + return nil, fmt.Errorf("Unsupported registry value type: %v", dataType) + } +} + +// bufToUTF16 function reinterprets []byte buffer as []uint16 +func bufToUTF16(buf []byte) []uint16 { + sl := struct { + addr *uint16 + len int + cap int + }{(*uint16)(unsafe.Pointer(&buf[0])), len(buf) / 2, cap(buf) / 2} + return *(*[]uint16)(unsafe.Pointer(&sl)) +} + +// utf16ToBuf function reinterprets []uint16 as []byte +func utf16ToBuf(buf []uint16) []byte { + sl := struct { + addr *byte + len int + cap int + }{(*byte)(unsafe.Pointer(&buf[0])), len(buf) * 2, cap(buf) * 2} + return *(*[]byte)(unsafe.Pointer(&sl)) +} + +func wcslen(str []uint16) int { + for i := 0; i < len(str); i++ { + if str[i] == 0 { + return i + } + } + return len(str) +} + +// DeviceRegistryProperty method retrieves a specified Plug and Play device property. +func (deviceInfoSet DevInfo) DeviceRegistryProperty(deviceInfoData *DevInfoData, property SPDRP) (interface{}, error) { + return SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property) +} + +//sys setupDiSetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, property SPDRP, propertyBuffer *byte, propertyBufferSize uint32) (err error) = setupapi.SetupDiSetDeviceRegistryPropertyW + +// SetupDiSetDeviceRegistryProperty function sets a Plug and Play device property for a device. +func SetupDiSetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, property SPDRP, propertyBuffers []byte) error { + return setupDiSetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, &propertyBuffers[0], uint32(len(propertyBuffers))) +} + +// SetDeviceRegistryProperty function sets a Plug and Play device property for a device. +func (deviceInfoSet DevInfo) SetDeviceRegistryProperty(deviceInfoData *DevInfoData, property SPDRP, propertyBuffers []byte) error { + return SetupDiSetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, propertyBuffers) +} + +// SetDeviceRegistryPropertyString method sets a Plug and Play device property string for a device. +func (deviceInfoSet DevInfo) SetDeviceRegistryPropertyString(deviceInfoData *DevInfoData, property SPDRP, str string) error { + str16, err := UTF16FromString(str) + if err != nil { + return err + } + err = SetupDiSetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, utf16ToBuf(append(str16, 0))) + runtime.KeepAlive(str16) + return err +} + +//sys setupDiGetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, deviceInstallParams *DevInstallParams) (err error) = setupapi.SetupDiGetDeviceInstallParamsW + +// SetupDiGetDeviceInstallParams function retrieves device installation parameters for a device information set or a particular device information element. +func SetupDiGetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (*DevInstallParams, error) { + params := &DevInstallParams{} + params.size = uint32(unsafe.Sizeof(*params)) + + return params, setupDiGetDeviceInstallParams(deviceInfoSet, deviceInfoData, params) +} + +// DeviceInstallParams method retrieves device installation parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) DeviceInstallParams(deviceInfoData *DevInfoData) (*DevInstallParams, error) { + return SetupDiGetDeviceInstallParams(deviceInfoSet, deviceInfoData) +} + +//sys setupDiGetDeviceInstanceId(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, instanceId *uint16, instanceIdSize uint32, instanceIdRequiredSize *uint32) (err error) = setupapi.SetupDiGetDeviceInstanceIdW + +// SetupDiGetDeviceInstanceId function retrieves the instance ID of the device. +func SetupDiGetDeviceInstanceId(deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (string, error) { + reqSize := uint32(1024) + for { + buf := make([]uint16, reqSize) + err := setupDiGetDeviceInstanceId(deviceInfoSet, deviceInfoData, &buf[0], uint32(len(buf)), &reqSize) + if err == ERROR_INSUFFICIENT_BUFFER { + continue + } + if err != nil { + return "", err + } + return UTF16ToString(buf), nil + } +} + +// DeviceInstanceID method retrieves the instance ID of the device. +func (deviceInfoSet DevInfo) DeviceInstanceID(deviceInfoData *DevInfoData) (string, error) { + return SetupDiGetDeviceInstanceId(deviceInfoSet, deviceInfoData) +} + +// SetupDiGetClassInstallParams function retrieves class installation parameters for a device information set or a particular device information element. +//sys SetupDiGetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, classInstallParams *ClassInstallHeader, classInstallParamsSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetClassInstallParamsW + +// ClassInstallParams method retrieves class installation parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) ClassInstallParams(deviceInfoData *DevInfoData, classInstallParams *ClassInstallHeader, classInstallParamsSize uint32, requiredSize *uint32) error { + return SetupDiGetClassInstallParams(deviceInfoSet, deviceInfoData, classInstallParams, classInstallParamsSize, requiredSize) +} + +//sys SetupDiSetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, deviceInstallParams *DevInstallParams) (err error) = setupapi.SetupDiSetDeviceInstallParamsW + +// SetDeviceInstallParams member sets device installation parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) SetDeviceInstallParams(deviceInfoData *DevInfoData, deviceInstallParams *DevInstallParams) error { + return SetupDiSetDeviceInstallParams(deviceInfoSet, deviceInfoData, deviceInstallParams) +} + +// SetupDiSetClassInstallParams function sets or clears class install parameters for a device information set or a particular device information element. +//sys SetupDiSetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, classInstallParams *ClassInstallHeader, classInstallParamsSize uint32) (err error) = setupapi.SetupDiSetClassInstallParamsW + +// SetClassInstallParams method sets or clears class install parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) SetClassInstallParams(deviceInfoData *DevInfoData, classInstallParams *ClassInstallHeader, classInstallParamsSize uint32) error { + return SetupDiSetClassInstallParams(deviceInfoSet, deviceInfoData, classInstallParams, classInstallParamsSize) +} + +//sys setupDiClassNameFromGuidEx(classGUID *GUID, className *uint16, classNameSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) = setupapi.SetupDiClassNameFromGuidExW + +// SetupDiClassNameFromGuidEx function retrieves the class name associated with a class GUID. The class can be installed on a local or remote computer. +func SetupDiClassNameFromGuidEx(classGUID *GUID, machineName string) (className string, err error) { + var classNameUTF16 [MAX_CLASS_NAME_LEN]uint16 + + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = UTF16PtrFromString(machineName) + if err != nil { + return + } + } + + err = setupDiClassNameFromGuidEx(classGUID, &classNameUTF16[0], MAX_CLASS_NAME_LEN, nil, machineNameUTF16, 0) + if err != nil { + return + } + + className = UTF16ToString(classNameUTF16[:]) + return +} + +//sys setupDiClassGuidsFromNameEx(className *uint16, classGuidList *GUID, classGuidListSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) = setupapi.SetupDiClassGuidsFromNameExW + +// SetupDiClassGuidsFromNameEx function retrieves the GUIDs associated with the specified class name. This resulting list contains the classes currently installed on a local or remote computer. +func SetupDiClassGuidsFromNameEx(className string, machineName string) ([]GUID, error) { + classNameUTF16, err := UTF16PtrFromString(className) + if err != nil { + return nil, err + } + + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = UTF16PtrFromString(machineName) + if err != nil { + return nil, err + } + } + + reqSize := uint32(4) + for { + buf := make([]GUID, reqSize) + err = setupDiClassGuidsFromNameEx(classNameUTF16, &buf[0], uint32(len(buf)), &reqSize, machineNameUTF16, 0) + if err == ERROR_INSUFFICIENT_BUFFER { + continue + } + if err != nil { + return nil, err + } + return buf[:reqSize], nil + } +} + +//sys setupDiGetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (err error) = setupapi.SetupDiGetSelectedDevice + +// SetupDiGetSelectedDevice function retrieves the selected device information element in a device information set. +func SetupDiGetSelectedDevice(deviceInfoSet DevInfo) (*DevInfoData, error) { + data := &DevInfoData{} + data.size = uint32(unsafe.Sizeof(*data)) + + return data, setupDiGetSelectedDevice(deviceInfoSet, data) +} + +// SelectedDevice method retrieves the selected device information element in a device information set. +func (deviceInfoSet DevInfo) SelectedDevice() (*DevInfoData, error) { + return SetupDiGetSelectedDevice(deviceInfoSet) +} + +// SetupDiSetSelectedDevice function sets a device information element as the selected member of a device information set. This function is typically used by an installation wizard. +//sys SetupDiSetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (err error) = setupapi.SetupDiSetSelectedDevice + +// SetSelectedDevice method sets a device information element as the selected member of a device information set. This function is typically used by an installation wizard. +func (deviceInfoSet DevInfo) SetSelectedDevice(deviceInfoData *DevInfoData) error { + return SetupDiSetSelectedDevice(deviceInfoSet, deviceInfoData) +} + +//sys setupUninstallOEMInf(infFileName *uint16, flags SUOI, reserved uintptr) (err error) = setupapi.SetupUninstallOEMInfW + +// SetupUninstallOEMInf uninstalls the specified driver. +func SetupUninstallOEMInf(infFileName string, flags SUOI) error { + infFileName16, err := UTF16PtrFromString(infFileName) + if err != nil { + return err + } + return setupUninstallOEMInf(infFileName16, flags, 0) +} + +//sys cm_MapCrToWin32Err(configRet CONFIGRET, defaultWin32Error Errno) (ret Errno) = CfgMgr32.CM_MapCrToWin32Err + +//sys cm_Get_Device_Interface_List_Size(len *uint32, interfaceClass *GUID, deviceID *uint16, flags uint32) (ret CONFIGRET) = CfgMgr32.CM_Get_Device_Interface_List_SizeW +//sys cm_Get_Device_Interface_List(interfaceClass *GUID, deviceID *uint16, buffer *uint16, bufferLen uint32, flags uint32) (ret CONFIGRET) = CfgMgr32.CM_Get_Device_Interface_ListW + +func CM_Get_Device_Interface_List(deviceID string, interfaceClass *GUID, flags uint32) ([]string, error) { + deviceID16, err := UTF16PtrFromString(deviceID) + if err != nil { + return nil, err + } + var buf []uint16 + var buflen uint32 + for { + if ret := cm_Get_Device_Interface_List_Size(&buflen, interfaceClass, deviceID16, flags); ret != CR_SUCCESS { + return nil, ret + } + buf = make([]uint16, buflen) + if ret := cm_Get_Device_Interface_List(interfaceClass, deviceID16, &buf[0], buflen, flags); ret == CR_SUCCESS { + break + } else if ret != CR_BUFFER_SMALL { + return nil, ret + } + } + var interfaces []string + for i := 0; i < len(buf); { + j := i + wcslen(buf[i:]) + if i < j { + interfaces = append(interfaces, UTF16ToString(buf[i:j])) + } + i = j + 1 + } + if interfaces == nil { + return nil, ERROR_NO_SUCH_DEVICE_INTERFACE + } + return interfaces, nil +} + +//sys cm_Get_DevNode_Status(status *uint32, problemNumber *uint32, devInst DEVINST, flags uint32) (ret CONFIGRET) = CfgMgr32.CM_Get_DevNode_Status + +func CM_Get_DevNode_Status(status *uint32, problemNumber *uint32, devInst DEVINST, flags uint32) error { + ret := cm_Get_DevNode_Status(status, problemNumber, devInst, flags) + if ret == CR_SUCCESS { + return nil + } + return ret +} diff --git a/vendor/golang.org/x/sys/windows/setupapierrors_windows.go b/vendor/golang.org/x/sys/windows/setupapierrors_windows.go deleted file mode 100644 index 1681810e0..000000000 --- a/vendor/golang.org/x/sys/windows/setupapierrors_windows.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020 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 windows - -import "syscall" - -const ( - ERROR_EXPECTED_SECTION_NAME syscall.Errno = 0x20000000 | 0xC0000000 | 0 - ERROR_BAD_SECTION_NAME_LINE syscall.Errno = 0x20000000 | 0xC0000000 | 1 - ERROR_SECTION_NAME_TOO_LONG syscall.Errno = 0x20000000 | 0xC0000000 | 2 - ERROR_GENERAL_SYNTAX syscall.Errno = 0x20000000 | 0xC0000000 | 3 - ERROR_WRONG_INF_STYLE syscall.Errno = 0x20000000 | 0xC0000000 | 0x100 - ERROR_SECTION_NOT_FOUND syscall.Errno = 0x20000000 | 0xC0000000 | 0x101 - ERROR_LINE_NOT_FOUND syscall.Errno = 0x20000000 | 0xC0000000 | 0x102 - ERROR_NO_BACKUP syscall.Errno = 0x20000000 | 0xC0000000 | 0x103 - ERROR_NO_ASSOCIATED_CLASS syscall.Errno = 0x20000000 | 0xC0000000 | 0x200 - ERROR_CLASS_MISMATCH syscall.Errno = 0x20000000 | 0xC0000000 | 0x201 - ERROR_DUPLICATE_FOUND syscall.Errno = 0x20000000 | 0xC0000000 | 0x202 - ERROR_NO_DRIVER_SELECTED syscall.Errno = 0x20000000 | 0xC0000000 | 0x203 - ERROR_KEY_DOES_NOT_EXIST syscall.Errno = 0x20000000 | 0xC0000000 | 0x204 - ERROR_INVALID_DEVINST_NAME syscall.Errno = 0x20000000 | 0xC0000000 | 0x205 - ERROR_INVALID_CLASS syscall.Errno = 0x20000000 | 0xC0000000 | 0x206 - ERROR_DEVINST_ALREADY_EXISTS syscall.Errno = 0x20000000 | 0xC0000000 | 0x207 - ERROR_DEVINFO_NOT_REGISTERED syscall.Errno = 0x20000000 | 0xC0000000 | 0x208 - ERROR_INVALID_REG_PROPERTY syscall.Errno = 0x20000000 | 0xC0000000 | 0x209 - ERROR_NO_INF syscall.Errno = 0x20000000 | 0xC0000000 | 0x20A - ERROR_NO_SUCH_DEVINST syscall.Errno = 0x20000000 | 0xC0000000 | 0x20B - ERROR_CANT_LOAD_CLASS_ICON syscall.Errno = 0x20000000 | 0xC0000000 | 0x20C - ERROR_INVALID_CLASS_INSTALLER syscall.Errno = 0x20000000 | 0xC0000000 | 0x20D - ERROR_DI_DO_DEFAULT syscall.Errno = 0x20000000 | 0xC0000000 | 0x20E - ERROR_DI_NOFILECOPY syscall.Errno = 0x20000000 | 0xC0000000 | 0x20F - ERROR_INVALID_HWPROFILE syscall.Errno = 0x20000000 | 0xC0000000 | 0x210 - ERROR_NO_DEVICE_SELECTED syscall.Errno = 0x20000000 | 0xC0000000 | 0x211 - ERROR_DEVINFO_LIST_LOCKED syscall.Errno = 0x20000000 | 0xC0000000 | 0x212 - ERROR_DEVINFO_DATA_LOCKED syscall.Errno = 0x20000000 | 0xC0000000 | 0x213 - ERROR_DI_BAD_PATH syscall.Errno = 0x20000000 | 0xC0000000 | 0x214 - ERROR_NO_CLASSINSTALL_PARAMS syscall.Errno = 0x20000000 | 0xC0000000 | 0x215 - ERROR_FILEQUEUE_LOCKED syscall.Errno = 0x20000000 | 0xC0000000 | 0x216 - ERROR_BAD_SERVICE_INSTALLSECT syscall.Errno = 0x20000000 | 0xC0000000 | 0x217 - ERROR_NO_CLASS_DRIVER_LIST syscall.Errno = 0x20000000 | 0xC0000000 | 0x218 - ERROR_NO_ASSOCIATED_SERVICE syscall.Errno = 0x20000000 | 0xC0000000 | 0x219 - ERROR_NO_DEFAULT_DEVICE_INTERFACE syscall.Errno = 0x20000000 | 0xC0000000 | 0x21A - ERROR_DEVICE_INTERFACE_ACTIVE syscall.Errno = 0x20000000 | 0xC0000000 | 0x21B - ERROR_DEVICE_INTERFACE_REMOVED syscall.Errno = 0x20000000 | 0xC0000000 | 0x21C - ERROR_BAD_INTERFACE_INSTALLSECT syscall.Errno = 0x20000000 | 0xC0000000 | 0x21D - ERROR_NO_SUCH_INTERFACE_CLASS syscall.Errno = 0x20000000 | 0xC0000000 | 0x21E - ERROR_INVALID_REFERENCE_STRING syscall.Errno = 0x20000000 | 0xC0000000 | 0x21F - ERROR_INVALID_MACHINENAME syscall.Errno = 0x20000000 | 0xC0000000 | 0x220 - ERROR_REMOTE_COMM_FAILURE syscall.Errno = 0x20000000 | 0xC0000000 | 0x221 - ERROR_MACHINE_UNAVAILABLE syscall.Errno = 0x20000000 | 0xC0000000 | 0x222 - ERROR_NO_CONFIGMGR_SERVICES syscall.Errno = 0x20000000 | 0xC0000000 | 0x223 - ERROR_INVALID_PROPPAGE_PROVIDER syscall.Errno = 0x20000000 | 0xC0000000 | 0x224 - ERROR_NO_SUCH_DEVICE_INTERFACE syscall.Errno = 0x20000000 | 0xC0000000 | 0x225 - ERROR_DI_POSTPROCESSING_REQUIRED syscall.Errno = 0x20000000 | 0xC0000000 | 0x226 - ERROR_INVALID_COINSTALLER syscall.Errno = 0x20000000 | 0xC0000000 | 0x227 - ERROR_NO_COMPAT_DRIVERS syscall.Errno = 0x20000000 | 0xC0000000 | 0x228 - ERROR_NO_DEVICE_ICON syscall.Errno = 0x20000000 | 0xC0000000 | 0x229 - ERROR_INVALID_INF_LOGCONFIG syscall.Errno = 0x20000000 | 0xC0000000 | 0x22A - ERROR_DI_DONT_INSTALL syscall.Errno = 0x20000000 | 0xC0000000 | 0x22B - ERROR_INVALID_FILTER_DRIVER syscall.Errno = 0x20000000 | 0xC0000000 | 0x22C - ERROR_NON_WINDOWS_NT_DRIVER syscall.Errno = 0x20000000 | 0xC0000000 | 0x22D - ERROR_NON_WINDOWS_DRIVER syscall.Errno = 0x20000000 | 0xC0000000 | 0x22E - ERROR_NO_CATALOG_FOR_OEM_INF syscall.Errno = 0x20000000 | 0xC0000000 | 0x22F - ERROR_DEVINSTALL_QUEUE_NONNATIVE syscall.Errno = 0x20000000 | 0xC0000000 | 0x230 - ERROR_NOT_DISABLEABLE syscall.Errno = 0x20000000 | 0xC0000000 | 0x231 - ERROR_CANT_REMOVE_DEVINST syscall.Errno = 0x20000000 | 0xC0000000 | 0x232 - ERROR_INVALID_TARGET syscall.Errno = 0x20000000 | 0xC0000000 | 0x233 - ERROR_DRIVER_NONNATIVE syscall.Errno = 0x20000000 | 0xC0000000 | 0x234 - ERROR_IN_WOW64 syscall.Errno = 0x20000000 | 0xC0000000 | 0x235 - ERROR_SET_SYSTEM_RESTORE_POINT syscall.Errno = 0x20000000 | 0xC0000000 | 0x236 - ERROR_SCE_DISABLED syscall.Errno = 0x20000000 | 0xC0000000 | 0x238 - ERROR_UNKNOWN_EXCEPTION syscall.Errno = 0x20000000 | 0xC0000000 | 0x239 - ERROR_PNP_REGISTRY_ERROR syscall.Errno = 0x20000000 | 0xC0000000 | 0x23A - ERROR_REMOTE_REQUEST_UNSUPPORTED syscall.Errno = 0x20000000 | 0xC0000000 | 0x23B - ERROR_NOT_AN_INSTALLED_OEM_INF syscall.Errno = 0x20000000 | 0xC0000000 | 0x23C - ERROR_INF_IN_USE_BY_DEVICES syscall.Errno = 0x20000000 | 0xC0000000 | 0x23D - ERROR_DI_FUNCTION_OBSOLETE syscall.Errno = 0x20000000 | 0xC0000000 | 0x23E - ERROR_NO_AUTHENTICODE_CATALOG syscall.Errno = 0x20000000 | 0xC0000000 | 0x23F - ERROR_AUTHENTICODE_DISALLOWED syscall.Errno = 0x20000000 | 0xC0000000 | 0x240 - ERROR_AUTHENTICODE_TRUSTED_PUBLISHER syscall.Errno = 0x20000000 | 0xC0000000 | 0x241 - ERROR_AUTHENTICODE_TRUST_NOT_ESTABLISHED syscall.Errno = 0x20000000 | 0xC0000000 | 0x242 - ERROR_AUTHENTICODE_PUBLISHER_NOT_TRUSTED syscall.Errno = 0x20000000 | 0xC0000000 | 0x243 - ERROR_SIGNATURE_OSATTRIBUTE_MISMATCH syscall.Errno = 0x20000000 | 0xC0000000 | 0x244 - ERROR_ONLY_VALIDATE_VIA_AUTHENTICODE syscall.Errno = 0x20000000 | 0xC0000000 | 0x245 - ERROR_DEVICE_INSTALLER_NOT_READY syscall.Errno = 0x20000000 | 0xC0000000 | 0x246 - ERROR_DRIVER_STORE_ADD_FAILED syscall.Errno = 0x20000000 | 0xC0000000 | 0x247 - ERROR_DEVICE_INSTALL_BLOCKED syscall.Errno = 0x20000000 | 0xC0000000 | 0x248 - ERROR_DRIVER_INSTALL_BLOCKED syscall.Errno = 0x20000000 | 0xC0000000 | 0x249 - ERROR_WRONG_INF_TYPE syscall.Errno = 0x20000000 | 0xC0000000 | 0x24A - ERROR_FILE_HASH_NOT_IN_CATALOG syscall.Errno = 0x20000000 | 0xC0000000 | 0x24B - ERROR_DRIVER_STORE_DELETE_FAILED syscall.Errno = 0x20000000 | 0xC0000000 | 0x24C - ERROR_UNRECOVERABLE_STACK_OVERFLOW syscall.Errno = 0x20000000 | 0xC0000000 | 0x300 - EXCEPTION_SPAPI_UNRECOVERABLE_STACK_OVERFLOW syscall.Errno = ERROR_UNRECOVERABLE_STACK_OVERFLOW - ERROR_NO_DEFAULT_INTERFACE_DEVICE syscall.Errno = ERROR_NO_DEFAULT_DEVICE_INTERFACE - ERROR_INTERFACE_DEVICE_ACTIVE syscall.Errno = ERROR_DEVICE_INTERFACE_ACTIVE - ERROR_INTERFACE_DEVICE_REMOVED syscall.Errno = ERROR_DEVICE_INTERFACE_REMOVED - ERROR_NO_SUCH_INTERFACE_DEVICE syscall.Errno = ERROR_NO_SUCH_DEVICE_INTERFACE -) diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 53ee74e08..200b62a00 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -248,6 +248,7 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys FreeEnvironmentStrings(envs *uint16) (err error) = kernel32.FreeEnvironmentStringsW //sys GetEnvironmentVariable(name *uint16, buffer *uint16, size uint32) (n uint32, err error) = kernel32.GetEnvironmentVariableW //sys SetEnvironmentVariable(name *uint16, value *uint16) (err error) = kernel32.SetEnvironmentVariableW +//sys ExpandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW //sys CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock //sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock //sys getTickCount64() (ms uint64) = kernel32.GetTickCount64 @@ -322,6 +323,8 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW //sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW //sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot +//sys Module32First(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32FirstW +//sys Module32Next(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32NextW //sys Process32First(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32FirstW //sys Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32NextW //sys Thread32First(snapshot Handle, threadEntry *ThreadEntry32) (err error) @@ -893,9 +896,7 @@ func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, int32, error) { p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil } @@ -915,9 +916,7 @@ func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, int32, error) { p[0] = byte(sa.Port >> 8) p[1] = byte(sa.Port) sa.raw.Scope_id = sa.ZoneId - for i := 0; i < len(sa.Addr); i++ { - sa.raw.Addr[i] = sa.Addr[i] - } + sa.raw.Addr = sa.Addr return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil } @@ -990,9 +989,7 @@ func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) { sa := new(SockaddrInet4) p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil case AF_INET6: @@ -1001,9 +998,7 @@ func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) { p := (*[2]byte)(unsafe.Pointer(&pp.Port)) sa.Port = int(p[0])<<8 + int(p[1]) sa.ZoneId = pp.Scope_id - for i := 0; i < len(sa.Addr); i++ { - sa.Addr[i] = pp.Addr[i] - } + sa.Addr = pp.Addr return sa, nil } return nil, syscall.EAFNOSUPPORT diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 286dd1eab..bb31abda4 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -156,6 +156,8 @@ const ( MAX_PATH = 260 MAX_LONG_PATH = 32768 + MAX_MODULE_NAME32 = 255 + MAX_COMPUTERNAME_LENGTH = 15 TIME_ZONE_ID_UNKNOWN = 0 @@ -936,8 +938,8 @@ type StartupInfoEx struct { type ProcThreadAttributeList struct{} type ProcThreadAttributeListContainer struct { - data *ProcThreadAttributeList - heapAllocations []uintptr + data *ProcThreadAttributeList + pointers []unsafe.Pointer } type ProcessInformation struct { @@ -970,6 +972,21 @@ type ThreadEntry32 struct { Flags uint32 } +type ModuleEntry32 struct { + Size uint32 + ModuleID uint32 + ProcessID uint32 + GlblcntUsage uint32 + ProccntUsage uint32 + ModBaseAddr uintptr + ModBaseSize uint32 + ModuleHandle Handle + Module [MAX_MODULE_NAME32 + 1]uint16 + ExePath [MAX_PATH]uint16 +} + +const SizeofModuleEntry32 = unsafe.Sizeof(ModuleEntry32{}) + type Systemtime struct { Year uint16 Month uint16 @@ -2732,6 +2749,43 @@ type PROCESS_BASIC_INFORMATION struct { InheritedFromUniqueProcessId uintptr } +type SYSTEM_PROCESS_INFORMATION struct { + NextEntryOffset uint32 + NumberOfThreads uint32 + WorkingSetPrivateSize int64 + HardFaultCount uint32 + NumberOfThreadsHighWatermark uint32 + CycleTime uint64 + CreateTime int64 + UserTime int64 + KernelTime int64 + ImageName NTUnicodeString + BasePriority int32 + UniqueProcessID uintptr + InheritedFromUniqueProcessID uintptr + HandleCount uint32 + SessionID uint32 + UniqueProcessKey *uint32 + PeakVirtualSize uintptr + VirtualSize uintptr + PageFaultCount uint32 + PeakWorkingSetSize uintptr + WorkingSetSize uintptr + QuotaPeakPagedPoolUsage uintptr + QuotaPagedPoolUsage uintptr + QuotaPeakNonPagedPoolUsage uintptr + QuotaNonPagedPoolUsage uintptr + PagefileUsage uintptr + PeakPagefileUsage uintptr + PrivatePageCount uintptr + ReadOperationCount int64 + WriteOperationCount int64 + OtherOperationCount int64 + ReadTransferCount int64 + WriteTransferCount int64 + OtherTransferCount int64 +} + // SystemInformationClasses for NtQuerySystemInformation and NtSetSystemInformation const ( SystemBasicInformation = iota diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 91817d6dc..1055d47ed 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -36,6 +36,7 @@ func errnoErr(e syscall.Errno) error { } var ( + modCfgMgr32 = NewLazySystemDLL("CfgMgr32.dll") modadvapi32 = NewLazySystemDLL("advapi32.dll") modcrypt32 = NewLazySystemDLL("crypt32.dll") moddnsapi = NewLazySystemDLL("dnsapi.dll") @@ -48,6 +49,7 @@ var ( modpsapi = NewLazySystemDLL("psapi.dll") modsechost = NewLazySystemDLL("sechost.dll") modsecur32 = NewLazySystemDLL("secur32.dll") + modsetupapi = NewLazySystemDLL("setupapi.dll") modshell32 = NewLazySystemDLL("shell32.dll") moduser32 = NewLazySystemDLL("user32.dll") moduserenv = NewLazySystemDLL("userenv.dll") @@ -56,6 +58,10 @@ var ( modws2_32 = NewLazySystemDLL("ws2_32.dll") modwtsapi32 = NewLazySystemDLL("wtsapi32.dll") + procCM_Get_DevNode_Status = modCfgMgr32.NewProc("CM_Get_DevNode_Status") + procCM_Get_Device_Interface_ListW = modCfgMgr32.NewProc("CM_Get_Device_Interface_ListW") + procCM_Get_Device_Interface_List_SizeW = modCfgMgr32.NewProc("CM_Get_Device_Interface_List_SizeW") + procCM_MapCrToWin32Err = modCfgMgr32.NewProc("CM_MapCrToWin32Err") procAdjustTokenGroups = modadvapi32.NewProc("AdjustTokenGroups") procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") procAllocateAndInitializeSid = modadvapi32.NewProc("AllocateAndInitializeSid") @@ -199,6 +205,7 @@ var ( procDeviceIoControl = modkernel32.NewProc("DeviceIoControl") procDuplicateHandle = modkernel32.NewProc("DuplicateHandle") procExitProcess = modkernel32.NewProc("ExitProcess") + procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") procFindClose = modkernel32.NewProc("FindClose") procFindCloseChangeNotification = modkernel32.NewProc("FindCloseChangeNotification") procFindFirstChangeNotificationW = modkernel32.NewProc("FindFirstChangeNotificationW") @@ -288,6 +295,8 @@ var ( procLockFileEx = modkernel32.NewProc("LockFileEx") procLockResource = modkernel32.NewProc("LockResource") procMapViewOfFile = modkernel32.NewProc("MapViewOfFile") + procModule32FirstW = modkernel32.NewProc("Module32FirstW") + procModule32NextW = modkernel32.NewProc("Module32NextW") procMoveFileExW = modkernel32.NewProc("MoveFileExW") procMoveFileW = modkernel32.NewProc("MoveFileW") procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar") @@ -400,6 +409,34 @@ var ( procUnsubscribeServiceChangeNotifications = modsechost.NewProc("UnsubscribeServiceChangeNotifications") procGetUserNameExW = modsecur32.NewProc("GetUserNameExW") procTranslateNameW = modsecur32.NewProc("TranslateNameW") + procSetupDiBuildDriverInfoList = modsetupapi.NewProc("SetupDiBuildDriverInfoList") + procSetupDiCallClassInstaller = modsetupapi.NewProc("SetupDiCallClassInstaller") + procSetupDiCancelDriverInfoSearch = modsetupapi.NewProc("SetupDiCancelDriverInfoSearch") + procSetupDiClassGuidsFromNameExW = modsetupapi.NewProc("SetupDiClassGuidsFromNameExW") + procSetupDiClassNameFromGuidExW = modsetupapi.NewProc("SetupDiClassNameFromGuidExW") + procSetupDiCreateDeviceInfoListExW = modsetupapi.NewProc("SetupDiCreateDeviceInfoListExW") + procSetupDiCreateDeviceInfoW = modsetupapi.NewProc("SetupDiCreateDeviceInfoW") + procSetupDiDestroyDeviceInfoList = modsetupapi.NewProc("SetupDiDestroyDeviceInfoList") + procSetupDiDestroyDriverInfoList = modsetupapi.NewProc("SetupDiDestroyDriverInfoList") + procSetupDiEnumDeviceInfo = modsetupapi.NewProc("SetupDiEnumDeviceInfo") + procSetupDiEnumDriverInfoW = modsetupapi.NewProc("SetupDiEnumDriverInfoW") + procSetupDiGetClassDevsExW = modsetupapi.NewProc("SetupDiGetClassDevsExW") + procSetupDiGetClassInstallParamsW = modsetupapi.NewProc("SetupDiGetClassInstallParamsW") + procSetupDiGetDeviceInfoListDetailW = modsetupapi.NewProc("SetupDiGetDeviceInfoListDetailW") + procSetupDiGetDeviceInstallParamsW = modsetupapi.NewProc("SetupDiGetDeviceInstallParamsW") + procSetupDiGetDeviceInstanceIdW = modsetupapi.NewProc("SetupDiGetDeviceInstanceIdW") + procSetupDiGetDevicePropertyW = modsetupapi.NewProc("SetupDiGetDevicePropertyW") + procSetupDiGetDeviceRegistryPropertyW = modsetupapi.NewProc("SetupDiGetDeviceRegistryPropertyW") + procSetupDiGetDriverInfoDetailW = modsetupapi.NewProc("SetupDiGetDriverInfoDetailW") + procSetupDiGetSelectedDevice = modsetupapi.NewProc("SetupDiGetSelectedDevice") + procSetupDiGetSelectedDriverW = modsetupapi.NewProc("SetupDiGetSelectedDriverW") + procSetupDiOpenDevRegKey = modsetupapi.NewProc("SetupDiOpenDevRegKey") + procSetupDiSetClassInstallParamsW = modsetupapi.NewProc("SetupDiSetClassInstallParamsW") + procSetupDiSetDeviceInstallParamsW = modsetupapi.NewProc("SetupDiSetDeviceInstallParamsW") + procSetupDiSetDeviceRegistryPropertyW = modsetupapi.NewProc("SetupDiSetDeviceRegistryPropertyW") + procSetupDiSetSelectedDevice = modsetupapi.NewProc("SetupDiSetSelectedDevice") + procSetupDiSetSelectedDriverW = modsetupapi.NewProc("SetupDiSetSelectedDriverW") + procSetupUninstallOEMInfW = modsetupapi.NewProc("SetupUninstallOEMInfW") procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW") procSHGetKnownFolderPath = modshell32.NewProc("SHGetKnownFolderPath") procShellExecuteW = modshell32.NewProc("ShellExecuteW") @@ -447,6 +484,30 @@ var ( procWTSQueryUserToken = modwtsapi32.NewProc("WTSQueryUserToken") ) +func cm_Get_DevNode_Status(status *uint32, problemNumber *uint32, devInst DEVINST, flags uint32) (ret CONFIGRET) { + r0, _, _ := syscall.Syscall6(procCM_Get_DevNode_Status.Addr(), 4, uintptr(unsafe.Pointer(status)), uintptr(unsafe.Pointer(problemNumber)), uintptr(devInst), uintptr(flags), 0, 0) + ret = CONFIGRET(r0) + return +} + +func cm_Get_Device_Interface_List(interfaceClass *GUID, deviceID *uint16, buffer *uint16, bufferLen uint32, flags uint32) (ret CONFIGRET) { + r0, _, _ := syscall.Syscall6(procCM_Get_Device_Interface_ListW.Addr(), 5, uintptr(unsafe.Pointer(interfaceClass)), uintptr(unsafe.Pointer(deviceID)), uintptr(unsafe.Pointer(buffer)), uintptr(bufferLen), uintptr(flags), 0) + ret = CONFIGRET(r0) + return +} + +func cm_Get_Device_Interface_List_Size(len *uint32, interfaceClass *GUID, deviceID *uint16, flags uint32) (ret CONFIGRET) { + r0, _, _ := syscall.Syscall6(procCM_Get_Device_Interface_List_SizeW.Addr(), 4, uintptr(unsafe.Pointer(len)), uintptr(unsafe.Pointer(interfaceClass)), uintptr(unsafe.Pointer(deviceID)), uintptr(flags), 0, 0) + ret = CONFIGRET(r0) + return +} + +func cm_MapCrToWin32Err(configRet CONFIGRET, defaultWin32Error Errno) (ret Errno) { + r0, _, _ := syscall.Syscall(procCM_MapCrToWin32Err.Addr(), 2, uintptr(configRet), uintptr(defaultWin32Error), 0) + ret = Errno(r0) + return +} + func AdjustTokenGroups(token Token, resetToDefault bool, newstate *Tokengroups, buflen uint32, prevstate *Tokengroups, returnlen *uint32) (err error) { var _p0 uint32 if resetToDefault { @@ -1716,6 +1777,15 @@ func ExitProcess(exitcode uint32) { return } +func ExpandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size)) + n = uint32(r0) + if n == 0 { + err = errnoErr(e1) + } + return +} + func FindClose(handle Handle) (err error) { r1, _, e1 := syscall.Syscall(procFindClose.Addr(), 1, uintptr(handle), 0, 0) if r1 == 0 { @@ -2499,6 +2569,22 @@ func MapViewOfFile(handle Handle, access uint32, offsetHigh uint32, offsetLow ui return } +func Module32First(snapshot Handle, moduleEntry *ModuleEntry32) (err error) { + r1, _, e1 := syscall.Syscall(procModule32FirstW.Addr(), 2, uintptr(snapshot), uintptr(unsafe.Pointer(moduleEntry)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func Module32Next(snapshot Handle, moduleEntry *ModuleEntry32) (err error) { + r1, _, e1 := syscall.Syscall(procModule32NextW.Addr(), 2, uintptr(snapshot), uintptr(unsafe.Pointer(moduleEntry)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func MoveFileEx(from *uint16, to *uint16, flags uint32) (err error) { r1, _, e1 := syscall.Syscall(procMoveFileExW.Addr(), 3, uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(to)), uintptr(flags)) if r1 == 0 { @@ -3432,6 +3518,233 @@ func TranslateName(accName *uint16, accNameFormat uint32, desiredNameFormat uint return } +func SetupDiBuildDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverType SPDIT) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiBuildDriverInfoList.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(driverType)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiCallClassInstaller(installFunction DI_FUNCTION, deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiCallClassInstaller.Addr(), 3, uintptr(installFunction), uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiCancelDriverInfoSearch(deviceInfoSet DevInfo) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiCancelDriverInfoSearch.Addr(), 1, uintptr(deviceInfoSet), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiClassGuidsFromNameEx(className *uint16, classGuidList *GUID, classGuidListSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiClassGuidsFromNameExW.Addr(), 6, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(classGuidList)), uintptr(classGuidListSize), uintptr(unsafe.Pointer(requiredSize)), uintptr(unsafe.Pointer(machineName)), uintptr(reserved)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiClassNameFromGuidEx(classGUID *GUID, className *uint16, classNameSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiClassNameFromGuidExW.Addr(), 6, uintptr(unsafe.Pointer(classGUID)), uintptr(unsafe.Pointer(className)), uintptr(classNameSize), uintptr(unsafe.Pointer(requiredSize)), uintptr(unsafe.Pointer(machineName)), uintptr(reserved)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiCreateDeviceInfoListEx(classGUID *GUID, hwndParent uintptr, machineName *uint16, reserved uintptr) (handle DevInfo, err error) { + r0, _, e1 := syscall.Syscall6(procSetupDiCreateDeviceInfoListExW.Addr(), 4, uintptr(unsafe.Pointer(classGUID)), uintptr(hwndParent), uintptr(unsafe.Pointer(machineName)), uintptr(reserved), 0, 0) + handle = DevInfo(r0) + if handle == DevInfo(InvalidHandle) { + err = errnoErr(e1) + } + return +} + +func setupDiCreateDeviceInfo(deviceInfoSet DevInfo, DeviceName *uint16, classGUID *GUID, DeviceDescription *uint16, hwndParent uintptr, CreationFlags DICD, deviceInfoData *DevInfoData) (err error) { + r1, _, e1 := syscall.Syscall9(procSetupDiCreateDeviceInfoW.Addr(), 7, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(DeviceName)), uintptr(unsafe.Pointer(classGUID)), uintptr(unsafe.Pointer(DeviceDescription)), uintptr(hwndParent), uintptr(CreationFlags), uintptr(unsafe.Pointer(deviceInfoData)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiDestroyDeviceInfoList(deviceInfoSet DevInfo) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiDestroyDeviceInfoList.Addr(), 1, uintptr(deviceInfoSet), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiDestroyDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverType SPDIT) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiDestroyDriverInfoList.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(driverType)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiEnumDeviceInfo(deviceInfoSet DevInfo, memberIndex uint32, deviceInfoData *DevInfoData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiEnumDeviceInfo.Addr(), 3, uintptr(deviceInfoSet), uintptr(memberIndex), uintptr(unsafe.Pointer(deviceInfoData))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiEnumDriverInfo(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverType SPDIT, memberIndex uint32, driverInfoData *DrvInfoData) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiEnumDriverInfoW.Addr(), 5, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(driverType), uintptr(memberIndex), uintptr(unsafe.Pointer(driverInfoData)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetClassDevsEx(classGUID *GUID, Enumerator *uint16, hwndParent uintptr, Flags DIGCF, deviceInfoSet DevInfo, machineName *uint16, reserved uintptr) (handle DevInfo, err error) { + r0, _, e1 := syscall.Syscall9(procSetupDiGetClassDevsExW.Addr(), 7, uintptr(unsafe.Pointer(classGUID)), uintptr(unsafe.Pointer(Enumerator)), uintptr(hwndParent), uintptr(Flags), uintptr(deviceInfoSet), uintptr(unsafe.Pointer(machineName)), uintptr(reserved), 0, 0) + handle = DevInfo(r0) + if handle == DevInfo(InvalidHandle) { + err = errnoErr(e1) + } + return +} + +func SetupDiGetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, classInstallParams *ClassInstallHeader, classInstallParamsSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiGetClassInstallParamsW.Addr(), 5, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(classInstallParams)), uintptr(classInstallParamsSize), uintptr(unsafe.Pointer(requiredSize)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetDeviceInfoListDetail(deviceInfoSet DevInfo, deviceInfoSetDetailData *DevInfoListDetailData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetDeviceInfoListDetailW.Addr(), 2, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoSetDetailData)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, deviceInstallParams *DevInstallParams) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetDeviceInstallParamsW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(deviceInstallParams))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetDeviceInstanceId(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, instanceId *uint16, instanceIdSize uint32, instanceIdRequiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiGetDeviceInstanceIdW.Addr(), 5, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(instanceId)), uintptr(instanceIdSize), uintptr(unsafe.Pointer(instanceIdRequiredSize)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetDeviceProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, propertyKey *DEVPROPKEY, propertyType *DEVPROPTYPE, propertyBuffer *byte, propertyBufferSize uint32, requiredSize *uint32, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procSetupDiGetDevicePropertyW.Addr(), 8, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(propertyKey)), uintptr(unsafe.Pointer(propertyType)), uintptr(unsafe.Pointer(propertyBuffer)), uintptr(propertyBufferSize), uintptr(unsafe.Pointer(requiredSize)), uintptr(flags), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, property SPDRP, propertyRegDataType *uint32, propertyBuffer *byte, propertyBufferSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procSetupDiGetDeviceRegistryPropertyW.Addr(), 7, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(property), uintptr(unsafe.Pointer(propertyRegDataType)), uintptr(unsafe.Pointer(propertyBuffer)), uintptr(propertyBufferSize), uintptr(unsafe.Pointer(requiredSize)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetDriverInfoDetail(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverInfoData *DrvInfoData, driverInfoDetailData *DrvInfoDetailData, driverInfoDetailDataSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiGetDriverInfoDetailW.Addr(), 6, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(driverInfoData)), uintptr(unsafe.Pointer(driverInfoDetailData)), uintptr(driverInfoDetailDataSize), uintptr(unsafe.Pointer(requiredSize))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetSelectedDevice.Addr(), 2, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiGetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverInfoData *DrvInfoData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetSelectedDriverW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(driverInfoData))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiOpenDevRegKey(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, Scope DICS_FLAG, HwProfile uint32, KeyType DIREG, samDesired uint32) (key Handle, err error) { + r0, _, e1 := syscall.Syscall6(procSetupDiOpenDevRegKey.Addr(), 6, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(Scope), uintptr(HwProfile), uintptr(KeyType), uintptr(samDesired)) + key = Handle(r0) + if key == InvalidHandle { + err = errnoErr(e1) + } + return +} + +func SetupDiSetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, classInstallParams *ClassInstallHeader, classInstallParamsSize uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiSetClassInstallParamsW.Addr(), 4, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(classInstallParams)), uintptr(classInstallParamsSize), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiSetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, deviceInstallParams *DevInstallParams) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiSetDeviceInstallParamsW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(deviceInstallParams))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupDiSetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, property SPDRP, propertyBuffer *byte, propertyBufferSize uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiSetDeviceRegistryPropertyW.Addr(), 5, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(property), uintptr(unsafe.Pointer(propertyBuffer)), uintptr(propertyBufferSize), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiSetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *DevInfoData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiSetSelectedDevice.Addr(), 2, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetupDiSetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *DevInfoData, driverInfoData *DrvInfoData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiSetSelectedDriverW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(driverInfoData))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func setupUninstallOEMInf(infFileName *uint16, flags SUOI, reserved uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procSetupUninstallOEMInfW.Addr(), 3, uintptr(unsafe.Pointer(infFileName)), uintptr(flags), uintptr(reserved)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) { r0, _, e1 := syscall.Syscall(procCommandLineToArgvW.Addr(), 2, uintptr(unsafe.Pointer(cmd)), uintptr(unsafe.Pointer(argc)), 0) argv = (*[8192]*[8192]uint16)(unsafe.Pointer(r0)) diff --git a/vendor/modules.txt b/vendor/modules.txt index 47c766c71..020eb54b1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -33,7 +33,8 @@ github.com/fatih/color github.com/fsnotify/fsnotify # github.com/gdamore/encoding v1.0.0 github.com/gdamore/encoding -# github.com/gdamore/tcell/v2 v2.4.0 +# github.com/gdamore/tcell/v2 v2.4.1-0.20210926162909-66f061b1fc9b +## explicit github.com/gdamore/tcell/v2 github.com/gdamore/tcell/v2/terminfo github.com/gdamore/tcell/v2/terminfo/a/aixterm @@ -161,7 +162,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474 +# github.com/jesseduffield/gocui v0.3.1-0.20220108045521-1945d7b9ed8b ## explicit github.com/jesseduffield/gocui # github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e @@ -206,12 +207,16 @@ github.com/mitchellh/go-homedir # github.com/onsi/gomega v1.7.1 ## explicit # github.com/pmezard/go-difflib v1.0.0 +## explicit github.com/pmezard/go-difflib/difflib # github.com/rivo/uniseg v0.2.0 github.com/rivo/uniseg # github.com/sahilm/fuzzy v0.1.0 ## explicit github.com/sahilm/fuzzy +# github.com/sanity-io/litter v1.5.2 +## explicit +github.com/sanity-io/litter # github.com/sergi/go-diff v1.1.0 github.com/sergi/go-diff/diffmatchpatch # github.com/sirupsen/logrus v1.4.2 @@ -253,7 +258,7 @@ golang.org/x/crypto/ssh/knownhosts golang.org/x/net/context golang.org/x/net/internal/socks golang.org/x/net/proxy -# golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c +# golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e ## explicit golang.org/x/sys/cpu golang.org/x/sys/internal/unsafeheader