diff --git a/.editorconfig b/.editorconfig
index bc1a4e7..b715678 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -20,3 +20,11 @@ trim_trailing_whitespace = false
[*.py]
indent_size = 4
+
+[*.bats]
+indent_style = tab
+indent_size = 4
+
+[tests/*.sh]
+indent_style = tab
+indent_size = 4
diff --git a/.github/.ignore_words b/.github/.ignore_words
index 8aad33d..35d9297 100644
--- a/.github/.ignore_words
+++ b/.github/.ignore_words
@@ -1 +1,2 @@
gool
+Cant
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b409721..9e66f95 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,53 +6,109 @@ on:
branches: [main]
jobs:
lint:
- runs-on: 'ubuntu-latest'
+ runs-on: ubuntu-latest
steps:
+ - name: Check out code.
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
- name: 'Get Changed Files'
id: 'files'
- uses: 'masesgroup/retrieve-changed-files@v3'
- with:
- format: 'json'
- # a force push will cause the action above to fail. Don't do force push when people are
- # reviewing!
- - name: Check out code.
- uses: actions/checkout@v4
- - uses: 'actions/setup-go@v5'
+ run: |
+ CHANGED_FILES=$(git diff --name-only "${BASE_SHA:-$BEFORE_SHA}...${HEAD_SHA:-$GITHUB_REF}")
+ echo "::notice::Changed files: $CHANGED_FILES"
+ {
+ echo "added_modified<> "$GITHUB_OUTPUT"
+ env:
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+ BEFORE_SHA: "${{ github.event.before }}"
+ - uses: 'actions/setup-go@v6'
with:
go-version: '1.20'
+ - name: 'Install EditorConfig Lint'
+ run: go install 'github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@latest'
- name: 'Check EditorConfig Lint'
- run: |
- sudo apt install -y jq
- go install 'github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@latest'
-
- readarray -t changed_files <<<"$(jq -r '.[]' <<<'${{ steps.files.outputs.added_modified }}')"
- ~/go/bin/editorconfig-checker ${changed_files[@]}
+ run: echo "$FILES" | xargs ~/go/bin/editorconfig-checker
+ env:
+ # NOTE: use env to pass the output in order to avoid possible injection attacks
+ FILES: "${{ steps.files.outputs.added_modified }}"
+ - name: checkstyle
+ run: ./scripts/checkstyle.py
+ - name: Shellcheck
+ run: shellcheck --severity=error bin/* ./*.sh
+ - name: Lint and format Python with Ruff
+ uses: astral-sh/ruff-action@v3
typo:
runs-on: ubuntu-latest
steps:
- name: Check out code.
- uses: actions/checkout@v4
- - name: spell check
- run: |
- pip install codespell==2.2
- git grep --cached -l '' | grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | xargs codespell --ignore-words=.github/.ignore_words
-
- test:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
- - name: Set up Python 3.12
- uses: actions/setup-python@v5
+ uses: actions/checkout@v6
+ - name: Install poetry
+ run: pip install poetry
+ - name: Set up Python
+ uses: actions/setup-python@v6
with:
- python-version: '3.12'
+ cache: 'poetry'
+ cache-dependency-path: "tests/pyproject.toml"
+ python-version-file: "tests/pyproject.toml"
- name: Install dependencies
run: |
- python -m pip install --upgrade pip
- pip install pytest==8.1.2 GitPython==3.1.43 testpath==0.6.0
- - name: Unit test
- run: make test
+ cd tests || exit
+ poetry install --only dev
+ - name: spell check
+ run: |
+ cd tests
+ git grep --cached -l '' .. | \
+ grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \
+ xargs poetry run codespell --ignore-words=../.github/.ignore_words
+
+ test-pytest:
+ name: 'Test with Pytest'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ submodules: recursive
+ - name: Install poetry
+ run: pip install poetry
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.12'
+ cache: 'poetry'
+ cache-dependency-path: "tests/pyproject.toml"
+ python-version-file: "tests/pyproject.toml"
+ - name: Install Python Dependencies
+ run: |
+ cd tests || exit
+ poetry install --only test
+ - name: Test with Pytest
+ run: |
+ cd tests
+ poetry run pytest
+
+ test-bats:
+ name: 'Test with Bats'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ submodules: recursive
+ - name: Setup Bats
+ id: setup-bats
+ uses: bats-core/bats-action@4.0.0
+ with:
+ bats-version: 'v1.8.1'
+ - name: Test with Bats
+ env:
+ BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
+ TERM: xterm
+ run: bats ./tests
build:
strategy:
@@ -64,14 +120,12 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- name: Check out code.
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Linux Install
if: matrix.platform == 'ubuntu-latest'
- run: |
- sudo apt-get install -y bsdmainutils
+ run: sudo apt-get install -y bsdmainutils
- name: Script
- run: |
- ./check_integrity.sh $(find bin | cut -b 5- | xargs)
+ run: ./check_integrity.sh
- name: Brew release
if: matrix.platform == 'macos-latest'
run: |
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
new file mode 100644
index 0000000..ff2fc42
--- /dev/null
+++ b/.github/workflows/stale.yaml
@@ -0,0 +1,18 @@
+name: "Maintenance: Close Stale PRs"
+on:
+ schedule:
+ - cron: "30 1 * * *"
+
+permissions:
+ pull-requests: "write"
+
+jobs:
+ stale:
+ runs-on: "ubuntu-latest"
+ if: github.repository_owner == 'tj'
+ steps:
+ - uses: "actions/stale@v10"
+ with:
+ close-pr-message: "This PR was closed because it has been stalled for 365 days with no activity. Feel free to make a new PR if you wish to continue"
+ days-before-pr-stale: 350
+ days-before-pr-close: 15
diff --git a/.gitignore b/.gitignore
index a230a78..294ece4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.venv/
__pycache__/
+coverage/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..33dec90
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "vendor/bats-all"]
+ path = vendor/bats-all
+ url = https://github.com/hyperupcall/bats-all
diff --git a/AUTHORS b/AUTHORS
index a008703..06598c6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,12 +1,8 @@
-git-extras is written and maintained by TJ Holowaychuk and
-various contributors:
-
-Development Lead
-````````````````
-- TJ Holowaychuk
+git-extras is written and maintained by various contributors:
Maintainers
```````````
+- TJ Holowaychuk
- Hemanth.HM
- Nimit Kalra
- Nicolai Skogheim
@@ -43,12 +39,15 @@ Patches and Suggestions
- Luke Childs
- Sasha Khamkov
- equt
+- oikarinen
- vyas
- Don Harper
+- Pierre Ayoub
- Robin Winslow
- Ross Smith II
- Yi EungJun
- grindhold
+- wyattscarpenter
- Aggelos Orfanakos
- Camille Reynders
- Carlos Prado
@@ -65,11 +64,13 @@ Patches and Suggestions
- Dominik Gedon
- Hogan Long
- Ivan Malopinsky
+- John Bachir
- Jonathan "Duke" Leto
- Julio Napurí
- Justin Dugger
- Nils Winkler
- Philipp Klose
+- Raphael Boidol
- Richard Russon
- Sam Bostock
- Vladimir Jimenez
@@ -85,6 +86,7 @@ Patches and Suggestions
- Beth Skurrie
- Brice Dutheil
- Curtis McEnroe
+- Daniele Paolella
- David Rogers
- Devin Withers
- Domenico Rotiroti
@@ -129,6 +131,7 @@ Patches and Suggestions
- Andrew Griffiths
- Andrew Marcinkevičius
- Andrew Starr-Bochicchio
+- Andrew Sullivan Cant
- Andrey Elizarov
- Angel Aguilera
- Antoine Beaupré
@@ -146,6 +149,7 @@ Patches and Suggestions
- Christophe Badoit
- Ciro Nunes
- CleanMachine1
+- CodeByZach
- Craig MacGregor
- Dan Goodliffe
- Dan Jackson
@@ -160,20 +164,24 @@ Patches and Suggestions
- Edward Betts
- Eli Schwartz
- Emil Kjelsrud
+- Erik Skultety
- François M
- George Crabtree
- Gerrit-K
- Greg Allen
+- Guilhem Saurel
- Guillermo Rauch
- Gunnlaugur Thor Briem
- Hasse Ramlev Hansen
- Heiko Becker
+- Hugo Ruiz-Mireles
- Isaac Mungai
- J.C. Yamokoski
- Jack O. Wasey
- James Manning
- James Zhu
- Jan Krueger
+- Jared Baur
- Jarod Stewart
- Jason Young
- Jens K. Mueller
@@ -201,7 +209,9 @@ Patches and Suggestions
- Mathieu D. (MatToufoutu)
- Matt
- Matt Colyer
+- Matt Headley
- Matthew Avant
+- Mattias Andersson
- Michael Komitee
- Michael Matuzak
- Michele Bologna
@@ -224,9 +234,12 @@ Patches and Suggestions
- Raphael Fleischlin
- Rasmus Wriedt Larsen
- René
+- Revisor
+- Ricardo Fernández Serrata
- Riceball LEE
- Rob Kennedy
- Robin von Bülow
+- Russ
- Ryan Bohn
- Sachin Gupta
- Sam Thursfield
@@ -254,6 +267,8 @@ Patches and Suggestions
- Vitaly Chikunov
- Wei Lee
- Wei Wu
+- Wen Sun
+- Wiktor Żurawik
- Xavier Krantz
- Xiaopei Li
- Zeeshan Ahmed
@@ -262,15 +277,16 @@ Patches and Suggestions
- dead-horse
- eszabpt
- fengkx
+- johnpyp
- jykntr
- kang
- meza
- neydroid
- nulltask
+- phanium
- sgleizes
- tfendin
- tiemonl
-- Zachary Miller
- zentarul
- zeroDivisible
- zhiyanfoo
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0e7e7a3..7336c6f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,7 +25,7 @@ Let's say you wish to add a new command. Assuming your new command is named `foo
5. (Optional) Update `./etc/bash_completion.sh`.
6. (Optional) Update `./etc/git-extras.fish`.
7. (Optional) Add a test under `./tests`.
-8. Run `./check_integrity.sh foo` to check if all done.
+8. Run `./check_integrity.sh foo` to check if all done. (You may also run `./check_integrity.sh` to check all commands.)
You are welcome to open up an issue to discuss new commands or features before opening a pull request.
diff --git a/Commands.md b/Commands.md
index af72af0..295f71e 100644
--- a/Commands.md
+++ b/Commands.md
@@ -12,6 +12,7 @@
- [`git clear-soft`](#git-clear-soft)
- [`git coauthor`](#git-coauthor)
- [`git commits-since`](#git-commits-since)
+ - [`git continue`](#git-continue)
- [`git contrib`](#git-contrib)
- [`git count`](#git-count)
- [`git cp`](#git-cp)
@@ -74,6 +75,8 @@
- [`git undo`](#git-undo)
- [`git unlock`](#git-unlock)
- [`git utimes`](#git-utimes)
+ - [`git unwip`](#git-unwip)
+ - [`git wip`](#git-wip)
## git extras
@@ -279,11 +282,20 @@ usage: git bulk [-g] ([-a]|[-w ])
git bulk --listall
```
- Register a workspace so that `git bulk` knows about it (notice that must be absolute path):
+ Register a workspace so that `git bulk` knows about it (it will be registered in your `.gitconfig`):
```bash
$ git bulk --addworkspace personal ~/workspaces/personal
```
+
+ Notice that `` must be an absolute path (or an environment variable pointing to an absolute path).
+ In the case of a **single quoted environment variable**, it will be dereferenced at `git-bulk` runtime, suitable for dynamic workspaces (*e.g.*, defined in your `.bashrc`).
+ As an illustration:
+
+```bash
+$ git bulk --addworkspace personal '$PERSONAL_WORKSPACE'
+```
+
With option `--from` the URL to a single repository or a file containing multiple URLs can be added and they will be cloned directly into the workspace. Suitable for the initial setup of a multi-repo project.
```bash
@@ -375,14 +387,53 @@ Git read-eval-print-loop. Lets you run `git` commands without typing 'git'.
Commands can be prefixed with an exclamation mark (!) to be interpreted as
a regular command.
-Type `exit` or `quit` to end the repl session.
+Type `exit`, `quit`, or `q` to end the repl session.
+
+Any arguments to git repl will be taken as the first command to execute in
+the repl.
+
+### CONFIGURATION
+
+Commands entered in a repl session will be saved to a history file and be available in
+future sessions, similar to a shell or programming language repl. By default,
+there is one global history file, ~/.git_repl_history. You can specify that your projects
+each have their own independent history file. This file will be saved in .git_repl_history
+at the top level of the repo, and will need to be added to the repo or global .gitignore.
+
+```bash
+# remove the --global flag to configure only an individual project to have its own history file
+git config --global git-extras.repl.use-local-history "true"
+```
+
+You can specify a default command to run when hitting enter:
+
+```bash
+git config --global git-extras.repl.on-enter-command "git status -sb"
+```
+
+You can configure which character is used at the end of the prompt: (default `>`):
+
+```bash
+git config --global git-extras.repl.prompt-character "±"
+```
+
+You can specify the prefix for the prompt, or remove it (default `git`):
+
+```bash
+git config --global git-extras.repl.prefix ""
+```
+
+You can have the name of the current git repo shown in the prompt (default `false`):
+
+```bash
+git config --global git-extras.repl.show-project-name "true"
+```
```bash
$ git repl
-git version 2.9.2
-git-extras version 3.0.0
-type 'ls' to ls files below current directory,
-'!command' to execute any command or just 'subcommand' to execute any git subcommand
+git version 2.34.1
+git-extras version 7.3.0
+Type 'ls' to ls files below current directory; '!command' to execute any command or just 'subcommand' to execute any git subcommand; 'quit', 'exit', 'q', ^D, or ^C to exit the git repl.
git (master)> ls-files
History.md
@@ -413,9 +464,9 @@ $ git coauthor user user@email.com
2 files changed, 145 insertions(+), 0 deletions(-)
create mode 100644 README.md
create mode 100644 CONTRIBUTING.md
-
+
$ git log -1
-
+
commit b62ceae2685e6ece071f3c3754e9b77fd0a35c88 (HEAD -> master)
Author: user person
Date: Sat Aug 17 17:33:53 2019 -0500
@@ -1296,7 +1347,7 @@ Creates a zip archive of the current git repository. The name of the archive wil
## git missing
-Print out which commits are on one branch or the other but not both.
+Print out which commits are on one branch or the other but not both. Optionally, you can specify a path to limit the comparison to a specific directory or file.
```bash
$ git missing master
@@ -1304,6 +1355,12 @@ $ git missing master
> 97ef387 only on master
```
+```bash
+$ git missing master -- src/
+< ed52989 only on current branch, in src/ directory
+> 7988c4b only on master, in src/ directory
+```
+
## git lock
Lock a local file `filename`:
@@ -1360,7 +1417,7 @@ Switched to branch 'mr/51'
With full URL, the head is fetched from a temporary remote pointing to the base URL.
``` bash
-$ git mr https://gitlab.com/owner/repository/merge_requests/51
+$ git mr https://gitlab.com/owner/repository/merge_requests/51
From gitlab.com:owner/repository
* [new ref] refs/merge-requests/51/head -> mr/51
Switched to branch 'mr/51'
@@ -1615,3 +1672,25 @@ Abort current revert, rebase, merge or cherry-pick, without the need to find exa
## git magic
Commits changes with a generated message.
+
+## git continue
+
+Continue current revert, rebase, merge or cherry-pick, without the need to find exact command in history.
+
+## git wip
+
+Create a Work In Progress(WIP) commit, which will include all changes in the
+working directory. (i.e., changes to existing files, new files, removed files)
+
+```bash
+$ git wip
+```
+
+## git unwip
+
+Undo a Work In Progress(WIP) commit and put all of those changes back into the
+working directory.
+
+```bash
+$ git unwip
+```
diff --git a/History.md b/History.md
index 20fbcbe..fc9617e 100644
--- a/History.md
+++ b/History.md
@@ -1,4 +1,83 @@
+7.5.0 / 2026-05-09
+==================
+
+ * Use standard prefix for home dir installs (#1248)
+ * refactor(abort): common `gitdir` (#1244)
+ * Fix man page usage docs (#1241)
+ * Fix `git-changelog` to handle whitespace in command arguments (#1232)
+ * chore(deps): bump bats-core/bats-action from 3.0.1 to 4.0.0 (#1230)
+ * add details when multiple active operations found (#1229)
+ * Add persistent history to `git-repl` (#1226)
+ * Support passing ref to `git-commits-since` (#1228)
+ * ability to specify command when hitting enter (#1223)
+ * Improve `git-repl` prompt (#1224)
+ * feat: `delete-branch` multiple unique branch names completions (#1221)
+ * chore(deps): bump actions/checkout from 5 to 6 (#1219)
+ * Made Git Magic use `--force-with-lease` (#1218)
+ * chore(deps): bump actions/stale from 9 to 10 (#1214)
+ * chore(deps): bump actions/setup-go from 5 to 6 (#1215)
+ * chore(deps): bump actions/setup-python from 5 to 6 (#1216)
+ * Fix minor typo in `git rename-branch` man page (#1211)
+ * Improvements to Bash completion (#1210)
+ * Fix use of `git-whatchanged` to `git-log` (#1212)
+ * chore(deps): bump actions/checkout from 4 to 5 (#1209)
+ * Bump version to 7.5.0-dev (#1207)
+
+7.4.0 / 2025-06-19
+==================
+
+ * Format comparisons, functions, and redirections to be consistent (#1201)
+ * add git-wip and git-unwip (#669)
+ * chore(deps): bump bats-core/bats-action from 3.0.0 to 3.0.1
+ * Mostly finish pytest to Bats conversion (#1200)
+ * Implement half of tests in Bats (#1187)
+ * Add stale bot for old PRs (#1186)
+ * feat(git-bulk): add new option to not follow hidden directories (#1195)
+ * Feat: allow git-summary showing full path of repository (#1193)
+ * feat(git-bulk): add new option to no follow symlinks (#1194)
+ * docs(git-bulk): Add zsh completion (#1190)
+ * fix(git-bulk): fix workspace selection when cd fails (#1197)
+ * fix(git-bulk): quiet find errors by default (#1196)
+ * fix(git-bulk): fix a bad integer expression (#1198)
+ * chore(deps): bump astral-sh/ruff-action from 2 to 3 (#1189)
+ * Fix all ShellCheck errors and add to CI (#1179)
+ * chore(deps): bump astral-sh/ruff-action from 1 to 2 (#1188)
+ * fix(ci): use poetry (#1183)
+ * Delete etc/test.fish (#1185)
+ * feat: add git-continue (#1176)
+ * feat: add ruff linter with ci check (#1182)
+ * fix(ci): missing dollar sign (#1184)
+ * fix(github-actions): changed files output for editorcondig-checker (#1180)
+ * Revert "feat: add ruff linter with ci check (#1178)" (#1181)
+ * feat: add ruff linter with ci check (#1178)
+ * Support `GITHUB_TOKEN` var for `git-fork` and `git-pull-request` (#1177)
+ * Bump version to 7.4.0-dev (#1175)
+
+7.3.0 / 2024-10-20
+==================
+
+ * Fix stripping trailing forward slash for git-get (#1172)
+ * Change git-cp to use cleaner branch approach (#1169)
+ * Improve warning for `git clear` (#1168)
+ * Enhance `git-repl` (#1160)
+ * Update some documentation that was out of sync (#1164)
+ * Use filetimes in check_integrity (#1162)
+ * Update git-alias.md: add brs to prevent incorrect line behavior (#1161)
+ * Update git-bulk.md: use correct stylization in synopsis (#1163)
+ * Update git-repl.md: typo: "let's" for "lets" (#1158)
+ * Update instructions for the OpenSUSE installation (#1157)
+ * Add pathspec support in `git-missing` (#1156)
+ * feat: add rename-file command (#1149)
+ * Update Commands.md (#1148)
+ * fix: git-summary commit count (#1147)
+ * tests: update dependencies (#1142)
+ * chore(deps): bump masesgroup/retrieve-changed-files from 2 to 3 (#1144)
+ * chore(deps): bump actions/setup-go from 4 to 5 (#1143)
+ * ci: add dependabot update for GitHub actions (#1141)
+ * ci: update actions (#1140)
+ * Bump version to 7.3.0-dev (#1139)
+
7.2.0 / 2024-04-21
==================
diff --git a/Installation.md b/Installation.md
index 2e82a69..dfe2ccc 100644
--- a/Installation.md
+++ b/Installation.md
@@ -41,10 +41,10 @@ $ sudo dnf install git-extras
### openSUSE
-Substitute your openSUSE version in the command below (in this case we are considering openSUSE Leap 15.2):
+Substitute your openSUSE version in the command below (in this case we are considering openSUSE Leap 15.6):
```bash
-$ sudo zypper ar https://download.opensuse.org/repositories/devel:/tools:/scm/openSUSE_Leap_15.2/devel:tools:scm.repo
+$ sudo zypper ar https://download.opensuse.org/repositories/devel:/tools:/scm/15.6/devel:tools:scm.repo
```
and install it:
@@ -144,7 +144,7 @@ alternate location, specify a `PREFIX` when calling `make`.
```bash
# Non-root users can install under their home directory
-make install PREFIX=$HOME/software
+make install PREFIX=$HOME/.local
# For third-party software kept under /opt
make install PREFIX=/opt
diff --git a/bin/git-abort b/bin/git-abort
index 9c17380..19b544a 100755
--- a/bin/git-abort
+++ b/bin/git-abort
@@ -1,17 +1,45 @@
#!/usr/bin/env bash
+set -euo pipefail
+
+# git rev-parse emits an error if not in a git repo so only need to bail out
gitdir="$(git rev-parse --git-dir)" || exit
-opfound=
-fcnt=
-for i in cherry-pick merge rebase revert; do
- f=${i^^}
- f=${f/-/_}
- test -f "${gitdir}/${f}_HEAD" && fcnt=1$fcnt && opfound=$i
-done
-if [ "${fcnt}" != 1 ]; then
- echo "I don't know what to abort" >&2
- exit 1
-fi
+discover_op() {
+ local op
+ for op in cherry_pick merge rebase revert ; do
+ if [ -f "${gitdir}/${op^^}_HEAD" ]; then
+ echo "${op/_/-}"
+ fi
+ done
+}
-git "${opfound}" --abort
+validate_op() {
+ local op="$1"
+ if [ -z "$op" ]; then
+ echo "No active operation found" >&2
+ exit 1
+ fi
+ if [[ "$(echo "$op" | wc -l)" -gt 1 ]]; then
+ echo "Multiple active operations found:" >&2
+ for o in $op; do
+ echo " - $o: HEAD: $(cat "${gitdir}/${o}_HEAD")" >&2
+ done
+ exit 1
+ fi
+}
+
+discover_action() {
+ local action=${1/git-/}
+ if [ "$action" != "abort" ] && [ "$action" != "continue" ]; then
+ echo "Invalid action: $1" >&2
+ exit 1
+ fi
+ echo "$action"
+}
+
+action=$(discover_action "$(basename "$0")")
+op=$(discover_op)
+validate_op "$op"
+
+git "$op" "--$action"
diff --git a/bin/git-bulk b/bin/git-bulk
index e3bdd66..06eecd2 100755
--- a/bin/git-bulk
+++ b/bin/git-bulk
@@ -9,12 +9,14 @@ guardedmode=false
singlemode=false
allwsmode=false
quiet=false
+no_follow_symlinks=false
+no_follow_hidden=false
#
# print usage message
#
usage() {
- echo 1>&2 "usage: git bulk [-q|--quiet] [-g] ([-a]|[-w ]) "
+ echo 1>&2 "usage: git bulk [--no-follow-symlinks] [--no-follow-hidden] [-q|--quiet] [-g] ([-a]|[-w ]) "
echo 1>&2 " git bulk --addworkspace (--from )"
echo 1>&2 " git bulk --removeworkspace "
echo 1>&2 " git bulk --addcurrent "
@@ -28,7 +30,7 @@ cdfail() {
}
# add another workspace to global git config
-function addworkspace {
+addworkspace() {
git config --global bulkworkspaces."$wsname" "$wsdir";
if [ -n "$source" ]; then
if [ ! -d "$wsdir" ]; then echo 1>&2 "Path of workspace doesn't exist, make it first."; exit 1; fi
@@ -57,19 +59,19 @@ function addworkspace {
}
# add current directory
-function addcurrent { git config --global bulkworkspaces."$wsname" "$PWD"; }
+addcurrent() { git config --global bulkworkspaces."$wsname" "$PWD"; }
# remove workspace from global git config
-function removeworkspace { checkWSName && git config --global --unset bulkworkspaces."$wsname"; }
+removeworkspace() { checkWSName && git config --global --unset bulkworkspaces."$wsname"; }
# remove workspace from global git config
-function purge { git config --global --remove-section bulkworkspaces; }
+purge() { git config --global --remove-section bulkworkspaces; }
# list all current workspace locations defined
-function listall { git config --global --get-regexp bulkworkspaces; }
+listall() { git config --global --get-regexp bulkworkspaces; }
# guarded execution of a git command in one specific repository
-function guardedExecution () {
+guardedExecution () {
if [ "${quiet?}" != "true" ] || $guardedmode; then
echo 1>&2 "${bldred}->${reset} executing ${inverse}git $gitcommand${reset} in repository ${leadingpath%/*}/${bldred}${curdir##*/}${reset}"
fi
@@ -86,7 +88,7 @@ function guardedExecution () {
}
# check if the passed command is known as a core git command
-function checkGitCommand () {
+checkGitCommand () {
if git help -a | grep -o -q "\b${corecommand}\b"; then
echo 1>&2 "Core command \"$corecommand\" accepted."
else
@@ -99,7 +101,7 @@ function checkGitCommand () {
}
# check if workspace name is registered
-function checkWSName () {
+checkWSName () {
while read -r workspace; do
parseWsName "$workspace"
if [[ $rwsname == "$wsname" ]]; then return; fi
@@ -109,14 +111,24 @@ function checkWSName () {
}
# parse out wsname from workspacespec
-function parseWsName () {
+parseWsName () {
local wsspec="$1"
+ # Get the workspace value from its specification in the `.gitconfig`.
+ # May be an absolute path or a variable name of the form: `$VARNAME`
rwsdir=${wsspec#* }
+ if [[ ${rwsdir:0:1} == '$' ]]; then
+ # Dereference the `rwsdir` value which is a variable name.
+ rwsdir_varname=${rwsdir:1}
+ rwsdir=${!rwsdir_varname}
+ if [[ -z "${rwsdir}" ]]; then
+ echo 1>&2 "error: bad environment variable: $rwsdir_varname" && exit 1
+ fi
+ fi
rwsname=${wsspec#*.} && rwsname=${rwsname%% *}
}
# detects the wsname of the current directory
-function wsnameToCurrent () {
+wsnameToCurrent () {
while read -r workspace; do
if [ -z "$workspace" ]; then continue; fi
parseWsName "$workspace"
@@ -128,33 +140,44 @@ function wsnameToCurrent () {
}
# helper to check number of arguments.
-function allowedargcount () {
- if [ "$paramcount" -ne "$1" ] && [ "$paramcount" -ne "$2" ]; then
+allowedargcount () {
+ if [ "$paramcount" -ne "${1:-0}" ] && [ "$paramcount" -ne "${2:-0}" ]; then
echo 1>&2 "error: wrong number of arguments" && usage;
exit 1;
fi
}
# execute the bulk operation
-function executBulkOp () {
+executBulkOp () {
checkGitCommand
if ! $allwsmode && ! $singlemode; then wsnameToCurrent; fi # by default git bulk works within the 'current' workspace
listall | while read -r workspacespec; do
parseWsName "$workspacespec"
if [[ -n $wsname ]] && [[ $rwsname != "$wsname" ]]; then continue; fi
- eval cd "\"$rwsdir\""
+ cd "$rwsdir" || exit 1
local actual=$PWD
[ "${quiet?}" != "true" ] && echo 1>&2 "Executing bulk operation in workspace ${inverse}$actual${reset}"
- allGitFolders=( $(eval find -L . -name ".git") )
+ # build `find` flags depending on command-line options
+ local find_flags=()
+ if [[ "$no_follow_symlinks" == true ]]; then
+ find_flags+=(-P)
+ else
+ find_flags+=(-L)
+ fi
+ # find all git repositories under the workspace on which we want to operate
+ readarray allGitFolders < <(find "${find_flags[@]}" . -name ".git" 2>/dev/null)
for line in "${allGitFolders[@]}"; do
local gitrepodir=${line::${#line}-5} # cut the .git part of find results to have the root git directory of that repository
- eval cd "\"$gitrepodir\"" # into git repo location
+ cd "$gitrepodir" || exit 1 # into git repo location
local curdir=$PWD
local leadingpath=${curdir#"${actual}"}
- guardedExecution "$@"
- eval cd "\"$rwsdir\"" # back to origin location of last find command
+ # do not execute if we do not want to consider a ".git" directory under a hidden directory
+ if [ $no_follow_hidden = false ] || ! [[ "$leadingpath" =~ "/." ]]; then
+ guardedExecution "$@"
+ fi
+ cd "$rwsdir" || exit 1 # back to origin location of last find command
done
done
}
@@ -171,7 +194,11 @@ while [ "${#}" -ge 1 ] ; do
--listall|--purge)
butilcommand="${1:2}" && break ;;
--removeworkspace|--addcurrent|--addworkspace)
- butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" == "--from" ]; then source="$5"; fi && break ;;
+ butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" = "--from" ]; then source="$5"; fi && break ;;
+ --no-follow-symlinks)
+ no_follow_symlinks=true ;;
+ --no-follow-hidden)
+ no_follow_hidden=true ;;
-a)
allwsmode=true ;;
-g)
diff --git a/bin/git-changelog b/bin/git-changelog
index ad5c2bb..ab4367f 100755
--- a/bin/git-changelog
+++ b/bin/git-changelog
@@ -9,6 +9,15 @@ GIT_MERGELOG_FORMAT="$(git config changelog.mergeformat)"
GIT_EDITOR="$(git var GIT_EDITOR)"
PROGNAME="git-changelog"
+git_editor() {
+ if test -z "${GIT_EDITOR:+set}"
+ then
+ GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
+ fi
+
+ eval "$GIT_EDITOR" '"$@"'
+}
+
_usage() {
cat << EOF
usage: $PROGNAME options [file]
@@ -574,7 +583,7 @@ main() {
rm -f "$tmpfile"
if [[ -n "$GIT_EDITOR" ]]; then
- $GIT_EDITOR "$changelog" || _exit
+ git_editor "$changelog" || _exit
else
less "$changelog" || _exit
fi
diff --git a/bin/git-clear b/bin/git-clear
index e443290..6fd3ae8 100755
--- a/bin/git-clear
+++ b/bin/git-clear
@@ -2,7 +2,7 @@
PROGNAME="git-clear"
FORCE=0
-function _usage() {
+_usage() {
cat << EOF
usage: $PROGNAME options
usage: $PROGNAME -h|help|?
@@ -32,12 +32,12 @@ done
# Only wait for answer if not forced by user
if [[ $FORCE == 0 ]]; then
- echo -n "Sure? - This command may delete files that cannot be recovered, including those in .gitignore [y/N]: "
+ echo -n "Sure? - THIS COMMAND MAY DELETE FILES THAT CANNOT BE RECOVERED, including those in .gitignore [y/N]: "
read -r clean
else
clean=y
fi
-if [ "$clean" == "y" ]; then
+if [ "$clean" = "y" ]; then
git clean -d -f -x && git reset --hard
fi
diff --git a/bin/git-clear-soft b/bin/git-clear-soft
index 0473b57..1274d21 100755
--- a/bin/git-clear-soft
+++ b/bin/git-clear-soft
@@ -2,6 +2,6 @@
echo -n "Sure? - This command may delete files that cannot be recovered. Files and directories in .gitignore will be preserved [y/N]: "
read -r answer
-if [ "$answer" == "y" ]
+if [ "$answer" = "y" ]
then git clean -d -f && git reset --hard
fi
diff --git a/bin/git-commits-since b/bin/git-commits-since
index c07c530..5cd53e7 100755
--- a/bin/git-commits-since
+++ b/bin/git-commits-since
@@ -1,6 +1,36 @@
#!/usr/bin/env bash
-SINCE="last week"
-test $# -ne 0 && SINCE=$*
-echo "... commits since $SINCE" >&2
-git log --pretty='%an - %s' --after="@{$SINCE}"
\ No newline at end of file
+REF=""
+SINCE=""
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -r|--ref)
+ if [[ -z "$2" ]]; then
+ echo "error: option $1 requires an argument" >&2
+ exit 1
+ fi
+ REF="$2"
+ shift
+ shift
+ ;;
+ *)
+ # non-flag args accumulation, e.g. "last " + "week" -> "last week"
+ SINCE="${SINCE:+$SINCE }$1"
+ shift
+ ;;
+ esac
+done
+
+if [[ -n "$REF" && -n "$SINCE" ]]; then
+ echo "error: '--ref [' and '' are mutually exclusive" >&2
+ exit 1
+fi
+
+SINCE="${SINCE:-last week}"
+
+if [[ -n "$REF" ]]; then
+ git log --pretty='%h %an - %s' "$REF..HEAD"
+else
+ git log --pretty='%h %an - %s' --after="@{$SINCE}"
+fi
diff --git a/bin/git-continue b/bin/git-continue
new file mode 120000
index 0000000..31ceda5
--- /dev/null
+++ b/bin/git-continue
@@ -0,0 +1 @@
+git-abort
\ No newline at end of file
diff --git a/bin/git-cp b/bin/git-cp
index 0911824..cabb564 100755
--- a/bin/git-cp
+++ b/bin/git-cp
@@ -50,37 +50,29 @@ else
fi
fi
- MERGE_OPT=
- ff=$(git config --get merge.ff || true)
- if [[ "$ff" == "only" ]]; then
- MERGE_OPT="--ff"
- fi
echo "Copying $CURRENT_FILENAME into $DESTINATION_FILENAME"
- INTERMEDIATE_FILENAME="${CURRENT_FILENAME//\//__}-move-to-${DESTINATION_FILENAME//\//__}"
+ # Pre-check that the source and destination will work
+ git mv --dry-run "${CURRENT_FILENAME}" "${DESTINATION_FILENAME}"
- # We keep the existing file on the side in a commit
- git mv "${CURRENT_FILENAME}" "${INTERMEDIATE_FILENAME}"
- git commit -nm "Keep $CURRENT_FILENAME"
+ # Make a new branch and switch to it
+ BRANCH_NAME="git-cp-$(date +%s)"
+ git checkout -b "$BRANCH_NAME"
- # We come back to the previous state and revert that change
- INTERMEDIATE_SAVED=$(git rev-parse HEAD)
- git reset --hard HEAD^
-
- # We move the file to its new destination
+ # Move the original file to the new destination, in this branch
git mv "${CURRENT_FILENAME}" "${DESTINATION_FILENAME}"
- git commit -nm "Copy $CURRENT_FILENAME into $DESTINATION_FILENAME"
+ git commit -nm "--Duplicate $CURRENT_FILENAME history into $DESTINATION_FILENAME"
- # We come back to the previous state and revert that change again
- DESTINATION_SAVED=$(git rev-parse HEAD)
- git reset --hard HEAD^
+ # Restore the original file to keep it in the history
+ git checkout HEAD~ "${CURRENT_FILENAME}"
+ git commit -nm "--Restore $CURRENT_FILENAME"
- # We keep both files
- # shellcheck disable=SC2086
- git merge $MERGE_OPT "${DESTINATION_SAVED}" "${INTERMEDIATE_SAVED}" -m "Duplicate ${CURRENT_FILENAME} history."
+ # Switch to the original branch and merge this back in.
+ git checkout -
+ git merge --no-ff "$BRANCH_NAME" -m "Copy $CURRENT_FILENAME into $DESTINATION_FILENAME"
- # We get back our original name
- git mv "${INTERMEDIATE_FILENAME}" "${CURRENT_FILENAME}"
- git commit -nm "Set back ${CURRENT_FILENAME} file"
+ # We're now done with the branch, so delete it.
+ # We shouldn't need -D here, as we've already merged it back in.
+ git branch -d "$BRANCH_NAME"
fi
diff --git a/bin/git-create-branch b/bin/git-create-branch
index c11c4ea..369a5f8 100755
--- a/bin/git-create-branch
+++ b/bin/git-create-branch
@@ -39,7 +39,7 @@ then
REMOTE=origin
fi
-test -z $BRANCH && echo "branch argument required." 1>&2 && exit 1
+test -z "$BRANCH" && echo "branch argument required." 1>&2 && exit 1
if [[ -n $REMOTE ]]
then
diff --git a/bin/git-extras b/bin/git-extras
index b9d6b42..d57dd7e 100755
--- a/bin/git-extras
+++ b/bin/git-extras
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-VERSION="7.3.0-dev"
+VERSION="7.6.0-dev"
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh"
update() {
diff --git a/bin/git-fork b/bin/git-fork
index ba85283..2242c4a 100755
--- a/bin/git-fork
+++ b/bin/git-fork
@@ -17,9 +17,9 @@ read -r user
# personal access token
# config name is github-personal-access-token '_' is not allowed in git config
-github_personal_access_token=$(git config git-extras.github-personal-access-token)
+github_personal_access_token=$(git config --default "$GITHUB_TOKEN" git-extras.github-personal-access-token)
-test -z "$github_personal_access_token" && abort "git config git-extras.github-personal-access-token required"
+test -z "$github_personal_access_token" && abort "GITHUB_TOKEN, or git config git-extras.github-personal-access-token required"
# extract owner + project from repo url
project=${url##*/}
@@ -61,7 +61,7 @@ else
# clone forked repo into current dir
git clone "${remote_prefix}${user}/${project}.git" "$project"
# add reference to origin fork so can merge in upstream changes
- cd "$project"
+ cd "$project" || exit
git remote add upstream "${remote_prefix}${owner}/${project}.git"
git fetch upstream
fi
diff --git a/bin/git-get b/bin/git-get
index 978badd..af43e42 100755
--- a/bin/git-get
+++ b/bin/git-get
@@ -33,7 +33,7 @@ if [ -z "$clone_path" ]; then
fi
dirname=${url%/}
-dirname=${url%.git}
+dirname=${dirname%.git}
dirname=${dirname##*/}
mkdir -p "$clone_path"
diff --git a/bin/git-guilt b/bin/git-guilt
index 3fe644a..30e2e0a 100755
--- a/bin/git-guilt
+++ b/bin/git-guilt
@@ -31,7 +31,7 @@ do
esac
done
-cd "$(git-root)" # cd for git blame
+cd "$(git-root)" || exit # cd for git blame
MERGED_LOG=$(git_extra_mktemp)
if [[ $EMAIL == '-e' ]]
then
@@ -44,9 +44,11 @@ for file in $(git diff --name-only "$@")
do
test -n "$DEBUG" && echo "git blame $file"
# $1 - since $2 - until
+ # shellcheck disable=SC2086
git blame $NOT_WHITESPACE --line-porcelain "$1" -- "$file" 2> /dev/null |
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/- \1/' >> "$MERGED_LOG"
# if $2 not given, use current commit as "until"
+ # shellcheck disable=SC2086
git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null |
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/+ \1/' >> "$MERGED_LOG"
done
@@ -71,7 +73,7 @@ END {
printf("%d %s\n", contributors[people], people)
}
}
-}' $MERGED_LOG | sort -nr | # only gawk supports built-in sort function
+}' "$MERGED_LOG" | sort -nr | # only gawk supports built-in sort function
while read -r line
do
people=${line#* }
@@ -103,7 +105,7 @@ do
do
printf "-"
done
- printf "(%s)" $num
+ printf "(%s)" "$num"
else
for (( i = 0; i > num; i-- ))
do
diff --git a/bin/git-ignore b/bin/git-ignore
index e380a2e..34a1791 100755
--- a/bin/git-ignore
+++ b/bin/git-ignore
@@ -2,7 +2,7 @@
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
-function show_contents {
+show_contents() {
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
echo "$1 gitignore: $2" && cat "$file"
@@ -11,7 +11,7 @@ function show_contents {
fi
}
-function cd_to_git_root {
+cd_to_git_root() {
local error_level="$1"
if ! git rev-parse --git-dir &>/dev/null; then
@@ -29,7 +29,7 @@ function cd_to_git_root {
fi
}
-function global_ignore() {
+global_ignore() {
if ! git config --global core.excludesFile 2>/dev/null; then
if [ -f "$HOME/.gitignore" ]; then
echo "$HOME/.gitignore"
@@ -39,11 +39,11 @@ function global_ignore() {
fi
}
-function show_global {
+show_global() {
show_contents Global "$(global_ignore)"
}
-function add_global {
+add_global() {
local global_gitignore
global_gitignore="$(global_ignore)"
if [ -z "$global_gitignore" ]; then
@@ -56,28 +56,28 @@ function add_global {
fi
}
-function show_local {
+show_local() {
cd_to_git_root --warn
show_contents Local .gitignore
}
-function add_local {
+add_local() {
cd_to_git_root --warn
add_patterns .gitignore "$@"
}
-function show_private {
+show_private() {
cd_to_git_root --error
show_contents Private "${GIT_DIR}/info/exclude"
}
-function add_private {
+add_private() {
cd_to_git_root --error
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
add_patterns "${GIT_DIR}/info/exclude" "$@"
}
-function add_patterns {
+add_patterns() {
echo "Adding pattern(s) to: $1"
local file="${1/#~/$HOME}"
dir_name=$(dirname "$file")
diff --git a/bin/git-magic b/bin/git-magic
index 8bc159a..de1dbaf 100755
--- a/bin/git-magic
+++ b/bin/git-magic
@@ -30,7 +30,7 @@ while getopts "m:eapfh" arg; do
PUSH=true
;;
f)
- FORCE='-f'
+ FORCE='--force-with-lease'
;;
h)
echo "$USAGE"
diff --git a/bin/git-merge-into b/bin/git-merge-into
index 0561a8d..e547555 100755
--- a/bin/git-merge-into
+++ b/bin/git-merge-into
@@ -18,7 +18,7 @@ then
git stash
fi
-if [ "${!#}" == '--ff-only' ]; then
+if [ "${!#}" = '--ff-only' ]; then
case $# in
2 ) # dest --ff
git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";;
diff --git a/bin/git-missing b/bin/git-missing
index 9569468..32fbdab 100755
--- a/bin/git-missing
+++ b/bin/git-missing
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
usage() {
- echo 1>&2 "usage: git missing [] []"
+ echo 1>&2 "usage: git missing [] [] [[--] ...]"
}
if [ "${#}" -lt 1 ]
@@ -12,9 +12,20 @@ fi
declare -a git_log_args=()
declare -a branches=()
+declare -a pathspec=()
+declare parse_path=false
+
for arg in "$@" ; do
+ if [[ $parse_path == true ]]; then
+ pathspec+=("$@")
+ break
+ fi
+
case "$arg" in
+ --)
+ parse_path=true
+ ;;
--*)
git_log_args+=( "$arg" )
;;
@@ -38,4 +49,4 @@ else
exit 1
fi
-git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right
+git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right -- "${pathspec[@]}"
diff --git a/bin/git-obliterate b/bin/git-obliterate
index 992e0eb..83e4ca1 100755
--- a/bin/git-obliterate
+++ b/bin/git-obliterate
@@ -9,15 +9,15 @@ do
file="$file"' '"$i"
shift
done
-test -n "$*" && range="$*"
+test -n "$*" && range=("$@")
test -z "$file" && echo "file required." 1>&2 && exit 1
-if [ -z "$range" ]
+if [ -z "${range[*]}" ]
then
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
--prune-empty --tag-name-filter cat -- --all
else
- # don't quote $range so that we can forward multiple rev-list arguments
+ # $range is an array so that we can forward multiple rev-list arguments
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
- --prune-empty --tag-name-filter cat -- $range
+ --prune-empty --tag-name-filter cat -- "${range[@]}"
fi
diff --git a/bin/git-paste b/bin/git-paste
index 33643dd..173367c 100755
--- a/bin/git-paste
+++ b/bin/git-paste
@@ -2,7 +2,7 @@
set -e
set -o pipefail
-if ! command -v pastebinit >/dev/null 2>&1; then
+if ! command -v pastebinit &>/dev/null; then
echo >&2 "To run 'git paste', you need to install pastebinit in your system"
exit 1
fi
diff --git a/bin/git-psykorebase b/bin/git-psykorebase
index b3e1a7c..a5e7577 100755
--- a/bin/git-psykorebase
+++ b/bin/git-psykorebase
@@ -5,7 +5,7 @@ SECONDARY_BRANCH=""
FF="--ff"
CONTINUE="no"
-function current_branch() {
+current_branch() {
git rev-parse --abbrev-ref HEAD
}
diff --git a/bin/git-pull-request b/bin/git-pull-request
index 5787da1..e31de4f 100755
--- a/bin/git-pull-request
+++ b/bin/git-pull-request
@@ -31,9 +31,9 @@ EOF
# personal access token
# config name is github-personal-access-token '_' is not allowed in git config
-github_personal_access_token=$(git config git-extras.github-personal-access-token)
+github_personal_access_token=$(git config --default "$GITHUB_TOKEN" git-extras.github-personal-access-token)
-test -z "$github_personal_access_token" && abort "git config git-extras.github-personal-access-token required"
+test -z "$github_personal_access_token" && abort "GITHUB_TOKEN or git config git-extras.github-personal-access-token required"
# branch
@@ -43,7 +43,7 @@ if [ -z "$remote" ]; then
echo 'no upstream found, push to origin as default'
remote="origin"
fi
-[ "$remote" == "." ] && abort "the upstream should be a remote branch."
+[ "$remote" = "." ] && abort "the upstream should be a remote branch."
# make sure it's pushed
diff --git a/bin/git-rebase-patch b/bin/git-rebase-patch
index 395304e..521d5fd 100755
--- a/bin/git-rebase-patch
+++ b/bin/git-rebase-patch
@@ -30,7 +30,7 @@ do
GIT_INDEX_FILE=$index git read-tree "$rev"
# Try to apply the patch.
- GIT_INDEX_FILE=$index git apply --cached "$1" >/dev/null 2>&1
+ GIT_INDEX_FILE=$index git apply --cached "$1" &>/dev/null
patch_failed=$?
# Do it again, but show the error, if the problem is the patch itself.
diff --git a/bin/git-release b/bin/git-release
index 676495d..8df102b 100755
--- a/bin/git-release
+++ b/bin/git-release
@@ -119,7 +119,7 @@ if test $# -gt 0; then
fi
declare -a sign_args
- if [ "$sign" == true ]; then
+ if [ "$sign" = true ]; then
sign_args=("-s")
fi
diff --git a/bin/git-repl b/bin/git-repl
index f9996f6..5390f8d 100755
--- a/bin/git-repl
+++ b/bin/git-repl
@@ -1,8 +1,25 @@
#!/usr/bin/env bash
+shopt -s extglob
git version
echo "git-extras version ""$(git-extras -v)"
-echo "type 'ls' to ls files below current directory, '!command' to execute any command or just 'subcommand' to execute any git subcommand"
+echo "Type 'ls' to ls files below current directory; '!command' to execute any command or just 'subcommand' to execute any git subcommand; 'quit', 'exit', 'q', ^D, or ^C to exit the git repl."
+
+HISTIGNORE=${HISTIGNORE:-+([[:space:]])}
+HISTCONTROL=${HISTCONTROL:-ignoredups}
+use_local_history=$(git config --get --default 'false' git-extras.repl.use-local-history)
+if [[ "$use_local_history" == "true" ]]; then
+ HISTFILE="$(git rev-parse --show-toplevel)/.git_extras_repl_history"
+else
+ HISTFILE=${XDG_STATE_HOME:-$HOME/.local/state}/git_extras_repl_history
+fi
+
+# file doesn't exist, is empty, or contains only whitespace
+if [[ ! -f "$HISTFILE" ]] || [[ ! -s "$HISTFILE" ]] || ! grep -q '[^[:space:]]' "$HISTFILE"; then
+ # `history -r` .... `history -a` are not happy with an empty initial file in bash 3.2.57, which is what MacOS 26 ships with
+ echo '!echo welcome to git-repl!' >> "$HISTFILE"
+fi
+history -r
while true; do
# Current branch
@@ -10,34 +27,73 @@ while true; do
# Prompt
if test -n "$cur"; then
- prompt="git ($cur)> "
+ cur_string=" ($cur)"
else
- prompt="git> "
+ cur_string=""
+ fi
+ if test -n "$exit_status" && test "$exit_status" -ne 0; then
+ es_string=$' \e[31m['"$exit_status"$']\e[0m'
+ else
+ es_string=""
+ fi
+ prompt_character=$(git config --get --default '>' git-extras.repl.prompt-character)
+
+ prefix=$(git config --get --default 'git' git-extras.repl.prefix)
+
+ show_project_name=$(git config --get --default 'false' git-extras.repl.show-project-name)
+ if [[ "$show_project_name" == "true" ]]; then
+ project_name=" $(basename "$(git rev-parse --show-toplevel)" .git)"
+ else
+ project_name=""
fi
- # Readline
- read -e -r -p "$prompt" cmd
+ prompt_base="$prefix$project_name$cur_string$es_string$prompt_character"
+ prompt_base_stripped=$(echo "$prompt_base" | awk '{$1=$1};1')
+ prompt="$prompt_base_stripped "
- # EOF
- test $? -ne 0 && break
+ # Use arguments as a command if any are provided.
+ if [ $# -ne 0 ]; then
+ cmd=$*
+ set --
+ echo "$prompt" "$cmd" # It is as though you had entered a command.
+ else
+ # Readline
+ read -e -r -p "$prompt" cmd
+ # Check for EOF, and end the program if so (handles ^D).
+ test $? -ne 0 && break
+ fi
- # History
- history -s "$cmd"
+ # Add command to history if it is not all whitespace
+ if [[ ! "$cmd" =~ ^[[:space:]]*$ ]]; then
+ history -s "$cmd"
+ fi
# Built-in commands
case $cmd in
ls) cmd=ls-files;;
- "") continue;;
- quit|exit) break;;
+ "")
+ on_enter_cmd=$(git config --get --default '' git-extras.repl.on-enter-command)
+ if test -n "$on_enter_cmd"; then
+ cmd="$on_enter_cmd"
+ else
+ continue
+ fi
+ ;;
+ quit|exit|q) break;;
esac
+ history -a
+
if [[ $cmd == !* ]]; then
- eval ${cmd:1}
+ # shellcheck disable=SC2086
+ eval ${cmd:1}
elif [[ $cmd == git* ]]; then
+ # shellcheck disable=SC2086
eval $cmd
else
eval git "$cmd"
fi
+ exit_status=$?
done
echo
diff --git a/bin/git-scp b/bin/git-scp
index 52d4d14..7a10a1f 100755
--- a/bin/git-scp
+++ b/bin/git-scp
@@ -59,7 +59,7 @@ function php_lint()
function _dos2unix()
{
- command -v dos2unix > /dev/null && dos2unix $@
+ command -v dos2unix > /dev/null && dos2unix "$@"
return 0
}
@@ -68,8 +68,8 @@ function _sanitize()
git config --get-all extras.scp.sanitize | while read -r i
do
case $i in
- php_lint) php_lint $@;; # git config --global --add extras.scp.sanitize php_lint
- dos2unix) _dos2unix $@;; # git config --global --add extras.scp.sanitize dos2unix
+ php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint
+ dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix
esac
done
return $?
@@ -107,6 +107,7 @@ function scp_and_stage
if [ -n "$list" ]
then
local _TMP=${0///}
+ # shellcheck disable=SC2086
echo "$list" > "$_TMP" &&
_sanitize $list &&
_info "Pushing to $remote ($(git config "remote.$remote.url"))" &&
@@ -131,7 +132,7 @@ function reverse_scp()
shift
local _TMP=${0///}
- echo $@ > "$_TMP" &&
+ echo "$@" > "$_TMP" &&
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
rm "$_TMP"
}
@@ -173,8 +174,8 @@ case $(basename "$0") in
git-scp)
case $1 in
''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";;
- *) scp_and_stage $@;;
+ *) scp_and_stage "$@";;
esac
;;
- git-rscp) reverse_scp $@;;
+ git-rscp) reverse_scp "$@";;
esac
diff --git a/bin/git-setup b/bin/git-setup
index d1319ec..2a8c82d 100755
--- a/bin/git-setup
+++ b/bin/git-setup
@@ -2,7 +2,7 @@
COMMIT_MESSAGE='Initial commit'
-if [ "$1" == "-m" ]; then
+if [ "$1" = "-m" ]; then
COMMIT_MESSAGE=$2
shift; shift
fi
diff --git a/bin/git-standup b/bin/git-standup
index c0787f5..89f8afe 100755
--- a/bin/git-standup
+++ b/bin/git-standup
@@ -35,7 +35,7 @@ in_git_repo=$?
# Use colors, but only if connected to a terminal, and that terminal
# supports them.
-if command -v tput >/dev/null 2>&1; then
+if command -v tput &>/dev/null; then
ncolors=$(tput colors)
fi
if [[ -t 1 ]] && [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then
diff --git a/bin/git-summary b/bin/git-summary
index f90adf1..3dbe1b6 100755
--- a/bin/git-summary
+++ b/bin/git-summary
@@ -3,12 +3,16 @@
cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; }
+PROJECT_FULL_PATH=
SUMMARY_BY_LINE=
DEDUP_BY_EMAIL=
MERGES_ARG=
OUTPUT_STYLE=
for arg in "$@"; do
case "$arg" in
+ --full-path)
+ PROJECT_FULL_PATH=1
+ ;;
--line)
SUMMARY_BY_LINE=1
;;
@@ -51,7 +55,12 @@ if [ -n "$SUMMARY_BY_LINE" ]; then
else
[ $# -ne 0 ] && commit=$*
fi
-project=${PWD##*/}
+
+if [[ -n "$PROJECT_FULL_PATH" ]]; then
+ project=${PWD/${HOME}/\~}
+else
+ project=${PWD##*/}
+fi
#
# get date for the given
@@ -197,10 +206,10 @@ COLUMN_CMD_DELIMTER="¬" # Hopefully, this symbol is not used in branch names...
SP="$COLUMN_CMD_DELIMTER|"
print_summary_by_line() {
- if [ "$OUTPUT_STYLE" == "tabular" ]; then
+ if [ "$OUTPUT_STYLE" = "tabular" ]; then
tabular_headers="# Repo $SP Lines"
echo -e "$tabular_headers\n$project $SP $(line_count "${paths[@]}")" | column -t -s "$COLUMN_CMD_DELIMTER"
- elif [ "$OUTPUT_STYLE" == "oneline" ]; then
+ elif [ "$OUTPUT_STYLE" = "oneline" ]; then
echo "$project / lines: $(line_count "${paths[@]}")"
elif [ -n "$SUMMARY_BY_LINE" ]; then
echo
@@ -212,10 +221,10 @@ print_summary_by_line() {
}
print_summary() {
- if [ "$OUTPUT_STYLE" == "tabular" ]; then
+ if [ "$OUTPUT_STYLE" = "tabular" ]; then
tabular_headers="# Repo $SP Age $SP Last active $SP Active on $SP Commits $SP Uncommitted $SP Branch"
echo -e "$tabular_headers\n$project $SP $(repository_age) $SP $(last_active) $SP $(active_days "$commit") days $SP $(commit_count "$commit") $SP $(uncommitted_changes_count) $SP $(current_branch_name)" | column -t -s "$COLUMN_CMD_DELIMTER"
- elif [ "$OUTPUT_STYLE" == "oneline" ]; then
+ elif [ "$OUTPUT_STYLE" = "oneline" ]; then
echo "$project / age: $(repository_age) / last active: $(last_active) / active on $(active_days "$commit") days / commits: $(commit_count "$commit") / uncommitted: $(uncommitted_changes_count) / branch: $(current_branch_name)"
else
echo
@@ -227,7 +236,7 @@ print_summary() {
echo " commits : $(commit_count "$commit")"
# The file count doesn't support passing a git ref so ignore it if a ref is given
- if [ "$commit" == "HEAD" ]; then
+ if [ "$commit" = "HEAD" ]; then
echo " files : $(file_count)"
fi
echo " uncommitted : $(uncommitted_changes_count)"
diff --git a/bin/git-unwip b/bin/git-unwip
new file mode 100755
index 0000000..4e772b0
--- /dev/null
+++ b/bin/git-unwip
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+# Based on scripts from git-utils:
+# * https://github.com/ddollar/git-utils/blob/master/git-unwip
+
+# Check if the last commit is a 'WIP' commit
+LAST_COMMIT=`git log -1 --pretty=%B | tr -d '[:space:]'`
+
+if [ 'WIP' != $LAST_COMMIT ]; then
+ echo 'Last commit is not a WIP commit, so it will not be unWIP-ed.'
+ exit 1
+fi
+
+git undo --soft
diff --git a/bin/git-utimes b/bin/git-utimes
index 9955a9d..f0dc1a4 100755
--- a/bin/git-utimes
+++ b/bin/git-utimes
@@ -34,10 +34,10 @@ fi
status_opts=(--porcelain --short)
# %ct: committer date, UNIX timestamp / %at: author date, UNIX timestamp
-whatchanged_opts=(--format='%ct')
+log_opts=(--format='%ct')
if git status --help 2>&1 | grep -q -- "--no-renames"; then
status_opts+=(--no-renames)
- whatchanged_opts+=(--no-renames)
+ log_opts+=(--no-renames)
fi
if git status --help 2>&1 | grep -q -- "--untracked-files"; then
status_opts+=(--untracked-files=no)
@@ -126,6 +126,6 @@ git --no-pager status "${status_opts[@]}" . \
| cut -c 4- >"${tmpfile}"
# prefix is not stripped:
-git --no-pager whatchanged "${whatchanged_opts[@]}" . \
+git --no-pager log --raw --no-merges "${log_opts[@]}" . \
| awk "${awk_flags[@]}" "${awk_script}" "${tmpfile}" - \
| BASH_ENV='' bash "${bash_opts[@]}" -
diff --git a/bin/git-wip b/bin/git-wip
new file mode 100755
index 0000000..a25d68f
--- /dev/null
+++ b/bin/git-wip
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+# Based on scripts from git-utils:
+# * https://github.com/ddollar/git-utils/blob/master/git-wip
+# * https://github.com/ddollar/git-utils/blob/master/git-addremove
+
+git add --all
+git commit --all --message="WIP"
diff --git a/check_integrity.sh b/check_integrity.sh
index 16c2583..28bb276 100755
--- a/check_integrity.sh
+++ b/check_integrity.sh
@@ -6,7 +6,7 @@ err() {
}
make_doc() {
- echo "'touch man/git-$1.md && make man/git-$1.{1,html}'"
+ echo "touch man/git-$1.md && make man/git-$1.{1,html}"
}
check_bash_script() {
@@ -47,6 +47,11 @@ check_documentation() {
err "Create man/$cmd.1 and man/$cmd.html via $(make_doc "$1")"
fi
+ if [ "man/$cmd.md" -nt "man/$cmd.1" ] || [ "man/$cmd.md" -nt "man/$cmd.html" ]
+ then
+ err "man/$cmd.md, man/$cmd.1, and man/$cmd.html all exist, but man/$cmd.md is newer. You should rm man/$cmd.{1,html} && $(make_doc "$1")"
+ fi
+
check_git_extras_cmd_list "$@"
check_man_page_index "$@"
}
@@ -77,12 +82,9 @@ check() {
check_completion "$1"
}
-usage() {
- echo >&2 "Usage: ./check_integrity.sh [ ...]"
- exit 0
-}
+test $# == 0 && set -- $(find bin | cut -b 5- | xargs)
-test $# == 0 && usage
+./bin/git-utimes --newer
for name in "$@"; do
name=${name#git-}
diff --git a/etc/bash_completion.sh b/etc/bash_completion.sh
index 7a6360a..d48cdb3 100644
--- a/etc/bash_completion.sh
+++ b/etc/bash_completion.sh
@@ -1,6 +1,29 @@
# shellcheck shell=bash
# bash completion support for git-extras.
+__gitex_heads_unique() {
+ local branch specified=("${COMP_WORDS[@]:2}")
+ for branch in $(__git_heads); do
+ [[ " ${specified[*]} " == *" $branch "* ]] || printf '%s\n' "$branch"
+ done
+}
+
+_git_authors(){
+ __gitcomp "-l --list --no-email"
+}
+
+_git_browse(){
+ __git_complete_remote_or_refspec
+}
+
+_git_browse_ci(){
+ __git_complete_remote_or_refspec
+}
+
+_git_brv(){
+ __gitcomp "-r --reverse"
+}
+
_git_changelog(){
local s_opts=( '-a' '-l' '-t' '-f' '-s' '-n' '-p' '-x' '-h' '?' )
local l_opts=(
@@ -22,14 +45,6 @@ _git_changelog(){
__gitcomp "$merged_opts_str"
}
-_git_authors(){
- __gitcomp "-l --list --no-email"
-}
-
-_git_brv(){
- __gitcomp "-r --reverse"
-}
-
_git_coauthor(){
local oldIfs=$IFS
IFS=$'\n'
@@ -67,6 +82,10 @@ _git_contrib(){
COMPREPLY=($(compgen -W "$all" -- "$cur"))
}
+_git_commits_since(){
+ __gitcomp "-r --ref"
+}
+
_git_count(){
__gitcomp "--all"
}
@@ -76,7 +95,7 @@ __git_cp(){
}
_git_delete_branch(){
- __gitcomp "$(__git_heads)"
+ __gitcomp "$(__gitex_heads_unique)"
}
_git_delete_squashed_branches(){
@@ -136,6 +155,10 @@ _git_ignore(){
esac
}
+_git_info(){
+ __gitcomp "--color -c --no-config"
+}
+
_git_missing(){
# Suggest all known refs
__gitcomp "$(git for-each-ref --format='%(refname:short)')"
@@ -158,38 +181,40 @@ _git_reauthor(){
__gitcomp "${comp}"
}
-_git_scp(){
- __git_complete_remote_or_refspec
+__git_extras_rename(){
+ if ((COMP_CWORD == 2 || COMP_CWORD == 3)); then
+ __gitcomp "$1"
+ fi
}
-_git_stamp(){
- __gitcomp '--replace -r'
+_git_rename_branch(){
+ __git_extras_rename "$(__git_heads)"
+}
+
+_git_rename_remote(){
+ __git_extras_rename "$(__git_remotes)"
+}
+
+_git_rename_tag(){
+ __git_extras_rename "$(__git_tags)"
}
_git_rscp(){
__git_complete_remote_or_refspec
}
+_git_scp(){
+ __git_complete_remote_or_refspec
+}
+
_git_squash(){
__gitcomp "$(__git_heads)"
}
+_git_stamp(){
+ __gitcomp '--replace -r'
+}
+
_git_undo(){
__gitcomp "--hard --soft -h -s"
}
-
-_git_info(){
- __gitcomp "--color -c --no-config"
-}
-
-_git_browse(){
- __git_complete_remote_or_refspec
-}
-
-_git_browse_ci(){
- __git_complete_remote_or_refspec
-}
-
-_git_rename_file() {
- __gitcomp "-h --help"
-}
diff --git a/etc/git-extras-completion.zsh b/etc/git-extras-completion.zsh
index 3a44d6b..128d269 100644
--- a/etc/git-extras-completion.zsh
+++ b/etc/git-extras-completion.zsh
@@ -72,6 +72,15 @@ __gitex_branch_names() {
_wanted branch-names expl branch-name compadd $* - $branch_names
}
+__gitex_branch_names_unique() {
+ local expl
+ declare -a branch_names already_specified
+ branch_names=(${${(f)"$(_call_program branchrefs git for-each-ref --format='"%(refname)"' refs/heads 2>/dev/null)"}#refs/heads/})
+ __gitex_command_successful || return
+ already_specified=(${words[2,-1]})
+ _wanted branch-names expl branch-name compadd -F already_specified $* - $branch_names
+}
+
__gitex_specific_branch_names() {
local expl
declare -a branch_names
@@ -92,6 +101,13 @@ __gitex_submodule_names() {
_wanted submodule-names expl submodule-name compadd $* - $submodule_names
}
+__gitex_workspace_names() {
+ local expl
+ declare -a workspace_names
+ workspace_names=($(git bulk --listall | awk '{print $1}' | cut -d "." -f 2))
+ __gitex_command_successful || return
+ _wanted workspace-names expl workspace-names compadd $* - $workspace_names
+}
__gitex_author_names() {
local expl
@@ -122,6 +138,22 @@ _git-brv() {
'(-r --reverse)'{-r,--reverse}'[reverse order]'
}
+_git-bulk() {
+ _arguments \
+ '-a[Run a git command on all workspaces and their repositories.]' \
+ '-g[Ask the user for confirmation on every execution (guarded mode).]' \
+ '-w[Run the git command on the specified workspace.]:workspace-name:__gitex_workspace_names' \
+ '-q[Suppress bulk output about current execution (quiet mode).]' \
+ '--no-follow-symlinks[Do not traverse symbolic links when searching for git repositories.]' \
+ '--no-follow-hidden[Do not traverse hidden directories when searching for git repositories.]' \
+ '--addworkspace[Register a workspace for bulk operations.]' \
+ '--removeworkspace[Remove the specified workspace.]:workspace-name:__gitex_workspace_names' \
+ '--addcurrent[Adds the current directory as workspace to git bulk operations]' \
+ '--purge[Removes all defined repository locations.]' \
+ '--listall[List all registered repositories.]' \
+ '--help[Show the help.]'
+}
+
_git-changelog() {
_arguments \
'(-l --list)'{-l,--list}'[list commits]' \
@@ -139,6 +171,11 @@ _git-coauthor() {
':co-author-email[email address of co-author to add]:__gitex_author_emails'
}
+_git-commits-since() {
+ _arguments \
+ '(-r --ref)'{-r,--ref}'[show commits since ref]'
+}
+
_git-contrib() {
_arguments \
':author:__gitex_author_names'
@@ -173,8 +210,7 @@ _git-create-branch() {
}
_git-delete-branch() {
- _arguments \
- ':branch-name:__gitex_branch_names'
+ __gitex_branch_names_unique
}
_git-delete-squashed-branches() {
@@ -349,6 +385,7 @@ _git-standup() {
}
_git-summary() {
+ _arguments '--full-path[show repository full path]'
_arguments '--line[summarize with lines rather than commits]'
_arguments '--dedup-by-email[remove duplicate users by the email address]'
_arguments '--no-merges[exclude merge commits]'
@@ -379,6 +416,7 @@ zstyle ':completion:*:*:git:*' user-commands $existing_user_commands \
clear:'rigorously clean up a repository' \
coauthor:'add a co-author to the last commit' \
commits-since:'show commit logs since some date' \
+ continue:'continue current revert, merge, rebase, or cherry-pick process' \
contrib:'show user contributions' \
count:'show commit count' \
create-branch:'create branches' \
@@ -439,4 +477,6 @@ zstyle ':completion:*:*:git:*' user-commands $existing_user_commands \
touch:'touch and add file to the index' \
undo:'remove latest commits' \
unlock:'unlock a file excluded from version control' \
- utimes:'change files modification time to their last commit date'
+ utimes:'change files modification time to their last commit date' \
+ unwip:'undo a WIP commit' \
+ wip:'create a WIP commit'
diff --git a/etc/git-extras.fish b/etc/git-extras.fish
index 0de358f..976cfca 100644
--- a/etc/git-extras.fish
+++ b/etc/git-extras.fish
@@ -131,6 +131,8 @@ function __fish_git_extra_coauthor_email
end
complete -c git -f -n '__fish_git_using_command coauthor; and __fish_git_arg_number 2' -a '(__fish_git_extra_coauthor_name)'
complete -c git -f -n '__fish_git_using_command coauthor; and __fish_git_arg_number 3' -a '(__fish_git_extra_coauthor_email)'
+# commits-since
+complete -c git -f -n '__fish_git_using_command commits-since' -s r -l ref -d 'show commits since ref'
# count
complete -c git -f -n '__fish_git_using_command count' -l all -d 'detailed commit count'
# create-branch
diff --git a/etc/test.fish b/etc/test.fish
deleted file mode 100644
index 159e97d..0000000
--- a/etc/test.fish
+++ /dev/null
@@ -1,20 +0,0 @@
-complete -e blah
-
-function __fish_git_arg_number -a number
- set -l cmd (commandline -opc)
- test (count $cmd) -eq $number
-end
-
-function __fish_git_extra_coauthor_name
- printf '%s\n' 'a' 'apple' 'ann'
-end
-
-function __fish_git_extra_coauthor_email
- set -l cmd (commandline -opc)
-
- set -l value $cmd[3]
- printf '%s\n' 'n' 'n1' 'n2' "$value"
-end
-
-complete -c blah -f -n '__fish_git_using_command coauthor; and __fish_git_arg_number 2' -a '(__fish_git_extra_coauthor_name)'
-complete -c blah -f -n '__fish_git_using_command coauthor; and __fish_git_arg_number 3' -a '(__fish_git_extra_coauthor_email)'
diff --git a/man/git-alias.1 b/man/git-alias.1
index b0bcd9b..0a54415 100644
--- a/man/git-alias.1
+++ b/man/git-alias.1
@@ -1,10 +1,27 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
-.TH "GIT\-ALIAS" "1" "September 2023" "" "Git Extras"
+.TH "GIT\-ALIAS" "1" "September 2024" "" "Git Extras"
.SH "NAME"
\fBgit\-alias\fR \- Define, search and show aliases
.SH "SYNOPSIS"
-\fBgit\-alias\fR \fBgit\-alias\fR \fBgit\-alias\fR \fBgit\-alias\fR [\-\-global] \fBgit\-alias\fR [\-\-local] \fBgit\-alias\fR [\-\-global] \fBgit\-alias\fR [\-\-local] \fBgit\-alias\fR [\-\-global] \fBgit\-alias\fR [\-\-local]
+\fBgit\-alias\fR
+.br
+\fBgit\-alias\fR
+.br
+\fBgit\-alias\fR
+.br
+\fBgit\-alias\fR [\-\-global]
+.br
+\fBgit\-alias\fR [\-\-local]
+.br
+\fBgit\-alias\fR [\-\-global]
+.br
+\fBgit\-alias\fR [\-\-local]
+.br
+\fBgit\-alias\fR [\-\-global]
+.br
+\fBgit\-alias\fR [\-\-local]
+.br
.SH "DESCRIPTION"
List all aliases, show one alias, or set one (global or local) alias\.
.SH "OPTIONS"
@@ -50,8 +67,8 @@ $ git alias
s = status
amend = commit \-\-amend
rank = shortlog \-sn \-\-no\-merges
-whatis = show \-s \-\-pretty=\'tformat:%h (%s, %ad)\' \-\-date=short
-whois = !sh \-c \'git log \-i \-1 \-\-pretty="format:%an <%ae>
+whatis = show \-s \-\-pretty='tformat:%h (%s, %ad)' \-\-date=short
+whois = !sh \-c 'git log \-i \-1 \-\-pretty="format:%an <%ae>
.fi
.IP "" 0
.SH "AUTHOR"
diff --git a/man/git-alias.html b/man/git-alias.html
index 2089ade..7a477e1 100644
--- a/man/git-alias.html
+++ b/man/git-alias.html
@@ -77,15 +77,15 @@
]
SYNOPSIS
-git-alias
-git-alias <search-pattern>
-git-alias <alias-name> <command>
-git-alias [--global]
-git-alias [--local]
-git-alias [--global] <search-pattern>
-git-alias [--local] <search-pattern>
-git-alias [--global] <alias-name> <command>
-git-alias [--local] <alias-name> <command>
+git-alias
+git-alias <search-pattern>
+git-alias <alias-name> <command>
+git-alias [--global]
+git-alias [--local]
+git-alias [--global] <search-pattern>
+git-alias [--local] <search-pattern>
+git-alias [--global] <alias-name> <command>
+git-alias [--local] <alias-name> <command>
DESCRIPTION
@@ -150,7 +150,7 @@ whois = !sh -c 'git log -i -1 --pretty="format:%an <%ae>
diff --git a/man/git-alias.md b/man/git-alias.md
index 81d7873..f32e893 100644
--- a/man/git-alias.md
+++ b/man/git-alias.md
@@ -3,15 +3,15 @@ git-alias(1) -- Define, search and show aliases
## SYNOPSIS
-`git-alias`
-`git-alias` <search-pattern>
-`git-alias` <alias-name> <command>
-`git-alias` [--global]
-`git-alias` [--local]
-`git-alias` [--global] <search-pattern>
-`git-alias` [--local] <search-pattern>
-`git-alias` [--global] <alias-name> <command>
-`git-alias` [--local] <alias-name> <command>
+`git-alias`
+`git-alias` <search-pattern>
+`git-alias` <alias-name> <command>
+`git-alias` [--global]
+`git-alias` [--local]
+`git-alias` [--global] <search-pattern>
+`git-alias` [--local] <search-pattern>
+`git-alias` [--global] <alias-name> <command>
+`git-alias` [--local] <alias-name> <command>
## DESCRIPTION
diff --git a/man/git-archive-file.1 b/man/git-archive-file.1
index 4610b48..bb28065 100644
--- a/man/git-archive-file.1
+++ b/man/git-archive-file.1
@@ -1,6 +1,6 @@
-.\" generated with Ronn-NG/v0.9.1
-.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
-.TH "GIT\-ARCHIVE\-FILE" "1" "May 2020" "" "Git Extras"
+.\" generated with Ronn-NG/v0.10.1
+.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
+.TH "GIT\-ARCHIVE\-FILE" "1" "March 2026" "" "Git Extras"
.SH "NAME"
\fBgit\-archive\-file\fR \- Export the current HEAD of the git repository to an archive
.SH "SYNOPSIS"
@@ -14,11 +14,11 @@ Archive naming conventions:
.P
On any branch: \fBgit\-extras\.1\.7\.0\-110\-gafefba7\.branch\-name\.zip\fR
.P
-On master branch: \fBgit\-extras\.1\.7\.0\-110\-gafefba7\.zip\fR
+On the default branch: \fBgit\-extras\.1\.7\.0\-110\-gafefba7\.zip\fR
.P
On a detached HEAD (e\.g\. a tag): \fBgit\-extras\.1\.7\.0\.zip\fR
.P
-The \'/\' and \'\e\' in the branch name will be converted into \'\-\'\.
+The '/' and '\e' in the branch name will be converted into '\-'\.
.SH "AUTHOR"
Written by Philipp Klose <\fIme@thehippo\.de\fR>
.SH "REPORTING BUGS"
diff --git a/man/git-archive-file.html b/man/git-archive-file.html
index 1a53ed8..27c9761 100644
--- a/man/git-archive-file.html
+++ b/man/git-archive-file.html
@@ -1,8 +1,8 @@
-
-
+
+
git-archive-file(1) - Export the current HEAD of the git repository to an archive
+
+
+
+
+
+
+
+
+ - git-continue(1)
+ - Git Extras
+ - git-continue(1)
+
+
+
+
+
NAME
+
+ git-continue - Continue current git operation
+
+
SYNOPSIS
+
+
git-continue
+
+
DESCRIPTION
+
+
Continue current git revert, rebase, merge or cherry-pick process.
+
+
OPTIONS
+
+
There are no options, it just continues current operation.
+
+
EXAMPLES
+
+
git-continue
+
+
AUTHOR
+
+
Written by oikarinen
+
+
REPORTING BUGS
+
+
<https://github.com/tj/git-extras/issues>
+
+
SEE ALSO
+
+
<https://github.com/tj/git-extras>
+
+
+
+
+
+
diff --git a/man/git-continue.md b/man/git-continue.md
new file mode 100644
index 0000000..439a95c
--- /dev/null
+++ b/man/git-continue.md
@@ -0,0 +1,30 @@
+git-continue(1) -- Continue current git operation
+================================
+
+## SYNOPSIS
+
+`git-continue`
+
+## DESCRIPTION
+
+ Continue current git revert, rebase, merge or cherry-pick process.
+
+## OPTIONS
+
+ There are no options, it just continues current operation.
+
+## EXAMPLES
+
+ `git-continue`
+
+## AUTHOR
+
+Written by oikarinen
+
+## REPORTING BUGS
+
+<>
+
+## SEE ALSO
+
+<>
diff --git a/man/git-delete-merged-branches.1 b/man/git-delete-merged-branches.1
index 590879f..4d03841 100644
--- a/man/git-delete-merged-branches.1
+++ b/man/git-delete-merged-branches.1
@@ -1,30 +1,19 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "GIT\-DELETE\-MERGED\-BRANCHES" "1" "October 2017" "" "Git Extras"
-.
+.\" generated with Ronn-NG/v0.10.1
+.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
+.TH "GIT\-DELETE\-MERGED\-BRANCHES" "1" "March 2026" "" "Git Extras"
.SH "NAME"
\fBgit\-delete\-merged\-branches\fR \- Delete merged branches
-.
.SH "SYNOPSIS"
\fBgit\-delete\-merged\-branches\fR
-.
.SH "DESCRIPTION"
-Deletes all branches merged in to current HEAD\. Does not delete \fImaster\fR, even if run from a branch that is a descendant of \fImaster\fR\.
-.
+Deletes all branches merged in to current HEAD\. Does not delete the default branch, even if run from a branch that is a descendant of it\.
.SH "EXAMPLES"
-.
.nf
-
$ git delete\-merged\-branches
-.
.fi
-.
.SH "AUTHOR"
Written by Jesús Espino <\fIjespinog@gmail\.com\fR>
-.
.SH "REPORTING BUGS"
<\fIhttps://github\.com/tj/git\-extras/issues\fR>
-.
.SH "SEE ALSO"
<\fIhttps://github\.com/tj/git\-extras\fR>
diff --git a/man/git-delete-merged-branches.html b/man/git-delete-merged-branches.html
index c1a2a8f..0330f72 100644
--- a/man/git-delete-merged-branches.html
+++ b/man/git-delete-merged-branches.html
@@ -1,8 +1,8 @@
-
-
+
+
git-delete-merged-branches(1) - Delete merged branches
-
-
-
-
-
-
-
-
- - git-magic(1)
- - Git Extras
- - git-magic(1)
-
-
-
NAME
-
- git-magic - Automate add/commit/push routines
-
-
-
SYNOPSIS
-
-
git-magic [-a] [-m msg] [-e] [-p] [-f]
-
-
DESCRIPTION
-
-
Produces summary of changes for commit message from git status --porcelain output.
-Commits staged changes with the generated commit message and
-opens editor to modify generated commit message optionally.
-Also staging and pushing can be automated optionally.
-
-
OPTIONS
-
-
-a
-
-
Adds everything including untracked files.
-
-
-m msg
-
-
Use the given msg as the commit message. If multiple -m options are given, their values are concatenated as separate paragraphs.
-Passed to git commit command. The generated is appended to user-given messages.
-
-
-e
-
-
This option lets you further edit the generated message.
-Passed to git commit command.
-
-
-p
-
-
Runs git push after commit.
-
-
-f
-
-
Adds -f option to git push command.
-
-
-h
-
-
Prints synopsis.
-
-
EXAMPLES
-
-
This example stages all changes then commits with automatic commit message.
-
-
$ git magic -a
-[feature/magic dc2a11e] A man/git-magic.md
- 1 file changed, 37 insertions(+)
- create mode 100644 man/git-auto.md
-# git log
-Author: overengineer <54alpersaid@gmail.com>
-Date: Thu Sep 30 20:14:22 2021 +0300
-
- M man/git-magic.md
-
-
-
-m option PREPENDS generated message.
-
-
$ git magic -am "Added documentation for git magic"
-[feature/magic dc2a11e] Added documentation for git magic
- 1 file changed, 42 insertions(+), 0 deletions(-)
- create mode 100644 A man/git-auto.md
-$ git log
-Author: overengineer <54alpersaid@gmail.com>
-Date: Thu Sep 30 20:14:22 2021 +0300
-
- Added documentation for git magic
-
- M man/git-magic.md
-
-
-
AUTHOR
-
-
Written by Alper S. Soylu <54alpersaid@gmail.com>
-
-
REPORTING BUGS
-
-
<https://github.com/tj/git-extras/issues>
-
-
SEE ALSO
-
-
<https://github.com/tj/git-extras>
-
-
-
-
-
-
-
+
+
+
+
+
+ git-magic(1) - Automate add/commit/push routines
+
+
+
+
+
+
+
+
+
+ - git-magic(1)
+ - Git Extras
+ - git-magic(1)
+
+
+
+
+
NAME
+
+ git-magic - Automate add/commit/push routines
+
+
SYNOPSIS
+
+
git-magic [-a] [-m msg] [-e] [-p] [-f]
+
+
DESCRIPTION
+
+
Produces summary of changes for commit message from git status --porcelain output.
+Commits staged changes with the generated commit message and
+opens editor to modify generated commit message optionally.
+Also staging and pushing can be automated optionally.
+
+
OPTIONS
+
+
-a
+
+
Adds everything including untracked files.
+
+
-m msg
+
+
Use the given msg as the commit message. If multiple -m options are given, their values are concatenated as separate paragraphs.
+Passed to git commit command. The generated is appended to user-given messages.
+
+
-e
+
+
This option lets you further edit the generated message.
+Passed to git commit command.
+
+
-p
+
+
Runs git push after commit.
+
+
-f
+
+
Adds --force-with-lease option to git push command for safer force pushing.
+
+
-h
+
+
Prints synopsis.
+
+
EXAMPLES
+
+
This example stages all changes then commits with automatic commit message.
+
+
$ git magic -a
+[feature/magic dc2a11e] A man/git-magic.md
+ 1 file changed, 37 insertions(+)
+ create mode 100644 man/git-auto.md
+# git log
+Author: overengineer <54alpersaid@gmail.com>
+Date: Thu Sep 30 20:14:22 2021 +0300
+
+ M man/git-magic.md
+
+
+
-m option PREPENDS generated message.
+
+
$ git magic -am "Added documentation for git magic"
+[feature/magic dc2a11e] Added documentation for git magic
+ 1 file changed, 42 insertions(+), 0 deletions(-)
+ create mode 100644 A man/git-auto.md
+$ git log
+Author: overengineer <54alpersaid@gmail.com>
+Date: Thu Sep 30 20:14:22 2021 +0300
+
+ Added documentation for git magic
+
+ M man/git-magic.md
+
+
+
AUTHOR
+
+
Written by Alper S. Soylu 54alpersaid@gmail.com
+
+
REPORTING BUGS
+
+
<https://github.com/tj/git-extras/issues>
+
+
SEE ALSO
+
+
<https://github.com/tj/git-extras>
+
+
+
+
+
+
diff --git a/man/git-magic.md b/man/git-magic.md
index 8da5878..d640d66 100644
--- a/man/git-magic.md
+++ b/man/git-magic.md
@@ -34,7 +34,7 @@ Runs `git push` after commit.
-f
-Adds `-f` option to `git push` command.
+Adds `--force-with-lease` option to `git push` command for safer force pushing.
-h
diff --git a/man/git-missing.1 b/man/git-missing.1
index 2e82e7a..413880d 100644
--- a/man/git-missing.1
+++ b/man/git-missing.1
@@ -1,16 +1,16 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "GIT\-MISSING" "1" "April 2018" "" "Git Extras"
+.TH "GIT\-MISSING" "1" "August 2024" "" "Git Extras"
.
.SH "NAME"
\fBgit\-missing\fR \- Show commits missing from another branch
.
.SH "SYNOPSIS"
-\fBgit\-missing\fR [] []
+\fBgit\-missing\fR [] [] [[\-\-] \.\.\.]
.
.SH "DESCRIPTION"
-Shows commits that are in either of two branches but not both\. Useful for seeing what would come across in a merge or push\.
+Shows commits that are in either of two branches but not both\. Useful for seeing what would come across in a merge or push\. Optionally, the comparison can be limited to specific paths\.
.
.SH "OPTIONS"
[]
@@ -30,6 +30,12 @@ Second branch to compare\.
.P
Any flags that should be passed to \'git log\', such as \-\-no\-merges\.
.
+.P
+[[\-\-] \.\.\.]
+.
+.P
+Optional path specifications (pathspec) to limit the comparison to specific files or directories\. For more details about the pathspec syntax, see the pathspec entry in gitglossary[7] \fIhttps://git\-scm\.com/docs/gitglossary#Documentation/gitglossary\.txt\-aiddefpathspecapathspec\fR\.
+.
.SH "EXAMPLES"
Show commits on either my current branch or master but not both:
.
@@ -60,11 +66,26 @@ $ git missing foo bar
.
.IP "" 0
.
+.P
+Show commits on either my current branch or master but not both, limited to the src/ directory:
+.
+.IP "" 4
+.
+.nf
+
+$ git missing master \-\- src/
+< ed52989 only on current checked out branch, in src/ directory
+> 7988c4b only on master, in src/ directory
+.
+.fi
+.
+.IP "" 0
+.
.SH "AUTHOR"
Written by Nate Jones <\fInate@endot\.org\fR>
.
.SH "REPORTING BUGS"
-<\fIhttp://github\.com/tj/git\-extras/issues\fR>
+<\fIhttps://github\.com/tj/git\-extras/issues\fR>
.
.SH "SEE ALSO"
-<\fIhttp://github\.com/tj/git\-extras\fR>
+<\fIhttps://github\.com/tj/git\-extras\fR>
diff --git a/man/git-missing.html b/man/git-missing.html
index 427d924..d82a07c 100644
--- a/man/git-missing.html
+++ b/man/git-missing.html
@@ -76,12 +76,13 @@
SYNOPSIS
-git-missing [<first branch>] <second branch> [<git log options>]
+git-missing [<first branch>] <second branch> [<git log options>] [[--] <path>...]
DESCRIPTION
- Shows commits that are in either of two branches but not both. Useful for
- seeing what would come across in a merge or push.
+ Shows commits that are in either of two branches but not both. Useful for
+ seeing what would come across in a merge or push. Optionally, the comparison
+ can be limited to specific paths.
OPTIONS
@@ -97,6 +98,12 @@
Any flags that should be passed to 'git log', such as --no-merges.
+ [[--] <path>...]
+
+ Optional path specifications (pathspec) to limit the comparison to specific
+ files or directories. For more details about the pathspec syntax, see the
+ pathspec entry in gitglossary[7].
+
EXAMPLES
Show commits on either my current branch or master but not both:
@@ -113,22 +120,30 @@
> f38797e only on bar
+ Show commits on either my current branch or master but not both, limited to the
+ src/ directory:
+
+$ git missing master -- src/
+< ed52989 only on current checked out branch, in src/ directory
+> 7988c4b only on master, in src/ directory
+
+
AUTHOR
-Written by Nate Jones <nate@endot.org>
+Written by Nate Jones <nate@endot.org>
REPORTING BUGS
-<http://github.com/tj/git-extras/issues>
+<https://github.com/tj/git-extras/issues>
SEE ALSO
-<http://github.com/tj/git-extras>
+<https://github.com/tj/git-extras>
diff --git a/man/git-missing.md b/man/git-missing.md
index 6f4ff65..8b49430 100644
--- a/man/git-missing.md
+++ b/man/git-missing.md
@@ -3,12 +3,13 @@ git-missing(1) -- Show commits missing from another branch
## SYNOPSIS
-`git-missing` [<first branch>] <second branch> [<git log options>]
+`git-missing` [<first branch>] <second branch> [<git log options>] [[--] <path>...]
## DESCRIPTION
- Shows commits that are in either of two branches but not both. Useful for
- seeing what would come across in a merge or push.
+ Shows commits that are in either of two branches but not both. Useful for
+ seeing what would come across in a merge or push. Optionally, the comparison
+ can be limited to specific paths.
## OPTIONS
@@ -24,6 +25,12 @@ git-missing(1) -- Show commits missing from another branch
Any flags that should be passed to 'git log', such as --no-merges.
+ [[--] <path>...]
+
+ Optional path specifications (pathspec) to limit the comparison to specific
+ files or directories. For more details about the pathspec syntax, see the
+ pathspec entry in [gitglossary[7]](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec).
+
## EXAMPLES
Show commits on either my current branch or master but not both:
@@ -38,6 +45,13 @@ git-missing(1) -- Show commits missing from another branch
< b8f0d14 only on foo
> f38797e only on bar
+ Show commits on either my current branch or master but not both, limited to the
+ src/ directory:
+
+ $ git missing master -- src/
+ < ed52989 only on current checked out branch, in src/ directory
+ > 7988c4b only on master, in src/ directory
+
## AUTHOR
Written by Nate Jones <>
diff --git a/man/git-pull-request.1 b/man/git-pull-request.1
index b44859c..2311773 100644
--- a/man/git-pull-request.1
+++ b/man/git-pull-request.1
@@ -1,58 +1,41 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "GIT\-PULL\-REQUEST" "1" "August 2020" "" "Git Extras"
-.
+.\" generated with Ronn-NG/v0.10.1
+.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
+.TH "GIT\-PULL\-REQUEST" "1" "March 2026" "" "Git Extras"
.SH "NAME"
\fBgit\-pull\-request\fR \- Create pull request for GitHub project
-.
.SH "SYNOPSIS"
-\fBgit\-pull\-request\fR []
-.
+\fBgit\-pull\-request\fR []
.SH "DESCRIPTION"
Create pull request for a project on GitHub via command line\.
-.
.P
A personal access token is required for making the API call to open the pull request(s) in GitHub\. API Documentation here \fIhttps://docs\.github\.com/en/rest/reference/pulls#create\-a\-pull\-request\fR
-.
.P
Make sure the personal access token has the right \fBOAuth\fR scopes for the repo(s)
-.
.P
-Use \fBgit config \-\-global \-\-add git\-extras\.github\-personal\-access\-token \fR
-.
+Use \fBGITHUB_TOKEN\fR environment variable, or \fBgit config \-\-global \-\-add git\-extras\.github\-personal\-access\-token \fR
.P
If using multiple accounts, override the global value in the specific repo using \fBgit config git\-extras\.github\-personal\-access\-token \fR
-.
.SH "OPTIONS"
-
-.
+
.P
-The target branch you want to send pull request to\.
-.
+The local branch to push and use as the pull request head\. The base branch is prompted separately\.
.SH "EXAMPLES"
-.
.nf
-
$ git pull\-request master
Everything up\-to\-date
- create pull\-request for spacewander/spacewander\-toolbox \'master\'
+ create pull\-request for spacewander/spacewander\-toolbox 'master'
title: test
body:
base [master]:
GitHub two\-factor authentication code (leave blank if not set up):
-\.\.\.
-.
+\|\.\|\.\|\.
.fi
-.
.SH "AUTHOR"
Written by Tj Holowaychuk <\fItj@vision\-media\.ca\fR>
-.
.SH "REPORTING BUGS"
<\fIhttps://github\.com/tj/git\-extras/issues\fR>
-.
.SH "SEE ALSO"
<\fIhttps://github\.com/tj/git\-extras\fR>
diff --git a/man/git-pull-request.html b/man/git-pull-request.html
index fa5796e..05169ac 100644
--- a/man/git-pull-request.html
+++ b/man/git-pull-request.html
@@ -1,8 +1,8 @@
-
-
+
+
git-pull-request(1) - Create pull request for GitHub project
+
+
+
+
+
+
+
+
+ - git-unwip(1)
+ - Git Extras
+ - git-unwip(1)
+
+
+
+
+
NAME
+
+ git-unwip - Undo a Work In Progress commit
+
+
SYNOPSIS
+
+
git-unwip
+
+
DESCRIPTION
+
+
Undo a Work In Progress commit.
+
+
OPTIONS
+
+
None
+
+
EXAMPLES
+
+
Create a WIP commit which stores all changes in the working directory.
+
+
$ git wip
+
+
+
Later on, undo the commit and continue making changes.
+
+
$ git unwip
+
+
+
AUTHOR
+
+
Written by Andrew Sullivan Cant <mail@andrewsullivancant.ca>
+
+
REPORTING BUGS
+
+
<https://github.com/tj/git-extras/issues>
+
+
SEE ALSO
+
+
<https://github.com/tj/git-extras>
+
+
+
+
+
+
diff --git a/man/git-unwip.md b/man/git-unwip.md
new file mode 100644
index 0000000..6d4d961
--- /dev/null
+++ b/man/git-unwip.md
@@ -0,0 +1,36 @@
+git-unwip(1) -- Undo a Work In Progress commit
+================================
+
+## SYNOPSIS
+
+`git-unwip`
+
+## DESCRIPTION
+
+ Undo a Work In Progress commit.
+
+## OPTIONS
+
+ None
+
+## EXAMPLES
+
+ Create a WIP commit which stores all changes in the working directory.
+
+ $ git wip
+
+ Later on, undo the commit and continue making changes.
+
+ $ git unwip
+
+## AUTHOR
+
+Written by Andrew Sullivan Cant <>
+
+## REPORTING BUGS
+
+<>
+
+## SEE ALSO
+
+<>
diff --git a/man/git-wip.1 b/man/git-wip.1
new file mode 100644
index 0000000..3d1e326
--- /dev/null
+++ b/man/git-wip.1
@@ -0,0 +1,31 @@
+.\" generated with Ronn-NG/v0.10.1
+.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
+.TH "GIT\-WIP" "1" "May 2025" "" "Git Extras"
+.SH "NAME"
+\fBgit\-wip\fR \- Create a Work In Progress commit
+.SH "SYNOPSIS"
+\fBgit\-wip\fR
+.SH "DESCRIPTION"
+Create a Work In Progress commit, include all files in the working directory\.
+.SH "OPTIONS"
+None
+.SH "EXAMPLES"
+Create a WIP commit which stores all changes in the working directory\.
+.IP "" 4
+.nf
+$ git wip
+.fi
+.IP "" 0
+.P
+Later on, undo the commit and continue making changes\.
+.IP "" 4
+.nf
+$ git unwip
+.fi
+.IP "" 0
+.SH "AUTHOR"
+Written by Andrew Sullivan Cant <\fImail@andrewsullivancant\.ca\fR>
+.SH "REPORTING BUGS"
+<\fIhttps://github\.com/tj/git\-extras/issues\fR>
+.SH "SEE ALSO"
+<\fIhttps://github\.com/tj/git\-extras\fR>
diff --git a/man/git-wip.html b/man/git-wip.html
new file mode 100644
index 0000000..b640255
--- /dev/null
+++ b/man/git-wip.html
@@ -0,0 +1,122 @@
+
+
+
+
+
+ git-wip(1) - Create a Work In Progress commit
+
+
+
+
+
+
+
+
+
+ - git-wip(1)
+ - Git Extras
+ - git-wip(1)
+
+
+
+
+
NAME
+
+ git-wip - Create a Work In Progress commit
+
+
SYNOPSIS
+
+
git-wip
+
+
DESCRIPTION
+
+
Create a Work In Progress commit, include all files in the working directory.
+
+
OPTIONS
+
+
None
+
+
EXAMPLES
+
+
Create a WIP commit which stores all changes in the working directory.
+
+
$ git wip
+
+
+
Later on, undo the commit and continue making changes.
+
+
$ git unwip
+
+
+
AUTHOR
+
+
Written by Andrew Sullivan Cant <mail@andrewsullivancant.ca>
+
+
REPORTING BUGS
+
+
<https://github.com/tj/git-extras/issues>
+
+
SEE ALSO
+
+
<https://github.com/tj/git-extras>
+
+
+
+
+
+
diff --git a/man/git-wip.md b/man/git-wip.md
new file mode 100644
index 0000000..58bc19b
--- /dev/null
+++ b/man/git-wip.md
@@ -0,0 +1,36 @@
+git-wip(1) -- Create a Work In Progress commit
+================================
+
+## SYNOPSIS
+
+`git-wip`
+
+## DESCRIPTION
+
+ Create a Work In Progress commit, include all files in the working directory.
+
+## OPTIONS
+
+ None
+
+## EXAMPLES
+
+ Create a WIP commit which stores all changes in the working directory.
+
+ $ git wip
+
+ Later on, undo the commit and continue making changes.
+
+ $ git unwip
+
+## AUTHOR
+
+Written by Andrew Sullivan Cant <>
+
+## REPORTING BUGS
+
+<>
+
+## SEE ALSO
+
+<>
diff --git a/man/index.txt b/man/index.txt
index 21c6503..4f24ad0 100644
--- a/man/index.txt
+++ b/man/index.txt
@@ -12,6 +12,7 @@ git-clear-soft(1) git-clear-soft
git-clear(1) git-clear
git-coauthor(1) git-coauthor
git-commits-since(1) git-commits-since
+git-continue(1) git-continue
git-contrib(1) git-contrib
git-count(1) git-count
git-cp(1) git-cp
@@ -72,4 +73,6 @@ git-sync(1) git-sync
git-touch(1) git-touch
git-undo(1) git-undo
git-unlock(1) git-unlock
+git-unwip(1) git-unwip
git-utimes(1) git-utimes
+git-wip(1) git-wip
diff --git a/scripts/checkstyle.py b/scripts/checkstyle.py
new file mode 100755
index 0000000..6e2d3dd
--- /dev/null
+++ b/scripts/checkstyle.py
@@ -0,0 +1,262 @@
+#!/usr/bin/env python3
+import re
+import os
+import argparse
+from pathlib import Path
+from typing import Callable, List, Dict, Any # compat
+
+# This file checks Bash and Shell scripts for violations not found with
+# shellcheck or existing methods. You can use it in several ways:
+#
+# Lint all .bash, .sh, .bats files along with 'bin/asdf' and print out violations:
+# $ ./scripts/checkstyle.py
+#
+# The former, but also fix all violations. This must be ran until there
+# are zero violations since any line can have more than one violation:
+# $ ./scripts/checkstyle.py --fix
+#
+# Lint a particular file:
+# $ ./scripts/checkstyle.py ./lib/functions/installs.bash
+#
+# Check to ensure all regular expressions are working as intended:
+# $ ./scripts/checkstyle.py --internal-test-regex
+
+Rule = Dict[str, Any]
+
+class c:
+ RED = '\033[91m'
+ GREEN = '\033[92m'
+ YELLOW = '\033[93m'
+ BLUE = '\033[94m'
+ MAGENTA = '\033[95m'
+ CYAN = '\033[96m'
+ RESET = '\033[0m'
+ BOLD = '\033[1m'
+ UNDERLINE = '\033[4m'
+ LINK: Callable[[str, str], str] = lambda href, text: f'\033]8;;{href}\a{text}\033]8;;\a'
+
+def utilGetStrs(line: Any, m: Any):
+ return (
+ line[0:m.start('match')],
+ line[m.start('match'):m.end('match')],
+ line[m.end('match'):]
+ )
+
+# Before: printf '%s\\n' '^w^'
+# After: printf '%s\n' '^w^'
+def noDoubleBackslashFixer(line: str, m: Any) -> str:
+ prestr, midstr, poststr = utilGetStrs(line, m)
+
+ return f'{prestr}{midstr[1:]}{poststr}'
+
+# Before: $(pwd)
+# After: $PWD
+def noPwdCaptureFixer(line: str, m: Any) -> str:
+ prestr, _, poststr = utilGetStrs(line, m)
+
+ return f'{prestr}$PWD{poststr}'
+
+# Before: [ a == b ]
+# After: [ a = b ]
+def noTestDoubleEqualsFixer(line: str, m: Any) -> str:
+ prestr, _, poststr = utilGetStrs(line, m)
+
+ return f'{prestr}={poststr}'
+
+# Before: function fn() { ...
+# After: fn() { ...
+# ---
+# Before: function fn { ...
+# After fn() { ...
+def noFunctionKeywordFixer(line: str, m: Any) -> str:
+ prestr, midstr, poststr = utilGetStrs(line, m)
+
+ midstr = midstr.strip()
+ midstr = midstr[len('function'):]
+ midstr = midstr.strip()
+
+ parenIdx = midstr.find('(')
+ if parenIdx != -1:
+ midstr = midstr[:parenIdx]
+
+ return f'{prestr}{midstr}() {poststr}'
+
+# Before: >/dev/null 2>&1
+# After: &>/dev/null
+# ---
+# Before: 2>/dev/null 1>&2
+# After: &>/dev/null
+def noVerboseRedirectionFixer(line: str, m: Any) -> str:
+ prestr, _, poststr = utilGetStrs(line, m)
+
+ return f'{prestr}&>/dev/null{poststr}'
+
+def lintfile(file: Path, rules: List[Rule], options: Dict[str, Any]):
+ content_arr = file.read_text().split('\n')
+
+ for line_i, line in enumerate(content_arr):
+ if 'checkstyle-ignore' in line:
+ continue
+
+ for rule in rules:
+ should_run = False
+ if 'sh' in rule['fileTypes']:
+ if file.name.endswith('.sh'):
+ should_run = True
+ if 'bash' in rule['fileTypes']:
+ if file.name.endswith('.bash') or file.name.endswith('.bats') or file.name.startswith('git-'):
+ should_run = True
+
+ if options['verbose']:
+ print(f'{str(file)}: {should_run}')
+
+ if not should_run:
+ continue
+
+ m = re.search(rule['regex'], line)
+ if m is not None and m.group('match') is not None:
+ dir = os.path.relpath(file.resolve(), Path.cwd())
+ prestr = line[0:m.start('match')]
+ midstr = line[m.start('match'):m.end('match')]
+ poststr = line[m.end('match'):]
+
+ print(f'{c.CYAN}{dir}{c.RESET}:{line_i + 1}')
+ print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: {rule["reason"]}')
+ print(f'{prestr}{c.RED}{midstr}{c.RESET}{poststr}')
+ print()
+
+ if options['fix']:
+ content_arr[line_i] = rule['fixerFn'](line, m)
+
+ rule['found'] += 1
+
+ if options['fix']:
+ file.write_text('\n'.join(content_arr))
+
+def main():
+ rules: List[Rule] = [
+ {
+ 'name': 'no-pwd-capture',
+ 'regex': '(?P\\$\\(pwd\\))',
+ 'reason': '$PWD is essentially equivalent to $(pwd) without the overhead of a subshell',
+ 'fileTypes': ['bash', 'sh'],
+ 'fixerFn': noPwdCaptureFixer,
+ 'testPositiveMatches': [
+ '$(pwd)'
+ ],
+ 'testNegativeMatches': [
+ '$PWD'
+ ],
+ },
+ {
+ 'name': 'no-test-double-equals',
+ 'regex': '(?==).*?]',
+ 'reason': 'Disallow double equals in places where they are not necessary for consistency',
+ 'fileTypes': ['bash', 'sh'],
+ 'fixerFn': noTestDoubleEqualsFixer,
+ 'testPositiveMatches': [
+ '[ a == b ]',
+ '[ "${lines[0]}" == blah ]',
+ ],
+ 'testNegativeMatches': [
+ '[ a = b ]',
+ '[[ a = b ]]',
+ '[[ a == b ]]',
+ '[ a = b ] || [[ a == b ]]',
+ '[[ a = b ]] || [[ a == b ]]',
+ '[[ "${lines[0]}" == \'usage: \'* ]]',
+ '[ "${lines[0]}" = blah ]',
+ ],
+ },
+ {
+ 'name': 'no-function-keyword',
+ 'regex': '^[ \\t]*(?Pfunction .*?(?:\\([ \\t]*\\))?[ \\t]*){',
+ 'reason': 'Only allow functions declared like `fn_name() {{ :; }}` for consistency (see ' + c.LINK('https://www.shellcheck.net/wiki/SC2113', 'ShellCheck SC2113') + ')',
+ 'fileTypes': ['bash', 'sh'],
+ 'fixerFn': noFunctionKeywordFixer,
+ 'testPositiveMatches': [
+ 'function fn() { :; }',
+ 'function fn { :; }',
+ ],
+ 'testNegativeMatches': [
+ 'fn() { :; }',
+ ],
+ },
+ {
+ 'name': 'no-verbose-redirection',
+ 'regex': '(?P(>/dev/null 2>&1|2>/dev/null 1>&2))',
+ 'reason': 'Use `&>/dev/null` instead of `>/dev/null 2>&1` or `2>/dev/null 1>&2` for consistency',
+ 'fileTypes': ['bash'],
+ 'fixerFn': noVerboseRedirectionFixer,
+ 'testPositiveMatches': [
+ 'echo woof >/dev/null 2>&1',
+ 'echo woof 2>/dev/null 1>&2',
+ ],
+ 'testNegativeMatches': [
+ 'echo woof &>/dev/null',
+ 'echo woof >&/dev/null',
+ ],
+ },
+ ]
+ [rule.update({ 'found': 0 }) for rule in rules]
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('files', metavar='FILES', nargs='*')
+ parser.add_argument('--fix', action='store_true')
+ parser.add_argument('--verbose', action='store_true')
+ parser.add_argument('--internal-test-regex', action='store_true')
+ args = parser.parse_args()
+
+ if args.internal_test_regex:
+ for rule in rules:
+ for positiveMatch in rule['testPositiveMatches']:
+ m: Any = re.search(rule['regex'], positiveMatch)
+ if m is None or m.group('match') is None:
+ print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: Failed {c.CYAN}positive{c.RESET} test:')
+ print(f'=> {positiveMatch}')
+ print()
+
+ for negativeMatch in rule['testNegativeMatches']:
+ m: Any = re.search(rule['regex'], negativeMatch)
+ if m is not None and m.group('match') is not None:
+ print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: Failed {c.YELLOW}negative{c.RESET} test:')
+ print(f'=> {negativeMatch}')
+ print()
+ print('Done.')
+ return
+
+ options = {
+ 'fix': args.fix,
+ 'verbose': args.verbose,
+ }
+
+ # parse files and print matched lints
+ if len(args.files) > 0:
+ for file in args.files:
+ p = Path(file)
+ if p.is_file():
+ lintfile(p, rules, options)
+ else:
+ for file in Path.cwd().glob('**/*'):
+ if '.git' in str(file.absolute()):
+ continue
+
+ if file.is_file():
+ lintfile(file, rules, options)
+
+ # print final results
+ print(f'{c.UNDERLINE}TOTAL ISSUES{c.RESET}')
+ for rule in rules:
+ print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: {rule["found"]}')
+
+ grand_total = sum([rule['found'] for rule in rules])
+ print(f'GRAND TOTAL: {grand_total}')
+ print(f'{c.BOLD}{c.YELLOW}NOTE:{c.RESET} Run "./scripts/checkstyle.py --fix" to automatically fix all issues (may need to run multiple times)')
+
+ # exit
+ if grand_total == 0:
+ exit(0)
+ else:
+ exit(2)
+
+main()
diff --git a/tests/README.md b/tests/README.md
index d97c077..836021d 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -1,7 +1,28 @@
-# Test for git-extras
-The git-extras has its own testcases now, and the more is on the way! So let's introduce it.
+# Testing
-We choose python to help us to reach to other shore cause **python is life saver**.
+Originally, the tests were written in pytest. However, tests are in the process of being converted to Bats so coverage can be calculated.
+
+## Bats Testing
+
+We require a somewhat recent version of Bats. Version v1.8.1 is tested in CI. Once it is installed, the tests can be executed like so:
+
+```sh
+bats ./tests
+```
+
+We highly recommend adding tests for new features and fixes.
+
+### Code Coverage
+
+Coverage can be calculated with [bashcov](https://github.com/infertux/bashcov) like so:
+
+```sh
+bashcov -- bats ./tests
+```
+
+By default, the report will be generated in `./coverage/index.html`.
+
+## Python Testing
The test part depends on:
@@ -12,7 +33,8 @@ The test part depends on:
So the versions are higher than above is recommended.
-# How to run the tests
+### How to run the tests
+
1. Install `poetry`
2. Install the dependencies via `poetry install`
3. Run `poetry run pytest`
@@ -22,11 +44,13 @@ It is done or go without `poetry`,
1. Install python >= 3.11
2. Install pytest >= 8.1.2
3. Install gitpython >= 3.1.43
-4. Run `pytest`
+4. Install testpath >= 0.6.0
+5. Run `pytest`
The second way maybe blocked the some missing dependencies at someday, so the first one is recommended.
-# What and how to create a unit test
+### What and how to create a unit test
+
One command has a unit test, because one `git-*` command is just do one thing, so we can eat a piece of `git-*` command in one time.
For example,
@@ -38,7 +62,8 @@ For example,
* `named_temp_repo` is just same as `temp_repo` except the custom directory renaming.
4. Loop the third step until the 100% coverage of the function of the `git-alias`
-# References
+### References
+
* [poetry](https://github.com/python-poetry/poetry)
* [pytest](https://github.com/pytest-dev/pytest/)
* [git python](https://github.com/gitpython-developers/GitPython)
diff --git a/tests/bin/open b/tests/bin/open
new file mode 100755
index 0000000..2be3eaf
--- /dev/null
+++ b/tests/bin/open
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+printf '%s\n' "open $*"
diff --git a/tests/bin/powershell.exe b/tests/bin/powershell.exe
new file mode 100755
index 0000000..aee36a1
--- /dev/null
+++ b/tests/bin/powershell.exe
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+printf '%s\n' "powershell.exe $*"
diff --git a/tests/bin/start b/tests/bin/start
new file mode 100755
index 0000000..89eeebd
--- /dev/null
+++ b/tests/bin/start
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+printf '%s\n' "start $*"
diff --git a/tests/bin/xdg-open b/tests/bin/xdg-open
new file mode 100755
index 0000000..951b999
--- /dev/null
+++ b/tests/bin/xdg-open
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+printf '%s\n' "xdg-open $*"
diff --git a/tests/conftest.py b/tests/conftest.py
index 74c91c8..3b31908 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,13 +4,15 @@
import pytest
from helper import TempRepository
-def create_repo(dirname = None):
+
+def create_repo(dirname=None):
repo = TempRepository(dirname)
- tmp_file_a = repo.create_tmp_file()
- tmp_file_b = repo.create_tmp_file()
+ repo.create_tmp_file() # tmp_file_a
+ repo.create_tmp_file() # tmp_file_b
repo.switch_cwd_under_repo()
return repo
+
def init_repo_git_status(repo):
git = repo.get_repo_git()
git.add(".")
@@ -18,12 +20,14 @@ def init_repo_git_status(repo):
git.config("--local", "user.email", "test@git-extras.com")
git.commit("-m", "chore: initial commit")
+
@pytest.fixture(scope="module")
def temp_repo():
repo = create_repo()
init_repo_git_status(repo)
return repo
+
@pytest.fixture(scope="module")
def named_temp_repo(request):
dirname = request.param
@@ -31,3 +35,11 @@ def named_temp_repo(request):
init_repo_git_status(repo)
yield repo
repo.teardown()
+
+
+@pytest.fixture(scope="function")
+def temp_repo_clean():
+ """Create a temporary repository that is reset for each function call."""
+ repo = create_repo()
+ init_repo_git_status(repo)
+ return repo
diff --git a/tests/git-abort.bats b/tests/git-abort.bats
new file mode 100644
index 0000000..73e8302
--- /dev/null
+++ b/tests/git-abort.bats
@@ -0,0 +1,93 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+setup_file() {
+ test_util.setup_file
+}
+
+setup() {
+ test_util.cd_test
+
+ test_util.git_init
+ git commit --allow-empty -m "Initial commit"
+ git branch A
+ git branch B
+ git checkout A
+ printf '%s\n' 'a' > tmpfile
+ git add .
+ git commit -m A
+ git checkout B
+ printf '%s\n' 'b' > tmpfile
+ git add .
+ git commit -m B
+ git status
+}
+
+@test "works with cherry pick" {
+ run git cherry-pick A
+ assert_failure
+
+ run git status
+ assert_line -p 'You are currently cherry-picking commit'
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ run git abort
+ assert_success
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
+
+@test "works with merge" {
+ run git merge A
+ assert_failure
+
+ run git status
+ assert_line -p 'You have unmerged paths'
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ run git abort
+ assert_success
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
+
+@test "works with rebase" {
+ run git rebase A
+ assert_failure
+
+ run git status
+ assert_line -p 'You are currently rebasing branch'
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ run git abort
+ assert_success
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
+
+@test "works with revert" {
+ run git revert A
+ assert_failure
+
+ run git status
+ assert_line -p 'You are currently reverting commit'
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ run git abort
+ assert_success
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
diff --git a/tests/git-alias.bats b/tests/git-alias.bats
new file mode 100644
index 0000000..bb15410
--- /dev/null
+++ b/tests/git-alias.bats
@@ -0,0 +1,106 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+setup_file() {
+ test_util.setup_file
+}
+
+setup() {
+ test_util.cd_test
+
+ test_util.git_init
+ git config --global alias.globalalias status
+ git config --global alias.x status
+ git config --local alias.localalias status
+ git config --local alias.y status
+}
+
+@test "list all works" {
+ run git alias
+ assert_output - <<-'EOF'
+ globalalias = status
+ localalias = status
+ x = status
+ y = status
+EOF
+ assert_success
+}
+
+@test "list all globally works" {
+ run git alias --global
+ assert_output - <<-'EOF'
+ globalalias = status
+ x = status
+EOF
+ assert_success
+}
+
+@test "list all locally works" {
+ run git alias --local
+ assert_output - <<-'EOF'
+ localalias = status
+ y = status
+EOF
+ assert_success
+}
+
+@test "search globally works" {
+ run git alias --global global
+ assert_output - <<-'EOF'
+ globalalias = status
+EOF
+ assert_success
+
+ run git alias --global local
+ assert_output ''
+ assert_failure
+}
+
+@test "search locally works" {
+ run git alias --local local
+ assert_output - <<-'EOF'
+ localalias = status
+EOF
+ assert_success
+
+ run git alias --local global
+ assert_output ''
+ assert_failure
+}
+
+@test "get alias globally and defaultly" {
+ run git alias globalalias
+ assert_output - <<-'EOF'
+ globalalias = status
+EOF
+ assert_success
+}
+
+@test "set alias globally and defaultly" {
+ git alias globalalias diff
+ run git alias diff
+ assert_output - <<-'EOF'
+ globalalias = diff
+EOF
+ assert_success
+}
+
+@test "get alias locally" {
+ run git alias --local localalias
+ assert_output - <<-'EOF'
+ localalias = status
+EOF
+ assert_success
+}
+
+@test "set alias locally" {
+ git alias --local localalias diff
+ run git alias
+ assert_output - <<-'EOF'
+ globalalias = status
+ localalias = diff
+ x = status
+ y = status
+EOF
+}
diff --git a/tests/git-archive-file.bats b/tests/git-archive-file.bats
new file mode 100644
index 0000000..b7f1849
--- /dev/null
+++ b/tests/git-archive-file.bats
@@ -0,0 +1,66 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+setup_file() {
+ test_util.setup_file
+}
+
+setup() {
+ test_util.cd_test
+
+ test_util.git_init
+ printf '%s\n' 'data' > tmpfile
+ git add .
+ git commit -m 'test: add data'
+ git tag 0.1.0 -m 'bump: 0.1.0'
+}
+
+@test "archive file on tags branch" {
+ git checkout -b tags0.1.0
+ run git archive-file
+ assert_success
+
+ local describe_output=
+ describe_output=$(git describe)
+ assert_file_exists "${PWD##*/}.$describe_output.zip"
+}
+
+@test "archive file on any not tags branch without default branch" {
+ git checkout -b not-tags-branch
+ run git archive-file
+ assert_success
+
+ local describe_output=
+ describe_output=$(git describe --always --long)
+ assert_file_exists "${PWD##*/}.$describe_output.not-tags-branch.zip"
+}
+
+@test "archive file on any not tags branch with default branch" {
+ skip "Not working as expected"
+
+ run git archive-file
+ assert_success
+
+ local describe_output=
+ describe_output=$(git describe --always --long)
+ assert_file_exists "${PWD##*/}.$describe_output.zip"
+}
+
+@test "archive file on branch name has slash" {
+ git checkout -b feature/slash
+ run git archive-file
+ assert_success
+
+ local describe_output=
+ describe_output=$(git describe --always --long)
+ assert_file_exists "${PWD##*/}.$describe_output.feature-slash.zip"
+}
+
+@test "archive file on dirname has backslash" {
+ skip
+}
+
+@test "archive file on tag name has slash" {
+ skip
+}
diff --git a/tests/git-authors.bats b/tests/git-authors.bats
new file mode 100644
index 0000000..2fafffa
--- /dev/null
+++ b/tests/git-authors.bats
@@ -0,0 +1,57 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+
+setup_file() {
+ test_util.setup_file
+}
+
+setup() {
+ test_util.cd_test
+
+ test_util.git_init
+
+ git config user.name 'test'
+ git config user.email 'test@example.com'
+ printf '%s\n' 'A' > tmpfile
+ git add .
+ git commit -m 'test: add data A'
+
+ git config user.name 'testagain'
+ git config user.email 'testagain@example.com'
+ printf '%s\n' 'B' > tmpfile
+ git add .
+ git commit -m 'test: add data B'
+
+ # git config unset user.name
+ # git config unset user.email
+}
+
+@test "output authors has email without any parameter" {
+ run git authors
+ assert_success
+
+ local content=$(\ntestagain '
+}
+
+@test "list authors has email defaultly" {
+ run git authors --list
+ assert_output $'test \ntestagain '
+ assert_success
+
+ run git authors -l
+ assert_output $'test \ntestagain '
+ assert_success
+}
+
+@test "list authors has no email" {
+ run git authors --list --no-email
+ assert_output $'test\ntestagain'
+ assert_success
+
+ run git authors -l --no-email
+ assert_output $'test\ntestagain'
+ assert_success
+}
diff --git a/tests/git-browse-ci.bats b/tests/git-browse-ci.bats
new file mode 100644
index 0000000..fee89fc
--- /dev/null
+++ b/tests/git-browse-ci.bats
@@ -0,0 +1,117 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+setup_file() {
+ test_util.setup_file
+
+ PATH="$BATS_TEST_DIRNAME/bin:$PATH"
+}
+
+setup() {
+ test_util.cd_test
+
+ test_util.git_init
+}
+
+get_ci_uri() {
+ local mode=$1
+
+ if [ "$mode" = 'github' ]; then
+ REPLY="https://github.com/tj/git-extras/actions"
+ elif [ "$mode" = 'gitlab' ]; then
+ REPLY="https://gitlab.com/tj/git-extras/-/pipelines"
+ elif [ "$mode" = 'bitbucket' ]; then
+ REPLY="https://bitbucket.org/tj/git-extras/addon/pipelines/home"
+ fi
+}
+
+@test "works with mac and github" {
+ get_ci_uri 'github'
+ local expected_url=$REPLY
+
+ git remote add upstream https://github.com/tj/git-extras
+ OSTYPE=darwin run git browse-ci upstream
+ assert_output "open $expected_url"
+ assert_success
+}
+
+@test "works with mac and gitlab" {
+ get_ci_uri 'gitlab'
+ local expected_url=$REPLY
+
+ git remote add upstream https://gitlab.com/tj/git-extras
+ OSTYPE=darwin run git browse-ci upstream
+ assert_output "open $expected_url"
+ assert_success
+}
+
+@test "works with mac and bitbucket" {
+ get_ci_uri 'bitbucket'
+ local expected_url=$REPLY
+
+ git remote add upstream https://bitbucket.org/tj/git-extras
+ OSTYPE=darwin run git browse-ci upstream
+ assert_output "open $expected_url"
+ assert_success
+}
+
+@test "works with windows and github" {
+ get_ci_uri 'github'
+ local expected_url=$REPLY
+
+ git remote add upstream https://github.com/tj/git-extras
+ OSTYPE=msys run git browse-ci upstream
+ assert_output "start $expected_url"
+ assert_success
+}
+
+@test "works with windows and gitlab" {
+ get_ci_uri 'gitlab'
+ local expected_url=$REPLY
+
+ git remote add upstream https://gitlab.com/tj/git-extras
+ OSTYPE=msys run git browse-ci upstream
+ assert_output "start $expected_url"
+ assert_success
+}
+
+@test "works with windows and bitbucket" {
+ get_ci_uri 'bitbucket'
+ local expected_url=$REPLY
+
+ git remote add upstream https://bitbucket.org/tj/git-extras
+ OSTYPE=msys run git browse-ci upstream
+ assert_output "start $expected_url"
+ assert_success
+}
+
+@test "works with linux and github" {
+ get_ci_uri 'github'
+ local expected_url=$REPLY
+
+ git remote add upstream https://github.com/tj/git-extras
+ OSTYPE=linux-gnu run git browse-ci upstream
+ assert_output "xdg-open $expected_url"
+ assert_success
+}
+
+@test "works with linux and gitlab" {
+ get_ci_uri 'gitlab'
+ local expected_url=$REPLY
+
+ git remote add upstream https://gitlab.com/tj/git-extras
+ OSTYPE=linux-gnu run git browse-ci upstream
+ assert_output "xdg-open $expected_url"
+ assert_success
+}
+
+@test "works with linux and bitbucket" {
+ get_ci_uri 'bitbucket'
+ local expected_url=$REPLY
+
+ git remote add upstream https://bitbucket.org/tj/git-extras
+ OSTYPE=linux-gnu run git browse-ci upstream
+ assert_output "xdg-open $expected_url"
+ assert_success
+}
diff --git a/tests/git-browse.bats b/tests/git-browse.bats
new file mode 100644
index 0000000..190ef00
--- /dev/null
+++ b/tests/git-browse.bats
@@ -0,0 +1,123 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+setup_file() {
+ test_util.setup_file
+
+ PATH="$BATS_TEST_DIRNAME/bin:$PATH"
+}
+
+setup() {
+ test_util.cd_test
+
+ test_util.git_init
+ touch ./browse_this
+ git add ./browse_this
+ git commit -m 'Add test file'
+}
+
+get_file_uri() {
+ local mode=$1
+ local filename=$2
+
+ local commit_hash=
+ commit_hash=$(git rev-parse HEAD)
+ if [ "$mode" = 'github' ]; then
+ REPLY="https://github.com/tj/git-extras/blob/$commit_hash/${filename}"
+ elif [ "$mode" = 'gitlab' ]; then
+ REPLY="https://gitlab.com/tj/git-extras/-/blob/${commit_hash}/${filename}"
+ elif [ "$mode" = 'bitbucket' ]; then
+ REPLY="https://bitbucket.org/tj/git-extras/src/${commit_hash}/${filename}"
+ fi
+}
+
+@test "works with mac and github" {
+ get_file_uri github ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://github.com/tj/git-extras
+ OSTYPE=darwin run git browse upstream ./browse_this
+ assert_output "open $expected_url"
+ assert_success
+}
+
+@test "works with mac and gitlab" {
+ get_file_uri gitlab ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://gitlab.com/tj/git-extras
+ OSTYPE=darwin run git browse upstream ./browse_this
+ assert_output "open $expected_url"
+ assert_success
+}
+
+@test "works with mac and bitbucket" {
+ get_file_uri bitbucket ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://bitbucket.org/tj/git-extras
+ OSTYPE=darwin run git browse upstream ./browse_this
+ assert_output "open $expected_url"
+ assert_success
+}
+
+@test "works with windows and github" {
+ get_file_uri github ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://github.com/tj/git-extras
+ OSTYPE=msys run git browse upstream ./browse_this
+ assert_output "start $expected_url"
+ assert_success
+}
+
+@test "works with windows and gitlab" {
+ get_file_uri gitlab ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://gitlab.com/tj/git-extras
+ OSTYPE=msys run git browse upstream ./browse_this
+ assert_output "start $expected_url"
+ assert_success
+}
+
+@test "works with windows and bitbucket" {
+ get_file_uri bitbucket ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://bitbucket.org/tj/git-extras
+ OSTYPE=msys run git browse upstream ./browse_this
+ assert_output "start $expected_url"
+ assert_success
+}
+
+@test "works with linux and github" {
+ get_file_uri github ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://github.com/tj/git-extras
+ OSTYPE=linux-gnu run git browse upstream ./browse_this
+ assert_output "xdg-open $expected_url"
+ assert_success
+}
+
+@test "works with linux and gitlab" {
+ get_file_uri gitlab ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://gitlab.com/tj/git-extras
+ OSTYPE=linux-gnu run git browse upstream ./browse_this
+ assert_output "xdg-open $expected_url"
+ assert_success
+}
+
+@test "works with linux and bitbucket" {
+ get_file_uri bitbucket ./browse_this
+ local expected_url=$REPLY
+
+ git remote add upstream https://bitbucket.org/tj/git-extras
+ OSTYPE=linux-gnu run git browse upstream ./browse_this
+ assert_output "xdg-open $expected_url"
+ assert_success
+}
diff --git a/tests/git-continue.bats b/tests/git-continue.bats
new file mode 100755
index 0000000..da916a8
--- /dev/null
+++ b/tests/git-continue.bats
@@ -0,0 +1,92 @@
+#!/usr/bin/env bats
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+setup_file() {
+ test_util.setup_file
+}
+
+setup() {
+ test_util.cd_test
+
+ test_util.git_init
+ git commit -m 'Initial commit' --allow-empty
+
+ git switch -c A main
+ printf '%s\n' 'a' >> ./tmp_file
+ git add ./tmp_file
+ git commit -m 'A'
+
+ git switch -c B main
+ printf '%s\n' 'b' >> ./tmp_file
+ git add ./tmp_file
+ git commit -m 'B'
+}
+
+@test "works with cherry pick" {
+ run git cherry-pick A
+ assert_failure
+
+ run git status
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ git add .
+ GIT_EDITOR=cat run git continue
+ assert_success
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
+
+@test "works with merge" {
+ run git merge A
+ assert_failure
+
+ run git status
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ git add .
+ GIT_EDITOR=cat run git continue
+ assert_success
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
+
+@test "works with rebase" {
+ run git rebase A
+ assert_failure
+
+ run git status
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ git add .
+ GIT_EDITOR=cat run git continue
+ assert_success
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
+
+@test "works with revert" {
+ run git revert A
+ assert_failure
+
+ run git status
+ assert_line -p 'Unmerged paths:'
+ assert_success
+
+ git add .
+ GIT_EDITOR=cat run git continue
+ assert_failure # TODO: Git seems to do nothing and error out?
+
+ run git status
+ assert_line -p 'nothing to commit, working tree clean'
+ assert_success
+}
diff --git a/tests/helper.py b/tests/helper.py
index 1c9349f..e4e931d 100644
--- a/tests/helper.py
+++ b/tests/helper.py
@@ -1,6 +1,8 @@
-import os, subprocess, shutil, tempfile
+import os
+import subprocess
+import shutil
+import tempfile
from git import Repo, GitCommandError
-from testpath import MockCommand, modified_env
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
GIT_EXTRAS_BIN = os.path.abspath(os.path.join(CURRENT_DIR, "..", "bin"))
@@ -10,15 +12,16 @@ GITHUB_ORIGIN = "https://github.com/tj/git-extras.git"
GITLAB_ORIGIN = "https://gitlab.com/tj/git-extras.git"
BITBUCKET_ORIGIN = "https://bitbucket.org/tj/git-extras.git"
+
class TempRepository:
- def __init__(self, repo_work_dir = None):
+ def __init__(self, repo_work_dir=None):
self._system_tmpdir = tempfile.gettempdir()
- if repo_work_dir == None:
+ if repo_work_dir is None:
repo_work_dir = tempfile.mkdtemp()
else:
repo_work_dir = os.path.join(self._system_tmpdir, repo_work_dir)
self._cwd = repo_work_dir
- self._tempdirname = self._cwd[len(self._system_tmpdir) + 1:]
+ self._tempdirname = self._cwd[len(self._system_tmpdir) + 1 :]
self._git_repo = Repo.init(repo_work_dir, b="default")
self._files = []
self.change_origin_to_github()
@@ -50,8 +53,8 @@ class TempRepository:
tmp_dir = tempfile.mkdtemp()
return tmp_dir
- def create_tmp_file(self, temp_dir = None):
- if temp_dir == None:
+ def create_tmp_file(self, temp_dir=None):
+ if temp_dir is None:
temp_dir = self._cwd
tmp_file = tempfile.mkstemp(dir=temp_dir)
@@ -63,7 +66,7 @@ class TempRepository:
print(f"File {file_path} has been removed")
def writefile(self, temp_file, data):
- if data == None:
+ if data is None:
return
with open(temp_file, "w", encoding="utf-8") as f:
@@ -86,8 +89,9 @@ class TempRepository:
origin_extras_command = os.path.join(GIT_EXTRAS_BIN, command_name)
temp_extras_command = os.path.join(self._cwd, command_name)
helpers = [
- os.path.join(GIT_EXTRAS_HELPER, "git-extra-utility"),
- os.path.join(GIT_EXTRAS_HELPER, "is-git-repo")]
+ os.path.join(GIT_EXTRAS_HELPER, "git-extra-utility"),
+ os.path.join(GIT_EXTRAS_HELPER, "is-git-repo"),
+ ]
if not os.path.exists(temp_extras_command):
whole = []
@@ -106,7 +110,7 @@ class TempRepository:
os.chmod(temp_extras_command, 0o775)
script = [temp_extras_command, *params]
- print(f"Run the script \"{script}\"")
+ print(f'Run the script "{script}"')
return subprocess.run(script, capture_output=True)
def change_origin(self, origin_url):
diff --git a/tests/poetry.lock b/tests/poetry.lock
index 5fa5e29..cef29c8 100644
--- a/tests/poetry.lock
+++ b/tests/poetry.lock
@@ -1,4 +1,21 @@
-# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+
+[[package]]
+name = "codespell"
+version = "2.4.0"
+description = "Fix common misspellings in text files"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "codespell-2.4.0-py3-none-any.whl", hash = "sha256:b4c5b779f747dd481587aeecb5773301183f52b94b96ed51a28126d0482eec1d"},
+ {file = "codespell-2.4.0.tar.gz", hash = "sha256:587d45b14707fb8ce51339ba4cce50ae0e98ce228ef61f3c5e160e34f681be58"},
+]
+
+[package.extras]
+dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
+hard-encoding-detection = ["chardet"]
+toml = ["tomli"]
+types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
[[package]]
name = "colorama"
@@ -56,13 +73,13 @@ files = [
[[package]]
name = "packaging"
-version = "24.0"
+version = "24.2"
description = "Core utilities for Python packages"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
- {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
+ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
+ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
@@ -127,5 +144,5 @@ test = ["pytest"]
[metadata]
lock-version = "2.0"
-python-versions = "^3.11"
-content-hash = "e73f9840c5f034acbd9b1ebc082e7ce4600ac3235991e57cd825c261a5f2e14e"
+python-versions = "^3.12"
+content-hash = "fcf0ba3dd6655735da5c1e21ba32b94011d32bc5a30fbdaf31146d3820c1442a"
diff --git a/tests/pyproject.toml b/tests/pyproject.toml
index 4326657..3d017cb 100644
--- a/tests/pyproject.toml
+++ b/tests/pyproject.toml
@@ -8,11 +8,16 @@ license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
-python = "^3.11"
+python = "^3.12"
+
+[tool.poetry.group.test.dependencies]
pytest = "8.1.2"
gitpython = "3.1.43"
testpath = "0.6.0"
+[tool.poetry.group.dev.dependencies]
+codespell = "2.4"
+
[tool.pytest.ini_options]
minversion = "7.4"
addopts = "-ra -q"
@@ -21,3 +26,66 @@ testpaths = ["."]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+
+[tool.ruff]
+# Exclude a variety of commonly ignored directories.
+exclude = [
+ ".bzr",
+ ".direnv",
+ ".eggs",
+ ".git",
+ ".git-rewrite",
+ ".hg",
+ ".ipynb_checkpoints",
+ ".mypy_cache",
+ ".nox",
+ ".pants.d",
+ ".pyenv",
+ ".pytest_cache",
+ ".pytype",
+ ".ruff_cache",
+ ".svn",
+ ".tox",
+ ".venv",
+ ".vscode",
+ "__pypackages__",
+ "_build",
+ "buck-out",
+ "build",
+ "dist",
+ "node_modules",
+ "site-packages",
+ "venv",
+]
+
+# Same as Black.
+line-length = 88
+indent-width = 4
+
+# Assume Python 3.9
+target-version = "py39"
+
+[tool.ruff.lint]
+# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
+select = ["E4", "E7", "E9", "F"]
+ignore = []
+
+# Allow fix for all enabled rules (when `--fix`) is provided.
+fixable = ["ALL"]
+unfixable = []
+
+# Allow unused variables when underscore-prefixed.
+dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
+
+[tool.ruff.format]
+# Like Black, use double quotes for strings.
+quote-style = "double"
+
+# Like Black, indent with spaces, rather than tabs.
+indent-style = "space"
+
+# Like Black, respect magic trailing commas.
+skip-magic-trailing-comma = false
+
+# Like Black, automatically detect the appropriate line ending.
+line-ending = "auto"
diff --git a/tests/test_git_abort.py b/tests/test_git_abort.py
index 38e52c5..1e3785f 100644
--- a/tests/test_git_abort.py
+++ b/tests/test_git_abort.py
@@ -1,5 +1,6 @@
from git import GitCommandError
+
class TestGitAbort:
def test_init(self, temp_repo):
git = temp_repo.get_repo_git()
diff --git a/tests/test_git_archive_file.py b/tests/test_git_archive_file.py
index 6874322..ddf117e 100644
--- a/tests/test_git_archive_file.py
+++ b/tests/test_git_archive_file.py
@@ -1,4 +1,6 @@
-import os, pytest
+import os
+import pytest
+
class TestGitArchiveFile:
def test_init(self, temp_repo):
@@ -16,14 +18,17 @@ class TestGitArchiveFile:
filename = "{0}.{1}.zip".format(temp_repo.get_repo_dirname(), git.describe())
assert filename in os.listdir()
- def test_archive_file_on_any_not_tags_branch_without_default_branch(self, temp_repo):
+ def test_archive_file_on_any_not_tags_branch_without_default_branch(
+ self, temp_repo
+ ):
git = temp_repo.get_repo_git()
git.checkout("-b", "not-tags-branch")
temp_repo.invoke_installed_extras_command("archive-file")
filename = "{0}.{1}.{2}.zip".format(
- temp_repo.get_repo_dirname(),
- git.describe("--always", "--long"),
- "not-tags-branch")
+ temp_repo.get_repo_dirname(),
+ git.describe("--always", "--long"),
+ "not-tags-branch",
+ )
assert filename in os.listdir()
def test_archive_file_on_any_not_tags_branch_with_default_branch(self, temp_repo):
@@ -32,8 +37,8 @@ class TestGitArchiveFile:
git.config("git-extras.default-branch", "default")
temp_repo.invoke_installed_extras_command("archive-file")
filename = "{0}.{1}.zip".format(
- temp_repo.get_repo_dirname(),
- git.describe("--always", "--long"))
+ temp_repo.get_repo_dirname(), git.describe("--always", "--long")
+ )
assert filename in os.listdir()
def test_archive_file_on_branch_name_has_slash(self, temp_repo):
@@ -41,9 +46,10 @@ class TestGitArchiveFile:
git.checkout("-b", "feature/slash")
temp_repo.invoke_installed_extras_command("archive-file")
filename = "{0}.{1}.{2}.zip".format(
- temp_repo.get_repo_dirname(),
- git.describe("--always", "--long"),
- "feature-slash")
+ temp_repo.get_repo_dirname(),
+ git.describe("--always", "--long"),
+ "feature-slash",
+ )
assert filename in os.listdir()
@pytest.mark.parametrize("named_temp_repo", ["backslash\\dir"], indirect=True)
@@ -51,9 +57,8 @@ class TestGitArchiveFile:
named_temp_repo.invoke_installed_extras_command("archive-file")
git = named_temp_repo.get_repo_git()
filename = "{0}.{1}.{2}.zip".format(
- "backslash-dir",
- git.describe("--always", "--long"),
- "default")
+ "backslash-dir", git.describe("--always", "--long"), "default"
+ )
assert filename in os.listdir()
def test_archive_file_on_tag_name_has_slash(self, temp_repo):
@@ -65,6 +70,6 @@ class TestGitArchiveFile:
temp_repo.invoke_installed_extras_command("archive-file")
description_include_version = git.describe("--always", "--long")
filename = "{0}.{1}.zip".format(
- temp_repo.get_repo_dirname(),
- description_include_version.replace("/", "-"))
+ temp_repo.get_repo_dirname(), description_include_version.replace("/", "-")
+ )
assert filename in os.listdir()
diff --git a/tests/test_git_authors.py b/tests/test_git_authors.py
index 8eb3cf9..f342433 100644
--- a/tests/test_git_authors.py
+++ b/tests/test_git_authors.py
@@ -1,9 +1,10 @@
-import os, subprocess
-
-expected_authors_list = "test \ntestagain \n"
+expected_authors_list = (
+ "test \ntestagain \n"
+)
expected_authors_list_without_email = "test\ntestagain\n"
authors_file = "AUTHORS"
+
class TestGitAuthors:
def test_init(self, temp_repo):
git = temp_repo.get_repo_git()
@@ -18,8 +19,8 @@ class TestGitAuthors:
git.commit("-m", "test: add data B")
def test_output_authors_has_email_without_any_parameter(self, temp_repo):
- git = temp_repo.get_repo_git()
- rs = temp_repo.invoke_extras_command("authors")
+ temp_repo.get_repo_git()
+ temp_repo.invoke_extras_command("authors")
with open(authors_file) as f:
content = f.read()
print(content)
@@ -27,7 +28,7 @@ class TestGitAuthors:
assert content == expected_authors_list
def test_list_authors_has_email_defaultly(self, temp_repo):
- git = temp_repo.get_repo_git()
+ temp_repo.get_repo_git()
actual = temp_repo.invoke_extras_command("authors", "--list")
actual = actual.stdout.decode()
assert actual == expected_authors_list
@@ -36,7 +37,7 @@ class TestGitAuthors:
assert actual == expected_authors_list
def test_list_authors_has_not_email(self, temp_repo):
- git = temp_repo.get_repo_git()
+ temp_repo.get_repo_git()
actual = temp_repo.invoke_extras_command("authors", "--list", "--no-email")
actual = actual.stdout.decode()
assert actual == expected_authors_list_without_email
diff --git a/tests/test_git_browse.py b/tests/test_git_browse.py
index ff4b4db..39822bf 100644
--- a/tests/test_git_browse.py
+++ b/tests/test_git_browse.py
@@ -1,22 +1,23 @@
-import os, subprocess
from testpath import MockCommand, modified_env
UNKNOWN_SITE_ORIGIN = "https://unknown-site.com/tj/git-extras.git"
+
def get_file_uri(mode, filename, git):
commit_hash = git.rev_parse("HEAD")
if mode == "github":
- return "https://github.com/tj/git-extras/blob/" + commit_hash + '/' + filename
+ return "https://github.com/tj/git-extras/blob/" + commit_hash + "/" + filename
if mode == "gitlab":
- return "https://gitlab.com/tj/git-extras/-/blob/" + commit_hash + '/' + filename
+ return "https://gitlab.com/tj/git-extras/-/blob/" + commit_hash + "/" + filename
if mode == "bitbucket":
- return "https://bitbucket.org/tj/git-extras/src/" + commit_hash + '/' + filename
+ return "https://bitbucket.org/tj/git-extras/src/" + commit_hash + "/" + filename
+
class TestGitBrowse:
def test_browse_github_file_on_mac(self, temp_repo):
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "darwin" }), MockCommand("open") as openCommand:
+ with modified_env({"OSTYPE": "darwin"}), MockCommand("open") as openCommand:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
@@ -25,7 +26,7 @@ class TestGitBrowse:
temp_repo.change_origin_to_gitlab()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "darwin" }), MockCommand("open") as openCommand:
+ with modified_env({"OSTYPE": "darwin"}), MockCommand("open") as openCommand:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
@@ -34,7 +35,7 @@ class TestGitBrowse:
temp_repo.change_origin_to_bitbucket()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "darwin" }), MockCommand("open") as openCommand:
+ with modified_env({"OSTYPE": "darwin"}), MockCommand("open") as openCommand:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
@@ -43,7 +44,7 @@ class TestGitBrowse:
temp_repo.change_origin_to_github()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "msys" }), MockCommand("start") as openCommand:
+ with modified_env({"OSTYPE": "msys"}), MockCommand("start") as openCommand:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
@@ -52,7 +53,7 @@ class TestGitBrowse:
temp_repo.change_origin_to_gitlab()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "msys" }), MockCommand("start") as openCommand:
+ with modified_env({"OSTYPE": "msys"}), MockCommand("start") as openCommand:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
@@ -61,7 +62,7 @@ class TestGitBrowse:
temp_repo.change_origin_to_bitbucket()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "msys" }), MockCommand("start") as openCommand:
+ with modified_env({"OSTYPE": "msys"}), MockCommand("start") as openCommand:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
@@ -70,8 +71,12 @@ class TestGitBrowse:
temp_repo.change_origin_to_github()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "microsoft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("powershell.exe") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "microsoft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("powershell.exe") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called(["-NoProfile", "start", expected_url])
@@ -80,8 +85,12 @@ class TestGitBrowse:
temp_repo.change_origin_to_gitlab()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "microsoft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("powershell.exe") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "microsoft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("powershell.exe") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called(["-NoProfile", "start", expected_url])
@@ -90,8 +99,12 @@ class TestGitBrowse:
temp_repo.change_origin_to_bitbucket()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "microsoft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("powershell.exe") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "microsoft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("powershell.exe") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called(["-NoProfile", "start", expected_url])
@@ -100,8 +113,12 @@ class TestGitBrowse:
temp_repo.change_origin_to_github()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "no-micro-soft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "no-micro-soft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
@@ -110,8 +127,12 @@ class TestGitBrowse:
temp_repo.change_origin_to_gitlab()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "no-micro-soft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "no-micro-soft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
@@ -120,8 +141,12 @@ class TestGitBrowse:
temp_repo.change_origin_to_bitbucket()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "no-micro-soft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "no-micro-soft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
@@ -130,7 +155,10 @@ class TestGitBrowse:
temp_repo.change_origin_to_github()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
@@ -139,7 +167,10 @@ class TestGitBrowse:
temp_repo.change_origin_to_gitlab()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
@@ -148,7 +179,10 @@ class TestGitBrowse:
temp_repo.change_origin_to_bitbucket()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
@@ -157,7 +191,10 @@ class TestGitBrowse:
temp_repo.change_origin_to_github()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename, "10", "20")
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url + "#L10-L20"])
@@ -166,7 +203,10 @@ class TestGitBrowse:
temp_repo.change_origin_to_gitlab()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename, "10", "20")
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url + "#L10-20"])
@@ -175,21 +215,30 @@ class TestGitBrowse:
temp_repo.change_origin_to_bitbucket()
git = temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename, "10", "20")
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url + "#lines-10:20"])
def test_browse_unknown_site_file(self, temp_repo):
temp_repo.change_origin(UNKNOWN_SITE_ORIGIN)
- git = temp_repo.get_repo_git()
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ temp_repo.get_repo_git()
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin")
openCommand.assert_called([UNKNOWN_SITE_ORIGIN[0:-4]])
def test_browse_unknown_site_file_with_line_number(self, temp_repo):
- git = temp_repo.get_repo_git()
+ temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse", "origin", filename, "10", "20")
openCommand.assert_called([UNKNOWN_SITE_ORIGIN[0:-4]])
diff --git a/tests/test_git_browse_ci.py b/tests/test_git_browse_ci.py
index 761d86d..b69e554 100644
--- a/tests/test_git_browse_ci.py
+++ b/tests/test_git_browse_ci.py
@@ -1,8 +1,8 @@
-import os, subprocess
from testpath import MockCommand, modified_env
UNKNOWN_SITE_ORIGIN = "https://unknown-site.com/tj/git-extras.git"
+
def get_ci_uri_by_domain(mode):
if mode == "github":
return "https://github.com/tj/git-extras/actions"
@@ -11,119 +11,156 @@ def get_ci_uri_by_domain(mode):
if mode == "bitbucket":
return "https://bitbucket.org/tj/git-extras/addon/pipelines/home"
+
class TestGitBrowse:
def test_browse_github_ci_on_mac(self, temp_repo):
- with modified_env({ "OSTYPE": "darwin" }), MockCommand("open") as openCommand:
+ with modified_env({"OSTYPE": "darwin"}), MockCommand("open") as openCommand:
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("github")
openCommand.assert_called([expected_url])
def test_browse_gitlab_ci_on_mac(self, temp_repo):
temp_repo.change_origin_to_gitlab()
- with modified_env({ "OSTYPE": "darwin" }), MockCommand("open") as openCommand:
+ with modified_env({"OSTYPE": "darwin"}), MockCommand("open") as openCommand:
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("gitlab")
openCommand.assert_called([expected_url])
def test_browse_bitbucket_ci_on_mac(self, temp_repo):
temp_repo.change_origin_to_bitbucket()
- with modified_env({ "OSTYPE": "darwin" }), MockCommand("open") as openCommand:
+ with modified_env({"OSTYPE": "darwin"}), MockCommand("open") as openCommand:
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("bitbucket")
openCommand.assert_called([expected_url])
def test_browse_github_ci_on_git_bash_on_window(self, temp_repo):
temp_repo.change_origin_to_github()
- with modified_env({ "OSTYPE": "msys" }), MockCommand("start") as openCommand:
+ with modified_env({"OSTYPE": "msys"}), MockCommand("start") as openCommand:
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("github")
openCommand.assert_called([expected_url])
def test_browse_gitlab_ci_on_git_bash_on_window(self, temp_repo):
temp_repo.change_origin_to_gitlab()
- with modified_env({ "OSTYPE": "msys" }), MockCommand("start") as openCommand:
+ with modified_env({"OSTYPE": "msys"}), MockCommand("start") as openCommand:
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("gitlab")
openCommand.assert_called([expected_url])
def test_browse_bitbucket_ci_on_git_bash_on_window(self, temp_repo):
temp_repo.change_origin_to_bitbucket()
- with modified_env({ "OSTYPE": "msys" }), MockCommand("start") as openCommand:
+ with modified_env({"OSTYPE": "msys"}), MockCommand("start") as openCommand:
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("bitbucket")
openCommand.assert_called([expected_url])
def test_browse_github_ci_on_WSL_with_microsoft_key(self, temp_repo):
temp_repo.change_origin_to_github()
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "microsoft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("powershell.exe") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "microsoft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("powershell.exe") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("github")
openCommand.assert_called(["-NoProfile", "start", expected_url])
def test_browse_gitlab_ci_on_WSL_with_microsoft_key(self, temp_repo):
temp_repo.change_origin_to_gitlab()
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "microsoft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("powershell.exe") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "microsoft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("powershell.exe") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("gitlab")
openCommand.assert_called(["-NoProfile", "start", expected_url])
def test_browse_bitbucket_ci_on_WSL_with_microsoft_key(self, temp_repo):
temp_repo.change_origin_to_bitbucket()
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "microsoft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("powershell.exe") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "microsoft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("powershell.exe") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("bitbucket")
openCommand.assert_called(["-NoProfile", "start", expected_url])
def test_browse_github_ci_on_WSL_without_microsoft_key(self, temp_repo):
temp_repo.change_origin_to_github()
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "no-micro-soft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "no-micro-soft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("github")
openCommand.assert_called([expected_url])
def test_browse_gitlab_ci_on_WSL_without_microsoft_key(self, temp_repo):
temp_repo.change_origin_to_gitlab()
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "no-micro-soft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "no-micro-soft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("gitlab")
openCommand.assert_called([expected_url])
def test_browse_bitbucket_ci_on_WSL_without_microsoft_key(self, temp_repo):
temp_repo.change_origin_to_bitbucket()
- with modified_env({ "OSTYPE": "linux" }), MockCommand.fixed_output("uname", "no-micro-soft"), \
- MockCommand.fixed_output("command", "/powershell.exe"), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "linux"}),
+ MockCommand.fixed_output("uname", "no-micro-soft"),
+ MockCommand.fixed_output("command", "/powershell.exe"),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("bitbucket")
openCommand.assert_called([expected_url])
def test_browse_github_ci_not_mac_or_msys_or_linux(self, temp_repo):
temp_repo.change_origin_to_github()
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("github")
openCommand.assert_called([expected_url])
def test_browse_gitlab_ci_not_mac_or_msys_or_linux(self, temp_repo):
temp_repo.change_origin_to_gitlab()
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("gitlab")
openCommand.assert_called([expected_url])
def test_browse_bitbucket_ci_not_mac_or_msys_or_linux(self, temp_repo):
temp_repo.change_origin_to_bitbucket()
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
expected_url = get_ci_uri_by_domain("bitbucket")
openCommand.assert_called([expected_url])
def test_browse_unknown_site_file(self, temp_repo):
temp_repo.change_origin(UNKNOWN_SITE_ORIGIN)
- with modified_env({ "OSTYPE": "unique-system" }), MockCommand("xdg-open") as openCommand:
+ with (
+ modified_env({"OSTYPE": "unique-system"}),
+ MockCommand("xdg-open") as openCommand,
+ ):
temp_repo.invoke_extras_command("browse-ci", "origin")
- openCommand.assert_called([''])
+ openCommand.assert_called([""])
diff --git a/tests/test_git_continue.py b/tests/test_git_continue.py
new file mode 100644
index 0000000..8fd7d5e
--- /dev/null
+++ b/tests/test_git_continue.py
@@ -0,0 +1,84 @@
+from git import GitCommandError
+
+
+class TestGitContinue:
+
+ @classmethod
+ def _init_repo(cls, repo):
+ git = repo.get_repo_git()
+ tmp_file = repo.get_file(0)
+ git.branch("A")
+ git.branch("B")
+ git.branch("C")
+ git.checkout("A")
+ repo.writefile(tmp_file, "a")
+ git.add(".")
+ git.commit("-m", "A")
+ git.checkout("B")
+ repo.writefile(tmp_file, "b")
+ git.add(".")
+ git.commit("-m", "B")
+
+ def test_init(self, temp_repo_clean):
+ TestGitContinue._init_repo(temp_repo_clean)
+ git = temp_repo_clean.get_repo_git()
+ git.status()
+
+ def test_cherry_pick(self, temp_repo_clean):
+ TestGitContinue._init_repo(temp_repo_clean)
+ git = temp_repo_clean.get_repo_git()
+ try:
+ git.cherry_pick("A")
+ except GitCommandError as err:
+ print(err)
+ result = git.status()
+ assert "Unmerged path" in result
+ git.add(".")
+ temp_repo_clean.invoke_extras_command("continue")
+ result = git.status()
+ assert "nothing to commit, working tree clean" in result
+
+ def test_merge(self, temp_repo_clean):
+ TestGitContinue._init_repo(temp_repo_clean)
+ git = temp_repo_clean.get_repo_git()
+ try:
+ git.merge("A")
+ except GitCommandError as err:
+ print(err)
+ result = git.status()
+ assert "Unmerged path" in result
+ git.add(".")
+ git.commit("-m", "resolve conflict")
+ temp_repo_clean.invoke_extras_command("continue")
+ result = git.status()
+ assert "nothing to commit, working tree clean" in result
+
+ def test_rebase(self, temp_repo_clean):
+ TestGitContinue._init_repo(temp_repo_clean)
+ git = temp_repo_clean.get_repo_git()
+ try:
+ git.rebase("A")
+ except GitCommandError as err:
+ print(err)
+ result = git.status()
+ assert "Unmerged path" in result
+ git.add(".")
+ git.commit("-m", "resolve conflict")
+ temp_repo_clean.invoke_extras_command("continue")
+ result = git.status()
+ assert "nothing to commit, working tree clean" in result
+
+ def test_revert(self, temp_repo_clean):
+ TestGitContinue._init_repo(temp_repo_clean)
+ git = temp_repo_clean.get_repo_git()
+ try:
+ git.revert("A")
+ except GitCommandError as err:
+ print(err)
+ result = git.status()
+ assert "Unmerged path" in result
+ git.add(".")
+ git.commit("-m", "resolve conflict")
+ temp_repo_clean.invoke_extras_command("continue")
+ result = git.status()
+ assert "nothing to commit, working tree clean" in result
diff --git a/tests/test_util.sh b/tests/test_util.sh
new file mode 100644
index 0000000..4c85773
--- /dev/null
+++ b/tests/test_util.sh
@@ -0,0 +1,24 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/../vendor/bats-all/load.bash"
+
+test_util.setup_file() {
+ cd "$BATS_FILE_TMPDIR"
+
+ export GIT_CONFIG_NOSYSTEM=1
+ export GIT_CONFIG_GLOBAL="$PWD/git_config"
+
+ # Append to path so that we can access all commands included from git-extras
+ # TODO: This currently breaks with commands that are included in "not_needed_git_repo" etc.
+ PATH="$BATS_TEST_DIRNAME/../bin:$PATH"
+}
+
+test_util.cd_test() {
+ cd "$BATS_TEST_TMPDIR"
+}
+
+test_util.git_init() {
+ git init --initial-branch main
+ git config user.name 'Name'
+ git config user.email 'name@example.com'
+}
diff --git a/vendor/bats-all b/vendor/bats-all
new file mode 160000
index 0000000..a16a4a2
--- /dev/null
+++ b/vendor/bats-all
@@ -0,0 +1 @@
+Subproject commit a16a4a2cfa744878992a5f5d3f206319dba54158