Compare commits

...

15 commits
7.5.0 ... main

Author SHA1 Message Date
dependabot[bot] 4cf28ec559
chore(deps): bump actions/stale from 10 to 11 (#1265)
Some checks failed
ci / lint (push) Has been cancelled
ci / typo (push) Has been cancelled
ci / Test with Bats (push) Has been cancelled
ci / build (macos-latest) (push) Has been cancelled
ci / build (ubuntu-latest) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 12:25:44 -07:00
罗泽轩 09b77e8ed6
ci: add generated manuals check (#1268)
Some checks failed
ci / lint (push) Has been cancelled
ci / typo (push) Has been cancelled
ci / Test with Bats (push) Has been cancelled
ci / build (macos-latest) (push) Has been cancelled
ci / build (ubuntu-latest) (push) Has been cancelled
Signed-off-by: spacewander <spacewanderlzx@gmail.com>
2026-08-27 13:59:29 +08:00
Vijay Misal 4c38988ba9
feat(mr): support Forgejo/Codeberg pull request URLs (#1267)
* feat(mr): support Forgejo/Codeberg pull request URLs

Codeberg and other Forgejo instances expose pull requests under
refs/pull/<id>/head instead of GitLab's refs/merge-requests/<id>/head,
so passing a Codeberg pull request URL to git mr fetched the wrong
ref. Recognize the /pulls/<id> URL shape and switch to the matching
ref for that case, leaving the GitLab URL and bare numeric id
behavior unchanged.

Closes #1213

* test(git-mr): cover Forgejo/Codeberg URLs, regenerate man docs

Add a bats test that exercises the pulls URL branch via a local
insteadOf remote rewrite (no real HTTP endpoint needed), and bring
git-mr.1/git-mr.html in line with the git-mr.md changes.

---------

Co-authored-by: vjymisal0 <vijay.looprai@gmail.com>
2026-08-27 13:59:00 +08:00
Vijay Misal dbf68bac59
fix(is-git-repo): recognize bare repositories (#1266)
* fix(is-git-repo): recognize bare repositories

is_git_repo() used `git rev-parse --show-toplevel` to detect whether
the current directory is inside a git repository. --show-toplevel
fails for bare repositories since they have no working tree, so every
command that relies on this shared helper (e.g. `git browse`) reports
"Not a git repo!" when run from inside a bare repo, even though it
plainly is one.

Switch to `git rev-parse --git-dir`, which succeeds for both normal
and bare repositories and keeps the existing "not a repo" behavior
for non-repo directories.

Fixes #1238, reported and LGTM'd by maintainers there with this exact
fix; no PR had been opened for it yet.

Adds tests/is-git-repo.bats covering: a normal repo, a bare repo, and
a plain (non-repo) directory.

* Fix ruff lint failures in scripts/checkstyle.py

This PR's new Ruff CI job surfaced pre-existing style issues (unsorted
imports, deprecated typing.List/Dict, nested ifs, bare exit()) in
checkstyle.py. Cleaned these up so the newly added lint job passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 14:15:48 +08:00
dependabot[bot] 03569856db
chore(deps): bump actions/setup-python from 6 to 7 (#1263)
* chore(deps): bump actions/setup-python from 6 to 7

Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix lints

* Fix typo

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Edwin Kofler <edwin@kofler.dev>
Co-authored-by: spacewander <spacewanderlzx@gmail.com>
2026-08-14 12:27:16 +08:00
罗泽轩 d11e45f0b1
port python tests to bats (#1264)
Signed-off-by: spacewander <spacewanderlzx@gmail.com>
2026-08-12 15:43:59 +08:00
dependabot[bot] fd2e03cf55
chore(deps): bump actions/setup-go from 6 to 7 (#1262)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-19 18:52:47 -07:00
AJ Côté 5a14757944
feat(scp): add verbose, interactive, and dry-run flags (#1261)
* feat(scp): add -v/--verbose and -i/--interactive flags

git scp/git rscp printed a diff via plain `git diff`/`git diff --stat`
with no `--no-pager` and no TTY check, blocking on the user's pager.
Add -v/--verbose to print the diff via --no-pager before syncing, and
-i/--interactive to also prompt for confirmation before the real
push/copy. Both apply to git scp and git rscp, and must precede
<remote>.

Updates man/git-scp.md and Commands.md.

* feat(scp): add -n/--dry-run flag

Add -n/--dry-run to preview a sync without changing anything: rsync
runs with -n, and git add --force / ssh ... rm are skipped outright.
Implies --verbose, since confirming or previewing with nothing shown
is useless.

Also restructures scp_and_stage's push/delete blocks from `&&`-chains
into if/then, needed to branch on dry-run, which incidentally fixes a
latent bug where a non-TTY COLOR_RESET could silently skip the real
rsync/git add/ssh rm calls.

Updates man/git-scp.md and Commands.md.

* test(scp): add coverage for -n/--dry-run and -i/--interactive abort

git-scp had no test coverage at all. Cover the two paths that need no
real remote destination: dry-run must preview without touching the
index, working tree, or remote path, and declining the interactive
confirmation must abort before anything syncs.

* feat(scp): Code review

* feat(scp): Code review
2026-07-14 19:43:00 +08:00
kdergachev b450b36036
Added edit/remove functionality to git-ignore (#1258)
* Added edit/remove functionality to git-ignore.

* Minor change to array-string assignment.

* Fixes array handling for remove.

* Suggested fixes (iteration over arguments, pattern removal, editor handling, tmp file location, symlinked gitignore handling).

* Changed pure bash editing of files to an awk script. Some other minor cosmetic changes.

* Updated docs.

* Modified autocomplete to work with new git-ignore

* Added help message to git-ignore.

* Added help flags to git-ignore's man.

* Made $file in eval safe. Should not do the same for editor since then all of the GIT_EDITOR will be treated as command (flags included).

* Typo fixes.
2026-07-14 19:42:02 +08:00
Acho Arnold 7f36d37283
fix: support Windows ARM64 (clangarm64) in install.cmd (#1259)
Some checks failed
ci / Test with Bats (push) Has been cancelled
ci / lint (push) Has been cancelled
ci / typo (push) Has been cancelled
ci / Test with Pytest (push) Has been cancelled
ci / build (macos-latest) (push) Has been cancelled
ci / build (ubuntu-latest) (push) Has been cancelled
* fix: support Windows ARM64 (clangarm64) in install.cmd

Git for Windows on ARM64 uses a `clangarm64` directory instead of
`mingw64`. The installer now auto-detects `clangarm64` when
`mingw64` is not present, so it works out of the box on ARM64
Windows machines without requiring the user to manually supply
the path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove trailing whitespace from install.cmd

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: correctly resolve PREFIX when git.exe is under the arch dir in PATH

When 'where git.exe' resolves to ...\mingw64\bin\git.exe or
...\clangarm64\bin\git.exe, installdir becomes the architecture directory
itself. Detect that case by inspecting the leaf directory name instead of
appending mingw64/clangarm64 (which produced non-existent ...\mingw64\mingw64
paths) or using ..\ segments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-08 15:20:25 +08:00
dependabot[bot] 184d37e980
chore(deps): bump actions/checkout from 6 to 7 (#1260)
Some checks failed
ci / lint (push) Has been cancelled
ci / typo (push) Has been cancelled
ci / Test with Pytest (push) Has been cancelled
ci / Test with Bats (push) Has been cancelled
ci / build (macos-latest) (push) Has been cancelled
ci / build (ubuntu-latest) (push) Has been cancelled
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 14:05:50 +08:00
Utsav Sabharwal e92878b81b
feat: add git-delete-gone-branches command (#1239)
* feat: add git-delete-gone-branches command

Adds a new command to delete local branches whose remote-tracking
branch has been deleted (shown as [gone] in git branch -vv).

This is a common need after PRs are merged and remote branches are
cleaned up, leaving stale local branches behind.

Features:
- Deletes all local branches with a gone remote in one command
- --dry-run / -n flag to preview branches before deletion

Closes #1220

* Address PR review feedback on git-delete-gone-branches

- Use git for-each-ref instead of git branch -vv to avoid false
  positives from commit messages containing ': gone]'
- Add interactive confirmation prompt by default; skip with -f/--force
- Add -p/--prune flag to run git fetch --prune before checking
- Add generated man page .1 and .html files

* Use GitHub profile for author contact in man page

* Fix gone-branch detection with nobracket format
2026-06-09 10:36:27 +08:00
罗泽轩 b9e1d88f2c
doc: regenerate git-extras.1 (#1255)
This file has been reset since commit 032090e847

Signed-off-by: spacewander <spacewanderlzx@gmail.com>
2026-05-26 12:28:42 +08:00
Ricardo Fernández Serrata 99207eee8e
refactor(delete-merged-branches): rm -n1 from xargs, fmt pipes (#1233) 2026-05-18 16:10:46 +08:00
罗泽轩 9d6319becb
Bump git-extras version to 7.6.0-dev (#1251) 2026-05-11 09:49:38 +08:00
49 changed files with 1747 additions and 1488 deletions

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code. - name: Check out code.
uses: actions/checkout@v6 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- name: 'Get Changed Files' - name: 'Get Changed Files'
@ -26,7 +26,26 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }} BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BEFORE_SHA: "${{ github.event.before }}" BEFORE_SHA: "${{ github.event.before }}"
- uses: 'actions/setup-go@v6' - name: 'Check Generated Manuals'
run: |
failed=0
while IFS= read -r source; do
case "$source" in
man/git-*.md)
stem=${source%.md}
for generated in "$stem.1" "$stem.html"; do
if ! grep -Fqx -- "$generated" <<< "$CHANGED_FILES"; then
echo "::error file=$source::$generated must be updated with $source"
failed=1
fi
done
;;
esac
done <<< "$CHANGED_FILES"
exit "$failed"
env:
CHANGED_FILES: "${{ steps.files.outputs.added_modified }}"
- uses: 'actions/setup-go@v7'
with: with:
go-version: '1.20' go-version: '1.20'
- name: 'Install EditorConfig Lint' - name: 'Install EditorConfig Lint'
@ -40,63 +59,25 @@ jobs:
run: ./scripts/checkstyle.py run: ./scripts/checkstyle.py
- name: Shellcheck - name: Shellcheck
run: shellcheck --severity=error bin/* ./*.sh run: shellcheck --severity=error bin/* ./*.sh
- name: Lint and format Python with Ruff
uses: astral-sh/ruff-action@v3
typo: typo:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code. - name: Check out code.
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Install poetry - name: Install codespell
run: pip install poetry run: python3 -m pip install codespell==2.4
- name: Set up Python
uses: actions/setup-python@v6
with:
cache: 'poetry'
cache-dependency-path: "tests/pyproject.toml"
python-version-file: "tests/pyproject.toml"
- name: Install dependencies
run: |
cd tests || exit
poetry install --only dev
- name: spell check - name: spell check
run: | run: |
cd tests git grep --cached -l '' . | \
git grep --cached -l '' .. | \
grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \ grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \
xargs poetry run codespell --ignore-words=../.github/.ignore_words xargs 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: test-bats:
name: 'Test with Bats' name: 'Test with Bats'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
with: with:
submodules: recursive submodules: recursive
- name: Setup Bats - name: Setup Bats
@ -120,7 +101,7 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Check out code. - name: Check out code.
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Linux Install - name: Linux Install
if: matrix.platform == 'ubuntu-latest' if: matrix.platform == 'ubuntu-latest'
run: sudo apt-get install -y bsdmainutils run: sudo apt-get install -y bsdmainutils

View file

@ -11,7 +11,7 @@ jobs:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
if: github.repository_owner == 'tj' if: github.repository_owner == 'tj'
steps: steps:
- uses: "actions/stale@v10" - uses: "actions/stale@v11"
with: 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" 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-stale: 350

View file

@ -18,6 +18,7 @@
- [`git cp`](#git-cp) - [`git cp`](#git-cp)
- [`git create-branch`](#git-create-branch) - [`git create-branch`](#git-create-branch)
- [`git delete-branch`](#git-delete-branch) - [`git delete-branch`](#git-delete-branch)
- [`git delete-gone-branches`](#git-delete-gone-branches)
- [`git delete-merged-branches`](#git-delete-merged-branches) - [`git delete-merged-branches`](#git-delete-merged-branches)
- [`git delete-squashed-branches`](#git-delete-squashed-branches) - [`git delete-squashed-branches`](#git-delete-squashed-branches)
- [`git delete-submodule`](#git-delete-submodule) - [`git delete-submodule`](#git-delete-submodule)
@ -932,6 +933,21 @@ Delete local and remote tag `name`:
$ git delete-tag 0.0.1 $ git delete-tag 0.0.1
``` ```
## git delete-gone-branches
Deletes all local branches whose remote-tracking branch has been deleted (i.e. branches shown as `[gone]` in `git branch -vv`). Use `--dry-run` to preview what would be deleted.
```bash
$ git delete-gone-branches
Deleted branch feature-123 (was abc1234).
Deleted branch feature-456 (was def5678).
$ git delete-gone-branches --dry-run
Would delete the following branches:
feature-123
feature-456
```
## git delete-merged-branches ## git delete-merged-branches
Deletes branches that are listed in `git branch --merged`. Deletes branches that are listed in `git branch --merged`.
@ -1187,6 +1203,14 @@ Internally this script uses `rsync` and not `scp` as the name suggests.
`git-rscp` - The reverse of `git-scp`. Copies specific files from the working directory of a remote repository to the current working directory. `git-rscp` - The reverse of `git-scp`. Copies specific files from the working directory of a remote repository to the current working directory.
The following options are available (must precede `<remote>`):
```bash
-v, --verbose Print what will be synced before syncing
-i, --interactive Prompt for confirmation before syncing (implies --verbose)
-n, --dry-run Show what would be synced, change nothing (implies --verbose)
```
### Examples ### Examples
Copy unstaged files to remote. Useful when you want to make quick test without making any commits Copy unstaged files to remote. Useful when you want to make quick test without making any commits
@ -1404,8 +1428,8 @@ $ git reset-file .htaccess dc82b19
## git mr ## git mr
Checks out a merge request from GitLab. Usage: `git mr <ID|URL> [REMOTE]`. Checks out a merge request from GitLab, or a pull request from Forgejo/Codeberg.
Default remote is `origin`. Usage: `git mr <ID|URL> [REMOTE]`. Default remote is `origin`.
``` bash ``` bash
$ git mr 51 $ git mr 51
@ -1423,6 +1447,15 @@ From gitlab.com:owner/repository
Switched to branch 'mr/51' Switched to branch 'mr/51'
``` ```
A Forgejo/Codeberg pull request URL is also supported:
``` bash
$ git mr https://codeberg.org/owner/repository/pulls/51
From codeberg.org:owner/repository
* [new ref] refs/pull/51/head -> mr/51
Switched to branch 'mr/51'
```
Just like [git pr](#git-pr), `git mr` accepts a `clean` argument to trash all Just like [git pr](#git-pr), `git mr` accepts a `clean` argument to trash all
`mr/` branches. Ensure current branch is not one. `mr/` branches. Ensure current branch is not one.

View file

@ -141,6 +141,6 @@ docclean:
rm -f man/*.html rm -f man/*.html
test: test:
pytest bats ./tests
.PHONY: default docs check install uninstall clean docclean test .PHONY: default docs check install uninstall clean docclean test

60
bin/git-delete-gone-branches Executable file
View file

@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -euo pipefail
dry_run=false
force=false
prune=false
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--dry-run)
dry_run=true
shift
;;
-f|--force)
force=true
shift
;;
-p|--prune)
prune=true
shift
;;
*)
echo "Unknown option: $1"
echo "Usage: git delete-gone-branches [-n|--dry-run] [-f|--force] [-p|--prune]"
exit 1
;;
esac
done
if [ "$prune" = true ]; then
git fetch --prune
fi
gone_branches=$(git for-each-ref --format \
'%(if:equals=gone)%(upstream:track,nobracket)%(then)%(refname:short)%(end)' refs/heads/ | sed '/^$/d')
if [ -z "$gone_branches" ]; then
echo "No branches with a gone remote found."
exit 0
fi
if [ "$dry_run" = true ]; then
echo "Would delete the following branches:"
echo "$gone_branches"
exit 0
fi
echo "The following branches will be deleted:"
echo "$gone_branches"
if [ "$force" = false ]; then
read -r -p "Delete these branches? [y/N] " confirm
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
echo "Aborted."
exit 0
fi
fi
echo "$gone_branches" | xargs git branch -D

View file

@ -3,5 +3,5 @@
branches=$(git branch --no-color --merged | grep -vE "^(\*|\+)" | grep -v "$(git_extra_default_branch)" | grep -v svn) branches=$(git branch --no-color --merged | grep -vE "^(\*|\+)" | grep -v "$(git_extra_default_branch)" | grep -v svn)
if [ -n "$branches" ] if [ -n "$branches" ]
then then
echo "$branches" | xargs -n 1 git branch -d echo "$branches" | xargs git branch -d
fi fi

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION="7.5.0" VERSION="7.6.0-dev"
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh" INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh"
update() { update() {

View file

@ -2,6 +2,26 @@
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
# awk script that takes 2 input files. The first one is newline deilmeted
# string of patterns to look for, second one is the file to remove them from.
# outfile has to be passed with -v upon call and is the file result is
# written to.
read -r -d '' AWK_SCRIPT <<'EOF'
# process the first "file" (newline split argument list)
NR==FNR { a[b++]=$0; next; }
# process file
{
found=0;
for (i=0; i != b; i++) {
if (a[i] == $0) { found=1; }
}
if (found == 1) { print "... removing '"$0"'"; }
else { print $0 > outfile }
}
EOF
show_contents() { show_contents() {
local file="${2/#~/$HOME}" local file="${2/#~/$HOME}"
if [ -f "$file" ]; then if [ -f "$file" ]; then
@ -94,6 +114,66 @@ add_patterns() {
done done
} }
edit_file() {
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
echo "Editing $1 gitignore: ($2)" && eval "$(git var GIT_EDITOR) $(printf '%q' "$file")"
echo "Done."
else
echo "There is no $1 .gitignore yet." >&2
exit 1
fi
}
_usage() {
cat << EOF
Usage: git ignore [OPTION]... [PATTERN]...
Modify or display local global or private gitignore files.
OPTIONs set context (local, global, private) and action (display, add,
remove, edit). In case of collision, last flag overwrites preceding ones.
Defaults are: local context, action is display (see man git ignore for
more details).
Context examples:
local: .../repo/.gitignore
private: .../repo/.git/info/exclude
global: \$HOME/.gitignore
PATTERNs are the gitignore patterns as specified in git documentation.
Options:
-l, --local Set context to local gitignore
-g, --global Set context to global gitignore
-p, --private Set context to private gitignore
-e, --edit Set action to edit
-r, --remove Set action to remove
-h, --help Print this message
-- End of options delimiter
EOF
}
remove_patterns() {
declare -a args
local tmpfile
args=( "${@:3}" )
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
tmpfile="$(mktemp -q "${TMPDIR:-/tmp}"/git-extras-ignore.XXXXXX 2>/dev/null)"
echo "Removing patterns from $1 gitignore: $2"
awk -v outfile="$tmpfile" "${AWK_SCRIPT}"\
<(printf '%s\n' "${args[@]}") "$file"
cat "$tmpfile" > "$file"
rm "$tmpfile"
else
echo "There is no $1 .gitignore yet"
exit 1
fi
}
# can have only 1 action and 1 level, if conflicting flags are passed
# latter ones overwrite former
# sanity of args not tested (e.g. there should be no args if -e is supplied)
if test $# -eq 0; then if test $# -eq 0; then
show_global show_global
echo "---------------------------------" echo "---------------------------------"
@ -101,22 +181,105 @@ if test $# -eq 0; then
echo "---------------------------------" echo "---------------------------------"
show_private show_private
else else
case "$1" in # parse flags and args
-l|--local) level="none"
test $# -gt 1 && add_local "${@:2}" && echo action="none"
show_local declare -a args
;; while [ $# -gt 0 ]; do
-g|--global) case "$1" in
test $# -gt 1 && add_global "${@:2}" && echo -h|--help)
show_global _usage
;; exit 0
-p|--private) ;;
test $# -gt 1 && add_private "${@:2}" && echo -l|--local)
show_private level="local"
;; ;;
*) -g|--global)
add_local "$@" level="global"
;; ;;
esac -p|--private)
fi level="private"
;;
-e|--edit)
action="edit"
;;
-r|--remove)
action="remove"
;;
--)
shift
args+=("$@")
break
;;
*)
args+=("$1")
;;
esac
shift
done
# compatible with previous version
if [ "$action" = "none" ]; then
case "$level" in
local)
test ${#args[@]} -ne 0 && add_local "${args[@]}" && echo
show_local
;;
global)
test ${#args[@]} -ne 0 && add_global "${args[@]}" && echo
show_global
;;
private)
test ${#args[@]} -ne 0 && add_private "${args[@]}" && echo
show_private
;;
none)
add_local "${args[@]}"
# maybe show_local here too?
;;
esac
exit 0
# open file in editor
elif [ "$action" = "edit" ]; then
case "$level" in
local|none)
cd_to_git_root --warn
edit_file 'local' .gitignore && echo
;;
global)
global_gitignore="$(global_ignore)"
edit_file global "$global_gitignore" && echo
;;
private)
cd_to_git_root --error
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
edit_file private "${GIT_DIR}/info/exclude" && echo
;;
esac
exit 0
# remove entries
else
if [ ${#args[@]} -eq 0 ]; then
echo "Nothing to remove."
exit 0
fi
case "$level" in
local|none)
cd_to_git_root --warn
remove_patterns 'local' .gitignore "${args[@]}" && echo
show_local
;;
global)
global_gitignore="$(global_ignore)"
remove_patterns global "$global_gitignore" "${args[@]}" && echo
show_global
;;
private)
cd_to_git_root --error
remove_patterns private "${GIT_DIR}/info/exclude" "${args[@]}" && echo
show_private
;;
esac
exit 0
fi
fi

View file

@ -7,6 +7,8 @@ if [ -z "${1-}" ] ; then
exit 1 exit 1
fi fi
remote_ref_kind=merge-requests
if test "$1" = "clean"; then if test "$1" = "clean"; then
git for-each-ref refs/heads/mr/* --format='%(refname)' | while read -r ref; do git for-each-ref refs/heads/mr/* --format='%(refname)' | while read -r ref; do
git branch -D "${ref#refs/heads/}" git branch -D "${ref#refs/heads/}"
@ -15,13 +17,19 @@ if test "$1" = "clean"; then
elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then
remote=${BASH_REMATCH[1]}.git remote=${BASH_REMATCH[1]}.git
id=${BASH_REMATCH[3]} id=${BASH_REMATCH[3]}
elif [[ $1 =~ ^(https?://[^/]+/(.+))/pulls/([0-9]+).*$ ]]; then
# Forgejo/Codeberg pull request URL, e.g.
# https://codeberg.org/owner/repository/pulls/453
remote=${BASH_REMATCH[1]}.git
id=${BASH_REMATCH[3]}
remote_ref_kind=pull
else else
id=$1 id=$1
remote=${2:-origin} remote=${2:-origin}
fi fi
branch=mr/$id branch=mr/$id
remote_ref=refs/merge-requests/$id/head remote_ref=refs/$remote_ref_kind/$id/head
git fetch -fu "$remote" "$remote_ref:$branch" git fetch -fu "$remote" "$remote_ref:$branch"
git checkout "$branch" git checkout "$branch"
git config --local --replace "branch.$branch.merge" "$remote_ref" git config --local --replace "branch.$branch.merge" "$remote_ref"

View file

@ -77,6 +77,17 @@ function _sanitize()
function scp_and_stage function scp_and_stage
{ {
local verbose=0 interactive=0 dry_run=0
while :
do
case "$1" in
-v|--verbose) verbose=1; shift;;
-i|--interactive) verbose=1; interactive=1; shift;;
-n|--dry-run) verbose=1; dry_run=1; shift;;
*) break;;
esac
done
set_remote "$1" set_remote "$1"
shift shift
@ -94,46 +105,116 @@ function scp_and_stage
list=$(git ls-files "$@")" "$(git ls-files -o "$@") list=$(git ls-files "$@")" "$(git ls-files -o "$@")
elif [ -n "$refhead" ] elif [ -n "$refhead" ]
then then
git diff --stat "$refhead" [ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
list=$(git diff "$refhead" --name-only) list=$(git diff "$refhead" --name-only)
else else
git diff [ "$verbose" -eq 1 ] && git --no-pager diff
list=$(git diff --name-only) list=$(git diff --name-only)
fi fi
deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done) deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done)
list=$(for i in $list; do [ -f "$i" ] && echo "$i"; done) list=$(for i in $list; do [ -f "$i" ] && echo "$i"; done)
if [ "$interactive" -eq 1 ] && [ "$dry_run" -eq 0 ] && { [ -n "$list" ] || [ -n "$deleted" ]; }
then
local reply
read -r -p "Proceed with sync to $remote? [y/N] " reply
case "$reply" in
y|Y|yes|YES) ;;
*) _info "Aborted, nothing synced."; exit 0;;
esac
fi
local status=0
if [ -n "$list" ] if [ -n "$list" ]
then then
local _TMP=${0///} local _TMP=${0///}
# shellcheck disable=SC2086 # shellcheck disable=SC2086
echo "$list" > "$_TMP" && echo "$list" > "$_TMP"
_sanitize $list && if [ "$dry_run" -eq 1 ] || _sanitize $list
_info "Pushing to $remote ($(git config "remote.$remote.url"))" && then
rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" && _info "Pushing to $remote ($(git config "remote.$remote.url"))"
git add --force $list && if [ "$dry_run" -eq 1 ]
then
rsync -rlDvn --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/"
else
rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" &&
git add --force $list
fi
status=$?
else
status=1
fi
rm "$_TMP" rm "$_TMP"
fi fi
deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done) deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done)
[ -n "$deleted" ] && if [ -n "$deleted" ]
COLOR_RED && then
echo Deleted remote files && COLOR_RED
ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted" && echo Deleted remote files
echo "$deleted" if [ "$dry_run" -eq 1 ]
COLOR_RESET then
echo "$deleted"
else
if ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted"
then
echo "$deleted"
else
status=1
fi
fi
COLOR_RESET
fi
return "$status"
} }
function reverse_scp() function reverse_scp()
{ {
local verbose=0 interactive=0 dry_run=0
while :
do
case "$1" in
-v|--verbose) verbose=1; shift;;
-i|--interactive) verbose=1; interactive=1; shift;;
-n|--dry-run) verbose=1; dry_run=1; shift;;
*) break;;
esac
done
set_remote "$1" set_remote "$1"
shift shift
local _TMP=${0///} local _TMP=${0///}
echo "$@" > "$_TMP" && echo "$@" > "$_TMP"
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
local status=0
if [ "$verbose" -eq 1 ]
then
rsync -rlDvn --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./
status=$?
fi
if [ "$dry_run" -eq 1 ]
then
rm "$_TMP"
return "$status"
fi
if [ "$interactive" -eq 1 ]
then
local reply
read -r -p "Proceed with copy from $remote? [y/N] " reply
case "$reply" in
y|Y|yes|YES) ;;
*) _info "Aborted, nothing copied."; rm "$_TMP"; exit 0;;
esac
fi
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
rm "$_TMP" rm "$_TMP"
} }
@ -148,9 +229,14 @@ function _usage()
{ {
echo "Usage: echo "Usage:
git scp -h|help|? git scp -h|help|?
git scp <remote> [ref|file..] # scp and stage your files to specified remote git scp [options] <remote> [ref|file..] # scp and stage your files to specified remote
git scp <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote> git scp [options] <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
git rscp <remote> [<file|directory>] # copy <remote> files to current working directory git rscp [options] <remote> [<file|directory>] # copy <remote> files to current working directory
OPTIONS:
-v, --verbose print what will be synced before syncing
-i, --interactive prompt for confirmation before syncing (implies --verbose)
-n, --dry-run show what would be synced, change nothing (implies --verbose; no staging, no remote writes/deletes)
" "
case $1 in case $1 in

View file

@ -145,11 +145,11 @@ _git_graft(){
_git_ignore(){ _git_ignore(){
case "$cur" in case "$cur" in
--*) --*)
__gitcomp "--global --local --private" __gitcomp "--global --local --private --edit --remove"
return return
;; ;;
-*) -*)
__gitcomp "--global --local --private -g -l -p" __gitcomp "--global --local --private --edit --remove -g -l -p -e -r"
return return
;; ;;
esac esac

View file

@ -213,6 +213,11 @@ _git-delete-branch() {
__gitex_branch_names_unique __gitex_branch_names_unique
} }
_git-delete-gone-branches() {
_arguments \
'(-n --dry-run)'{-n,--dry-run}'[show branches that would be deleted without deleting]'
}
_git-delete-squashed-branches() { _git-delete-squashed-branches() {
_arguments \ _arguments \
':branch-name:__gitex_branch_names' ':branch-name:__gitex_branch_names'
@ -312,9 +317,11 @@ _git-guilt() {
_git-ignore() { _git-ignore() {
_arguments -C \ _arguments -C \
'(--local -l)'{--local,-l}'[show local gitignore]' \ '(--local -l)'{--local,-l}'[set context to local gitignore]' \
'(--global -g)'{--global,-g}'[show global gitignore]' \ '(--global -g)'{--global,-g}'[set context to global gitignore]' \
'(--private -p)'{--private,-p}'[show repo gitignore]' \ '(--private -p)'{--private,-p}'[set context to private gitignore]' \
'(--edit -e)'{--edit,-e}'[set action to edit]' \
'(--remove -r)'{--remove,-r}'[set action to remove]' \
'*:filename:_files' '*:filename:_files'
} }
@ -421,6 +428,7 @@ zstyle ':completion:*:*:git:*' user-commands $existing_user_commands \
count:'show commit count' \ count:'show commit count' \
create-branch:'create branches' \ create-branch:'create branches' \
delete-branch:'delete branches' \ delete-branch:'delete branches' \
delete-gone-branches:'delete branches whose remote is gone' \
delete-merged-branches:'delete merged branches' \ delete-merged-branches:'delete merged branches' \
delete-squashed-branches:'delete squashed branches' \ delete-squashed-branches:'delete squashed branches' \
delete-submodule:'delete submodules' \ delete-submodule:'delete submodules' \
@ -437,7 +445,7 @@ zstyle ':completion:*:*:git:*' user-commands $existing_user_commands \
graft:'merge and destroy a given branch' \ graft:'merge and destroy a given branch' \
guilt:'calculate change between two revisions' \ guilt:'calculate change between two revisions' \
ignore-io:'get sample gitignore file' \ ignore-io:'get sample gitignore file' \
ignore:'add .gitignore patterns' \ ignore:'Modify or display .gitignore files' \
info:'returns information on current repository' \ info:'returns information on current repository' \
local-commits:'list local commits' \ local-commits:'list local commits' \
lock:'lock a file excluded from version control' \ lock:'lock a file excluded from version control' \

View file

@ -32,7 +32,7 @@ set __fish_git_extras_commands \
"graft:Merge and destroy a given branch" \ "graft:Merge and destroy a given branch" \
"guilt:calculate change between two revisions" \ "guilt:calculate change between two revisions" \
"ignore-io:Get sample gitignore file" \ "ignore-io:Get sample gitignore file" \
"ignore:Add .gitignore patterns" \ "ignore:Modify or display .gitignore files" \
"info:Returns information on current repository" \ "info:Returns information on current repository" \
"local-commits:List local commits" \ "local-commits:List local commits" \
"lock:Lock a file excluded from version control" \ "lock:Lock a file excluded from version control" \
@ -162,9 +162,11 @@ complete -c git -f -n '__fish_git_using_command guilt' -s e -l email -d 'display
complete -c git -f -n '__fish_git_using_command guilt' -s d -l debug -d 'output debug information' complete -c git -f -n '__fish_git_using_command guilt' -s d -l debug -d 'output debug information'
complete -c git -f -n '__fish_git_using_command guilt' -s h -d 'output usage information' complete -c git -f -n '__fish_git_using_command guilt' -s h -d 'output usage information'
# ignore # ignore
complete -c git -f -n '__fish_git_using_command ignore' -s l -l local -d 'show local gitignore' complete -c git -f -n '__fish_git_using_command ignore' -s l -l local -d 'set context to local gitignore'
complete -c git -f -n '__fish_git_using_command ignore' -s g -l global -d 'show global gitignore' complete -c git -f -n '__fish_git_using_command ignore' -s g -l global -d 'set context to global gitignore'
complete -c git -f -n '__fish_git_using_command ignore' -s p -l private -d 'show repo gitignore' complete -c git -f -n '__fish_git_using_command ignore' -s p -l private -d 'set context to private gitignore'
complete -c git -f -n '__fish_git_using_command ignore' -s e -l edit -d 'set action to edit'
complete -c git -f -n '__fish_git_using_command ignore' -s r -l remove -d 'set action to remove'
# ignore-io # ignore-io
function __fish_git_extra_get_ignore_io_types function __fish_git_extra_get_ignore_io_types
# we will first remove every tab spaces, and then append `\t` at the end to remove the default description # we will first remove every tab spaces, and then append `\t` at the end to remove the default description

View file

@ -3,7 +3,9 @@
# #
is_git_repo() { is_git_repo() {
git rev-parse --show-toplevel > /dev/null 2>&1 # --git-dir succeeds for both normal and bare repositories, whereas
# --show-toplevel fails inside a bare repository (it has no working tree).
git rev-parse --git-dir > /dev/null 2>&1
result=$? result=$?
if test $result != 0; then if test $result != 0; then
>&2 echo 'Not a git repo!' >&2 echo 'Not a git repo!'

View file

@ -39,13 +39,34 @@ goto :defaultpath
rem remove the last slash rem remove the last slash
SET bindir=%bindir:~0,-1% SET bindir=%bindir:~0,-1%
for %%G in ("%bindir%") do set installdir=%%~dpG for %%G in ("%bindir%") do set installdir=%%~dpG
set PREFIX=%installdir%mingw64 rem strip a trailing slash so we can inspect the leaf directory name
if "%installdir:~-1%"=="\" set installdir=%installdir:~0,-1%
rem git.exe found under <GitRoot>\mingw64\bin or <GitRoot>\clangarm64\bin
rem means installdir already is the architecture directory itself
for %%G in ("%installdir%") do set archdir=%%~nxG
if /I "%archdir%"=="mingw64" (
set PREFIX=%installdir%
) else if /I "%archdir%"=="clangarm64" (
set PREFIX=%installdir%
) else if exist "%installdir%\mingw64" (
set PREFIX=%installdir%\mingw64
) else if exist "%installdir%\clangarm64" (
set PREFIX=%installdir%\clangarm64
) else (
set PREFIX=%installdir%\mingw64
)
goto :foundprefix goto :foundprefix
:defaultpath :defaultpath
:: default for Git for Windows 2.x :: default for Git for Windows 2.x
if exist "%ProgramFiles%\Git" ( if exist "%ProgramFiles%\Git" (
set PREFIX=%ProgramFiles%\Git\mingw64 if exist "%ProgramFiles%\Git\mingw64" (
set PREFIX=%ProgramFiles%\Git\mingw64
) else if exist "%ProgramFiles%\Git\clangarm64" (
set PREFIX=%ProgramFiles%\Git\clangarm64
) else (
set PREFIX=%ProgramFiles%\Git\mingw64
)
) )
:foundprefix :foundprefix
@ -55,9 +76,11 @@ IF NOT "%~1"=="" (
REM just supplying the git dir is enough... REM just supplying the git dir is enough...
if exist "%~1\mingw64" ( if exist "%~1\mingw64" (
set PREFIX=%~1\mingw64 set PREFIX=%~1\mingw64
) else if exist "%~1\clangarm64" (
set PREFIX=%~1\clangarm64
) else ( ) else (
echo Using git install path "%~1" as PREFIX, please make sure it's really a echo Using git install path "%~1" as PREFIX, please make sure it's really a
echo path to the mingw64 directory... echo path to the mingw64 or clangarm64 directory...
echo. echo.
SET PREFIX=%~1 SET PREFIX=%~1
) )
@ -72,12 +95,14 @@ set GIT_INSTALL_DIR=!GIT_INSTALL_DIR:"=!
IF %GIT_INSTALL_DIR:~-1%==\ SET GIT_INSTALL_DIR=%GIT_INSTALL_DIR:~0,-1% IF %GIT_INSTALL_DIR:~-1%==\ SET GIT_INSTALL_DIR=%GIT_INSTALL_DIR:~0,-1%
if not exist "%GIT_INSTALL_DIR%\mingw64" ( if not exist "%GIT_INSTALL_DIR%\mingw64" (
echo No mingw64 folder found in %GIT_INSTALL_DIR%. if not exist "%GIT_INSTALL_DIR%\clangarm64" (
echo. echo No mingw64 or clangarm64 folder found in %GIT_INSTALL_DIR%.
echo Please supply a proper "Git for Windows 2.x" install path: echo.
echo "install.cmd c:\[git-install-path]" echo Please supply a proper "Git for Windows 2.x" install path:
set ERROR=1 echo "install.cmd c:\[git-install-path]"
goto :exit set ERROR=1
goto :exit
)
) )
echo Installing to %PREFIX% echo Installing to %PREFIX%
@ -116,7 +141,7 @@ FOR /R "%GITEXTRAS%\bin" %%i in (*.*) DO (
TYPE "%GITEXTRAS%\helper\reset-env" >> "%PREFIX%\bin\%%~ni" TYPE "%GITEXTRAS%\helper\reset-env" >> "%PREFIX%\bin\%%~ni"
TYPE "%GITEXTRAS%\helper\git-extra-utility" >> "%PREFIX%\bin\%%~ni" TYPE "%GITEXTRAS%\helper\git-extra-utility" >> "%PREFIX%\bin\%%~ni"
TYPE "%GITEXTRAS%\helper\is-git-repo" >> "%PREFIX%\bin\%%~ni" TYPE "%GITEXTRAS%\helper\is-git-repo" >> "%PREFIX%\bin\%%~ni"
REM Added /E option for installation fix on Windows 10.0.17134 and higher REM Added /E option for installation fix on Windows 10.0.17134 and higher
MORE /E +2 "%GITEXTRAS%\bin\%%~ni" >> "%PREFIX%\bin\%%~ni" MORE /E +2 "%GITEXTRAS%\bin\%%~ni" >> "%PREFIX%\bin\%%~ni"
) )
@ -127,7 +152,7 @@ FOR %%i in (%COMMANDS_WITHOUT_REPO%) DO (
ECHO #^^!/usr/bin/env bash > "%PREFIX%\bin\%%i" ECHO #^^!/usr/bin/env bash > "%PREFIX%\bin\%%i"
TYPE "%GITEXTRAS%\helper\reset-env" >> "%PREFIX%\bin\%%i" TYPE "%GITEXTRAS%\helper\reset-env" >> "%PREFIX%\bin\%%i"
TYPE "%GITEXTRAS%\helper\git-extra-utility" >> "%PREFIX%\bin\%%i" TYPE "%GITEXTRAS%\helper\git-extra-utility" >> "%PREFIX%\bin\%%i"
REM Added /E option for installation fix on Windows 10.0.17134 and higher REM Added /E option for installation fix on Windows 10.0.17134 and higher
MORE /E +2 "%GITEXTRAS%\bin\%%i" >> "%PREFIX%\bin\%%i" MORE /E +2 "%GITEXTRAS%\bin\%%i" >> "%PREFIX%\bin\%%i"
) )

View file

@ -0,0 +1,54 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "GIT\-DELETE\-GONE\-BRANCHES" "1" "March 2026" "" "Git Extras"
.
.SH "NAME"
\fBgit\-delete\-gone\-branches\fR \- Delete branches whose remote is gone
.
.SH "SYNOPSIS"
\fBgit\-delete\-gone\-branches\fR [\-n|\-\-dry\-run] [\-f|\-\-force] [\-p|\-\-prune]
.
.SH "DESCRIPTION"
Deletes all local branches whose remote\-tracking branch has been deleted\. This commonly happens after a pull request is merged and the remote branch is removed\. By default, lists the branches and prompts for confirmation before deleting\.
.
.SH "OPTIONS"
\-n, \-\-dry\-run
.
.P
Show the branches that would be deleted without actually deleting them\.
.
.P
\-f, \-\-force
.
.P
Skip the confirmation prompt and delete branches immediately\.
.
.P
\-p, \-\-prune
.
.P
Run \fBgit fetch \-\-prune\fR before checking for gone branches, to ensure remote\-tracking refs are up to date\.
.
.SH "EXAMPLES"
.
.nf
$ git delete\-gone\-branches
$ git delete\-gone\-branches \-\-dry\-run
$ git delete\-gone\-branches \-\-force
$ git delete\-gone\-branches \-\-prune
.
.fi
.
.SH "AUTHOR"
Written by Utsav Sabharwal <\fIhttps://github\.com/codersofthedark\fR>
.
.SH "REPORTING BUGS"
<\fIhttps://github\.com/tj/git\-extras/issues\fR>
.
.SH "SEE ALSO"
<\fIhttps://github\.com/tj/git\-extras\fR>

View file

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' value='text/html;charset=utf8'>
<meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
<title>git-delete-gone-branches(1) - Delete branches whose remote is gone</title>
<style type='text/css' media='all'>
/* style: man */
body#manpage {margin:0}
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
.mp h2 {margin:10px 0 0 0}
.mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
.mp h3 {margin:0 0 0 4ex}
.mp dt {margin:0;clear:left}
.mp dt.flush {float:left;width:8ex}
.mp dd {margin:0 0 0 9ex}
.mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
.mp pre {margin-bottom:20px}
.mp pre+h2,.mp pre+h3 {margin-top:22px}
.mp h2+pre,.mp h3+pre {margin-top:5px}
.mp img {display:block;margin:auto}
.mp h1.man-title {display:none}
.mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
.mp h2 {font-size:16px;line-height:1.25}
.mp h1 {font-size:20px;line-height:2}
.mp {text-align:justify;background:#fff}
.mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
.mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
.mp u {text-decoration:underline}
.mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
.mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
.mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
.mp b.man-ref {font-weight:normal;color:#434241}
.mp pre {padding:0 4ex}
.mp pre code {font-weight:normal;color:#434241}
.mp h2+pre,h3+pre {padding-left:0}
ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
ol.man-decor {width:100%}
ol.man-decor li.tl {text-align:left}
ol.man-decor li.tc {text-align:center;letter-spacing:4px}
ol.man-decor li.tr {text-align:right;float:right}
</style>
</head>
<!--
The following styles are deprecated and will be removed at some point:
div#man, div#man ol.man, div#man ol.head, div#man ol.man.
The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
.man-navigation should be used instead.
-->
<body id='manpage'>
<div class='mp' id='man'>
<div class='man-navigation' style='display:none'>
<a href="#NAME">NAME</a>
<a href="#SYNOPSIS">SYNOPSIS</a>
<a href="#DESCRIPTION">DESCRIPTION</a>
<a href="#OPTIONS">OPTIONS</a>
<a href="#EXAMPLES">EXAMPLES</a>
<a href="#AUTHOR">AUTHOR</a>
<a href="#REPORTING-BUGS">REPORTING BUGS</a>
<a href="#SEE-ALSO">SEE ALSO</a>
</div>
<ol class='man-decor man-head man head'>
<li class='tl'>git-delete-gone-branches(1)</li>
<li class='tc'>Git Extras</li>
<li class='tr'>git-delete-gone-branches(1)</li>
</ol>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>git-delete-gone-branches</code> - <span class="man-whatis">Delete branches whose remote is gone</span>
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>git-delete-gone-branches</code> [-n|--dry-run] [-f|--force] [-p|--prune]</p>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<p> Deletes all local branches whose remote-tracking branch has been deleted.
This commonly happens after a pull request is merged and the remote branch
is removed. By default, lists the branches and prompts for confirmation
before deleting.</p>
<h2 id="OPTIONS">OPTIONS</h2>
<p> -n, --dry-run</p>
<p> Show the branches that would be deleted without actually deleting them.</p>
<p> -f, --force</p>
<p> Skip the confirmation prompt and delete branches immediately.</p>
<p> -p, --prune</p>
<p> Run <code>git fetch --prune</code> before checking for gone branches, to ensure
remote-tracking refs are up to date.</p>
<h2 id="EXAMPLES">EXAMPLES</h2>
<pre><code>$ git delete-gone-branches
$ git delete-gone-branches --dry-run
$ git delete-gone-branches --force
$ git delete-gone-branches --prune
</code></pre>
<h2 id="AUTHOR">AUTHOR</h2>
<p>Written by Utsav Sabharwal &lt;<a href="&#x6d;&#97;&#105;&#x6c;&#x74;&#111;&#x3a;&#117;&#116;&#115;&#97;&#x76;&#x2e;&#115;&#x61;&#x62;&#x68;&#x61;&#x72;&#x77;&#x61;&#108;&#64;&#115;&#x79;&#x73;&#x64;&#105;&#x67;&#x2e;&#99;&#111;&#x6d;" data-bare-link="true">&#117;&#116;&#115;&#x61;&#x76;&#x2e;&#x73;&#x61;&#x62;&#104;&#97;&#x72;&#x77;&#97;&#108;&#64;&#x73;&#121;&#115;&#x64;&#105;&#103;&#46;&#x63;&#x6f;&#109;</a>&gt;</p>
<h2 id="REPORTING-BUGS">REPORTING BUGS</h2>
<p>&lt;<a href="https://github.com/tj/git-extras/issues" data-bare-link="true">https://github.com/tj/git-extras/issues</a>&gt;</p>
<h2 id="SEE-ALSO">SEE ALSO</h2>
<p>&lt;<a href="https://github.com/tj/git-extras" data-bare-link="true">https://github.com/tj/git-extras</a>&gt;</p>
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>March 2026</li>
<li class='tr'>git-delete-gone-branches(1)</li>
</ol>
</div>
</body>
</html>

View file

@ -0,0 +1,50 @@
git-delete-gone-branches(1) -- Delete branches whose remote is gone
====================================================================
## SYNOPSIS
`git-delete-gone-branches` [-n|--dry-run] [-f|--force] [-p|--prune]
## DESCRIPTION
Deletes all local branches whose remote-tracking branch has been deleted.
This commonly happens after a pull request is merged and the remote branch
is removed. By default, lists the branches and prompts for confirmation
before deleting.
## OPTIONS
-n, --dry-run
Show the branches that would be deleted without actually deleting them.
-f, --force
Skip the confirmation prompt and delete branches immediately.
-p, --prune
Run `git fetch --prune` before checking for gone branches, to ensure
remote-tracking refs are up to date.
## EXAMPLES
$ git delete-gone-branches
$ git delete-gone-branches --dry-run
$ git delete-gone-branches --force
$ git delete-gone-branches --prune
## AUTHOR
Written by Utsav Sabharwal &lt;<https://github.com/codersofthedark>&gt;
## REPORTING BUGS
&lt;<https://github.com/tj/git-extras/issues>&gt;
## SEE ALSO
&lt;<https://github.com/tj/git-extras>&gt;

View file

@ -0,0 +1,183 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
.TH "GIT\-EXTRAS" "1" "May 2026" "" "Git Extras"
.SH "NAME"
\fBgit\-extras\fR \- Awesome GIT utilities
.SH "SYNOPSIS"
\fBgit\-extras\fR [\-v,\-\-version] [\-h,\-\-help] [update]
.SH "OPTIONS"
\-v, \-\-version
.P
Show git\-extras version number\.
.P
\-h, \-\-help
.P
Show this help\. This option can also be used for any of the extras commands\.
.P
update
.P
Self update\.
.SH "ENVIRONMENT AND CONFIGURATION VARIABLES"
\fBgit config \-\-add git\-extras\.default\-branch $BRANCH\fR
.P
Change the default branch to \fB$BRANCH\fR\. If \fBgit\-extras\.default\-branch\fR isn't set, \fBinit\.defaultBranch\fR is used instead\. If none of them are set it defaults to \fBmain\fR\.
.SH "COMMANDS"
.IP "\(bu" 4
\fBgit\-abort(1)\fR Abort current git operation
.IP "\(bu" 4
\fBgit\-alias(1)\fR Define, search and show aliases
.IP "\(bu" 4
\fBgit\-archive\-file(1)\fR Export the current HEAD of the git repository to an archive
.IP "\(bu" 4
\fBgit\-authors(1)\fR Generate authors report
.IP "\(bu" 4
\fBgit\-browse\-ci(1)\fR View the CI page for the current repository
.IP "\(bu" 4
\fBgit\-browse(1)\fR View the web page for the current repository
.IP "\(bu" 4
\fBgit\-brv(1)\fR List branches sorted by their last commit date
.IP "\(bu" 4
\fBgit\-bulk(1)\fR Run git commands on multiple repositories
.IP "\(bu" 4
\fBgit\-changelog(1)\fR Generate a changelog report
.IP "\(bu" 4
\fBgit\-clear\-soft(1)\fR Soft clean up a repository
.IP "\(bu" 4
\fBgit\-clear(1)\fR Rigorously clean up a repository
.IP "\(bu" 4
\fBgit\-coauthor(1)\fR Add a co\-author to the last commit
.IP "\(bu" 4
\fBgit\-commits\-since(1)\fR Show commit logs since some date
.IP "\(bu" 4
\fBgit\-continue(1)\fR Continue current git operation
.IP "\(bu" 4
\fBgit\-contrib(1)\fR Show user's contributions
.IP "\(bu" 4
\fBgit\-count(1)\fR Show commit count
.IP "\(bu" 4
\fBgit\-cp(1)\fR Copy a file keeping its history
.IP "\(bu" 4
\fBgit\-create\-branch(1)\fR Create branches
.IP "\(bu" 4
\fBgit\-delete\-branch(1)\fR Delete branches
.IP "\(bu" 4
\fBgit\-delete\-merged\-branches(1)\fR Delete merged branches
.IP "\(bu" 4
\fBgit\-delete\-squashed\-branches(1)\fR Delete branches that were squashed
.IP "\(bu" 4
\fBgit\-delete\-submodule(1)\fR Delete submodules
.IP "\(bu" 4
\fBgit\-delete\-tag(1)\fR Delete tags
.IP "\(bu" 4
\fBgit\-delta(1)\fR Lists changed files
.IP "\(bu" 4
\fBgit\-effort(1)\fR Show effort statistics on file(s)
.IP "\(bu" 4
\fBgit\-feature(1)\fR Create/Merge feature branch
.IP "\(bu" 4
\fBgit\-force\-clone(1)\fR overwrite local repositories with clone
.IP "\(bu" 4
\fBgit\-fork(1)\fR Fork a repo on github
.IP "\(bu" 4
\fBgit\-fresh\-branch(1)\fR Create fresh branches
.IP "\(bu" 4
\fBgit\-get(1)\fR Clone a Git repository under a configured directory
.IP "\(bu" 4
\fBgit\-gh\-pages(1)\fR Create the GitHub Pages branch
.IP "\(bu" 4
\fBgit\-graft(1)\fR Merge and destroy a given branch
.IP "\(bu" 4
\fBgit\-guilt(1)\fR calculate change between two revisions
.IP "\(bu" 4
\fBgit\-ignore\-io(1)\fR Get sample gitignore file
.IP "\(bu" 4
\fBgit\-ignore(1)\fR Add \.gitignore patterns
.IP "\(bu" 4
\fBgit\-info(1)\fR Returns information on current repository
.IP "\(bu" 4
\fBgit\-local\-commits(1)\fR List local commits
.IP "\(bu" 4
\fBgit\-lock(1)\fR Lock a file excluded from version control
.IP "\(bu" 4
\fBgit\-locked(1)\fR ls files that have been locked
.IP "\(bu" 4
\fBgit\-magic(1)\fR Automate add/commit/push routines
.IP "\(bu" 4
\fBgit\-merge\-into(1)\fR Merge one branch into another
.IP "\(bu" 4
\fBgit\-merge\-repo(1)\fR Merge two repo histories
.IP "\(bu" 4
\fBgit\-missing(1)\fR Show commits missing from another branch
.IP "\(bu" 4
\fBgit\-mr(1)\fR Checks out a merge request locally
.IP "\(bu" 4
\fBgit\-obliterate(1)\fR rewrite past commits to remove some files
.IP "\(bu" 4
\fBgit\-paste(1)\fR Send patches to pastebin for chat conversations
.IP "\(bu" 4
\fBgit\-pr(1)\fR Checks out a pull request locally
.IP "\(bu" 4
\fBgit\-psykorebase(1)\fR Rebase a branch with a merge commit
.IP "\(bu" 4
\fBgit\-pull\-request(1)\fR Create pull request for GitHub project
.IP "\(bu" 4
\fBgit\-reauthor(1)\fR Rewrite history to change author's identity
.IP "\(bu" 4
\fBgit\-rebase\-patch(1)\fR Rebases a patch
.IP "\(bu" 4
\fBgit\-release(1)\fR Commit, tag and push changes to the repository
.IP "\(bu" 4
\fBgit\-rename\-branch(1)\fR rename local branch and push to remote
.IP "\(bu" 4
\fBgit\-rename\-file(1)\fR Rename a file or directory and ensure Git recognizes the change, regardless of filesystem case\-sensitivity\.
.IP "\(bu" 4
\fBgit\-rename\-remote(1)\fR Rename a remote
.IP "\(bu" 4
\fBgit\-rename\-tag(1)\fR Rename a tag
.IP "\(bu" 4
\fBgit\-repl(1)\fR git read\-eval\-print\-loop
.IP "\(bu" 4
\fBgit\-reset\-file(1)\fR Reset one file
.IP "\(bu" 4
\fBgit\-root(1)\fR show path of root
.IP "\(bu" 4
\fBgit\-scp(1)\fR Copy files to SSH compatible \fBgit\-remote\fR
.IP "\(bu" 4
\fBgit\-sed(1)\fR replace patterns in git\-controlled files
.IP "\(bu" 4
\fBgit\-setup(1)\fR Set up a git repository
.IP "\(bu" 4
\fBgit\-show\-merged\-branches(1)\fR Show merged branches
.IP "\(bu" 4
\fBgit\-show\-tree(1)\fR show branch tree of commit history
.IP "\(bu" 4
\fBgit\-show\-unmerged\-branches(1)\fR Show unmerged branches
.IP "\(bu" 4
\fBgit\-squash(1)\fR squash N last changes up to a ref'ed commit
.IP "\(bu" 4
\fBgit\-stamp(1)\fR Stamp the last commit message
.IP "\(bu" 4
\fBgit\-standup(1)\fR Recall the commit history
.IP "\(bu" 4
\fBgit\-summary(1)\fR Show repository summary
.IP "\(bu" 4
\fBgit\-sync(1)\fR Sync local branch with remote branch
.IP "\(bu" 4
\fBgit\-touch(1)\fR Touch and add file to the index
.IP "\(bu" 4
\fBgit\-undo(1)\fR Remove latest commits
.IP "\(bu" 4
\fBgit\-unlock(1)\fR Unlock a file excluded from version control
.IP "\(bu" 4
\fBgit\-unwip(1)\fR Undo a Work In Progress commit
.IP "\(bu" 4
\fBgit\-utimes(1)\fR Change files modification time to their last commit date
.IP "\(bu" 4
\fBgit\-wip(1)\fR Create a Work In Progress commit
.IP "" 0
.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>

View file

@ -111,11 +111,9 @@
<li> <li>
<strong><a class="man-ref" href="git-authors.html">git-authors<span class="s">(1)</span></a></strong> Generate authors report</li> <strong><a class="man-ref" href="git-authors.html">git-authors<span class="s">(1)</span></a></strong> Generate authors report</li>
<li> <li>
<strong><a class="man-ref" href="git-browse-ci.html">git-browse-ci<span class="s">(1)</span></a></strong> <var>View the web page for the current repository</var> <strong><a class="man-ref" href="git-browse-ci.html">git-browse-ci<span class="s">(1)</span></a></strong> View the CI page for the current repository</li>
</li>
<li> <li>
<strong><a class="man-ref" href="git-browse.html">git-browse<span class="s">(1)</span></a></strong> <var>View the web page for the current repository</var> <strong><a class="man-ref" href="git-browse.html">git-browse<span class="s">(1)</span></a></strong> View the web page for the current repository</li>
</li>
<li> <li>
<strong><a class="man-ref" href="git-brv.html">git-brv<span class="s">(1)</span></a></strong> List branches sorted by their last commit date</li> <strong><a class="man-ref" href="git-brv.html">git-brv<span class="s">(1)</span></a></strong> List branches sorted by their last commit date</li>
<li> <li>
@ -273,7 +271,7 @@
<ol class='man-decor man-foot man foot'> <ol class='man-decor man-foot man foot'>
<li class='tl'></li> <li class='tl'></li>
<li class='tc'>February 2026</li> <li class='tc'>May 2026</li>
<li class='tr'>git-extras(1)</li> <li class='tr'>git-extras(1)</li>
</ol> </ol>

View file

@ -31,8 +31,8 @@ git-extras(1) -- Awesome GIT utilities
- **git-alias(1)** Define, search and show aliases - **git-alias(1)** Define, search and show aliases
- **git-archive-file(1)** Export the current HEAD of the git repository to an archive - **git-archive-file(1)** Export the current HEAD of the git repository to an archive
- **git-authors(1)** Generate authors report - **git-authors(1)** Generate authors report
- **git-browse-ci(1)** <View the web page for the current repository> - **git-browse-ci(1)** View the CI page for the current repository
- **git-browse(1)** <View the web page for the current repository> - **git-browse(1)** View the web page for the current repository
- **git-brv(1)** List branches sorted by their last commit date - **git-brv(1)** List branches sorted by their last commit date
- **git-bulk(1)** Run git commands on multiple repositories - **git-bulk(1)** Run git commands on multiple repositories
- **git-changelog(1)** Generate a changelog report - **git-changelog(1)** Generate a changelog report
@ -46,6 +46,7 @@ git-extras(1) -- Awesome GIT utilities
- **git-cp(1)** Copy a file keeping its history - **git-cp(1)** Copy a file keeping its history
- **git-create-branch(1)** Create branches - **git-create-branch(1)** Create branches
- **git-delete-branch(1)** Delete branches - **git-delete-branch(1)** Delete branches
- **git-delete-gone-branches(1)** Delete branches whose remote is gone
- **git-delete-merged-branches(1)** Delete merged branches - **git-delete-merged-branches(1)** Delete merged branches
- **git-delete-squashed-branches(1)** Delete branches that were squashed - **git-delete-squashed-branches(1)** Delete branches that were squashed
- **git-delete-submodule(1)** Delete submodules - **git-delete-submodule(1)** Delete submodules
@ -61,7 +62,7 @@ git-extras(1) -- Awesome GIT utilities
- **git-graft(1)** Merge and destroy a given branch - **git-graft(1)** Merge and destroy a given branch
- **git-guilt(1)** calculate change between two revisions - **git-guilt(1)** calculate change between two revisions
- **git-ignore-io(1)** Get sample gitignore file - **git-ignore-io(1)** Get sample gitignore file
- **git-ignore(1)** Add .gitignore patterns - **git-ignore(1)** Modify or display .gitignore files
- **git-info(1)** Returns information on current repository - **git-info(1)** Returns information on current repository
- **git-local-commits(1)** List local commits - **git-local-commits(1)** List local commits
- **git-lock(1)** Lock a file excluded from version control - **git-lock(1)** Lock a file excluded from version control

View file

@ -1,77 +1,88 @@
.\" generated with Ronn/v0.7.3 .\" generated with Ronn-NG/v0.10.1
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1
. .TH "GIT\-IGNORE" "1" "June 2026" "" "Git Extras"
.TH "GIT\-IGNORE" "1" "April 2018" "" "Git Extras"
.
.SH "NAME" .SH "NAME"
\fBgit\-ignore\fR \- Add \.gitignore patterns \fBgit\-ignore\fR \- Modify or display \.gitignore files
.
.SH "SYNOPSIS" .SH "SYNOPSIS"
\fBgit\-ignore\fR [<context>] [<pattern> [<pattern>]\.\.\.] \fBgit\-ignore\fR [<context>] [<action>] [\-\-] [<pattern> [<pattern>]\|\.\|\.\|\.]
.
.SH "DESCRIPTION" .SH "DESCRIPTION"
Adds the given _pattern_s to a \.gitignore file if it doesn\'t already exist\. Modifies or shows \.gitignore of selected \fIcontext\fR\.
. .P
Possible actions are: addition of new \fIpattern\fRs, removal of existing \fIpattern\fRs from gitignore files, opening these files in text editor or simply displaying their contents in current terminal (for details see flags/usage)\.
.P
Contexts are global (e\.g\. $HOME/\.gitignore or $HOME/\.local/\.git/\.gitignore), private (e\.g\. \|\.\|\.\|\./project/\.git/info/exclude) and local (e\.g\. \|\.\|\.\|\./project/\.gitignore)
.P
Adding only happens for \fIpattern\fRs that don't already exist in the file; removing \fIpattern\fRs that are not present does not do anything; editing fails if the file is not yet present\.
.P
At most 1 \fIcontext\fR and 1 \fIaction\fR matter for the program, the ones selected are the latest encountered in the flag list\.
.P
Default behavior:
.IP "\(bu" 4
If no flags or arguments are passed, prints gitignores of all 3 \fIcontext\fRs\.
.IP "\(bu" 4
Passing arguments with no \fIaction\fR implies addition of \fIpattern\fRs to gitignore\.
.IP "\(bu" 4
If an \fIaction\fR is chosen, but not \fIcontext\fR, \fIaction\fR is performed on local gitignore\.
.IP "\(bu" 4
If \fIcontext\fR is chosen but not action (not even implied), gitignore of chosen \fIcontext\fR is printed\.
.IP "" 0
.SH "OPTIONS" .SH "OPTIONS"
<context> <context>
.
.P .P
\-l, \-\-local \-l, \-\-local
.
.P .P
Sets the context to the \.gitignore file in the current working directory\. (default) Sets the context to the \.gitignore file in the current working directory\. (default)
.
.P .P
\-g, \-\-global \-g, \-\-global
.
.P .P
Sets the context to the global gitignore file for the current user\. Sets the context to the global gitignore file for the current user\.
.
.P .P
\-p, \-\-private \-p, \-\-private
.
.P .P
Sets the context to the private exclude file for the repository (\fB\.git/info/exclude\fR)\. Sets the context to the private exclude file for the repository (\fB\.git/info/exclude\fR)\.
. .P
<action>
.P
\-e, \-\-edit
.P
Sets action to edit\. This will open gitignore in a text editor (editor selection is the same as for git commit)\. If no \fIcontext\fR is provided local is assumed\. Any \fIpattern\fR; provided in additional arguments is silently omitted\.
.P
\-r, \-\-remove
.P
Sets action to remove\. This will look for \fIpattern\fRs in gitignore of selected \fIcontext\fR and remove if finds them\. If no \fIpattern\fR is provided does nothing\. All characters in \fIpattern\fR are treated literally, no interpretation of metacharacters is performed as would be, for example, for a regex pattern\.
.P .P
<pattern> <pattern>
.
.P .P
A space delimited list of patterns to append to the file in context\. A space delimited list of patterns to append to the file in context\.
. .P
\-\-
.P
End of options delimeter\. Everything to the right of it is treated as positional arguments or more precisely a \fIpattern\fR\.
.P
\-h, \-\-help
.P
Print a brief usage summary\.
.SS "PATTERN FORMAT" .SS "PATTERN FORMAT"
Pattern format as described in the git manual Pattern format as described in the git manual
.
.IP "\(bu" 4 .IP "\(bu" 4
A blank line matches no files, so it can serve as a separator for readability\. To append a blank line use empty quotes ""\. A blank line matches no files, so it can serve as a separator for readability\. To append a blank line use empty quotes ""\.
.
.IP "\(bu" 4 .IP "\(bu" 4
A line starting with # serves as a comment\. For example, "# This is a comment" A line starting with # serves as a comment\. For example, "# This is a comment"
.
.IP "\(bu" 4 .IP "\(bu" 4
An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again\. If a negated pattern matches, this will override lower precedence patterns sources\. To use an exclamation ! as command line argument it is best placed between single quotes \'\'\. For example, \'!src\' An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again\. If a negated pattern matches, this will override lower precedence patterns sources\. To use an exclamation ! as command line argument it is best placed between single quotes ''\. For example, '!src'
.
.IP "\(bu" 4 .IP "\(bu" 4
If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory\. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in git)\. If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory\. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in git)\.
.
.IP "\(bu" 4 .IP "\(bu" 4
If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the \.gitignore file (relative to the top level of the work tree if not from a \.gitignore file)\. If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the \.gitignore file (relative to the top level of the work tree if not from a \.gitignore file)\.
.
.IP "\(bu" 4 .IP "\(bu" 4
Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname\. For example, "Documentation/*\.html" matches "Documentation/git\.html" but not "Documentation/ppc/ppc\.html" or "tools/perf/Documentation/perf\.html"\. Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname\. For example, "Documentation/*\.html" matches "Documentation/git\.html" but not "Documentation/ppc/ppc\.html" or "tools/perf/Documentation/perf\.html"\.
.
.IP "\(bu" 4 .IP "\(bu" 4
A leading slash matches the beginning of the pathname\. For example, "/*\.c" matches "cat\-file\.c" but not "mozilla\-sha1/sha1\.c"\. A leading slash matches the beginning of the pathname\. For example, "/*\.c" matches "cat\-file\.c" but not "mozilla\-sha1/sha1\.c"\.
.
.IP "" 0 .IP "" 0
.
.SH "EXAMPLES" .SH "EXAMPLES"
All arguments are optional so calling git\-ignore alone will display first the global then the local gitignore files: All arguments are optional so calling git\-ignore alone will display global, local and private gitignore files in order:
.
.IP "" 4 .IP "" 4
.
.nf .nf
$ git ignore $ git ignore
Global gitignore: /home/alice/\.gitignore Global gitignore: /home/alice/\.gitignore
# Numerous always\-ignore extensions # Numerous always\-ignore extensions
@ -86,92 +97,101 @@ Global gitignore: /home/alice/\.gitignore
*\.sass\-cache *\.sass\-cache
# OS or Editor folders # OS or Editor folders
\.DS_Store \&\.DS_Store
\.Trashes \&\.Trashes
\._* \&\._*
Thumbs\.db Thumbs\.db
\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
Local gitignore: \.gitignore Local gitignore: \.gitignore
\.cache \&\.cache
\.project \&\.project
\.settings \&\.settings
\.tmproj \&\.tmproj
nbproject nbproject
. \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
Private gitignore: \.git/info/exclude
notes\.txt
test\.sh
\&\.env\.local
config\.json
.fi .fi
.
.IP "" 0 .IP "" 0
.
.P .P
If you only want to see the global context use the \-\-global argument (for local use \-\-local): If you only want to see the global context use the \-\-global argument (for local use \-\-local, for private use \-\-private):
.
.IP "" 4 .IP "" 4
.
.nf .nf
$ git ignore $ git ignore
Global gitignore: /home/alice/\.gitignore Global gitignore: /home/alice/\.gitignore
\.DS_Store \&\.DS_Store
\.Trashes \&\.Trashes
\._* \&\._*
Thumbs\.db Thumbs\.db
.
.fi .fi
.
.IP "" 0 .IP "" 0
.
.P .P
To quickly append a new pattern to the default/local context simply: To quickly append a new pattern to the default/local context simply:
.
.IP "" 4 .IP "" 4
.
.nf .nf
$ git ignore *\.log $ git ignore *\.log
Adding pattern(s) to: \.gitignore Adding pattern(s) to: \.gitignore
\.\.\. adding \'*\.log\' \|\.\|\.\|\. adding '*\.log'
.
.fi .fi
.
.IP "" 0 .IP "" 0
.
.P .P
You can now configure any patterns without ever using an editor, with a context and pattern arguments: The resulting configuration is also returned for your convenience\. You can now configure any patterns without ever using an editor (or use a shortcut to open file in editor when needed), with a context and pattern arguments: The resulting configuration is also returned for your convenience\.
.
.IP "" 4 .IP "" 4
.
.nf .nf
$ git ignore \-\-local "" "# Temporary files" *\.tmp "*\.log" tmp/* "" "# Files I'd like to keep" '!work' ""
$ git ignore \-\-local "" "# Temporary files" *\.tmp "*\.log" tmp/* "" "# Files I\'d like to keep" \'!work\' ""
Adding pattern(s) to: \.gitignore Adding pattern(s) to: \.gitignore
\.\.\. adding \'\' \|\.\|\.\|\. adding ''
\.\.\. adding \'# Temporary files\' \|\.\|\.\|\. adding '# Temporary files'
\.\.\. adding \'index\.tmp\' \|\.\|\.\|\. adding '*\.tmp'
\.\.\. adding \'*\.log\' \|\.\|\.\|\. adding '*\.log'
\.\.\. adding \'tmp/*\' \|\.\|\.\|\. adding 'tmp/*'
\.\.\. adding \'\' \|\.\|\.\|\. adding ''
\.\.\. adding \'# Files I\'d like to keep\' \|\.\|\.\|\. adding '# Files I'd like to keep'
\.\.\. adding \'!work\' \|\.\|\.\|\. adding '!work'
\.\.\. adding \'\' \|\.\|\.\|\. adding ''
Local gitignore: \.gitignore Local gitignore: \.gitignore
# Temporary files # Temporary files
index\.tmp *\.tmp
*\.log *\.log
tmp/*
# Files I\'d like to keep # Files I'd like to keep
!work !work
.
.fi .fi
.
.IP "" 0 .IP "" 0
. .P
Removing patterns works in a similar way\. Select context and pass patterns to be removed from the gitignore file\.
.IP "" 4
.nf
$ git ignore \-l \-r "*\.log"
Removing patterns from local gitignore: \.gitignore
\|\.\|\.\|\. removing '*\.log'
Local gitignore: \.gitignore
# Temporary files
*\.tmp
tmp/*
# Files I'd like to keep
!work
.fi
.IP "" 0
.P
If you want to open gitignore in an editor and do changes manually pass \-e flag:
.IP "" 4
.nf
$ git ignore \-p \-e
.fi
.IP "" 0
.SH "AUTHOR" .SH "AUTHOR"
Written by Tj Holowaychuk <\fItj@vision\-media\.ca\fR> and Tema Bolshakov <\fItweekane@gmail\.com\fR> and Nick Lombard <\fIgithub@jigsoft\.co\.za\fR> Written by Tj Holowaychuk <\fItj@vision\-media\.ca\fR> and Tema Bolshakov <\fItweekane@gmail\.com\fR> and Nick Lombard <\fIgithub@jigsoft\.co\.za\fR&gt and Kirill Dergachev <\fIkdergachev1062@gmail\.com\fR>
.
.SH "REPORTING BUGS" .SH "REPORTING BUGS"
<\fIhttps://github\.com/tj/git\-extras/issues\fR> <\fIhttps://github\.com/tj/git\-extras/issues\fR>
.
.SH "SEE ALSO" .SH "SEE ALSO"
<\fIhttps://github\.com/tj/git\-extras\fR> <\fIhttps://github\.com/tj/git\-extras\fR>

View file

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv='content-type' value='text/html;charset=utf8'> <meta http-equiv='content-type' content='text/html;charset=utf-8'>
<meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'> <meta name='generator' content='Ronn-NG/v0.10.1 (http://github.com/apjanke/ronn-ng/tree/0.10.1)'>
<title>git-ignore(1) - Add .gitignore patterns</title> <title>git-ignore(1) - Modify or display .gitignore files</title>
<style type='text/css' media='all'> <style type='text/css' media='all'>
/* style: man */ /* style: man */
body#manpage {margin:0} body#manpage {margin:0}
@ -69,57 +69,114 @@
<li class='tr'>git-ignore(1)</li> <li class='tr'>git-ignore(1)</li>
</ol> </ol>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>git-ignore</code> - <span class="man-whatis">Add .gitignore patterns</span>
</p>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>git-ignore</code> - <span class="man-whatis">Modify or display .gitignore files</span>
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2> <h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>git-ignore</code> [&lt;context&gt;] [&lt;pattern&gt; [&lt;pattern&gt;]...]</p> <p><code>git-ignore</code> [&lt;context&gt;] [&lt;action&gt;] [--] [&lt;pattern&gt; [&lt;pattern&gt;]...]</p>
<h2 id="DESCRIPTION">DESCRIPTION</h2> <h2 id="DESCRIPTION">DESCRIPTION</h2>
<p>Adds the given _pattern_s to a .gitignore file if it doesn't already exist.</p> <p>Modifies or shows .gitignore of selected <em>context</em>.</p>
<p>Possible actions are: addition of new <em>pattern</em>s, removal of existing <em>pattern</em>s from gitignore files, opening these files in text editor or simply displaying their contents in current terminal (for details see flags/usage).</p>
<p>Contexts are global (e.g. $HOME/.gitignore or $HOME/.local/.git/.gitignore), private (e.g. .../project/.git/info/exclude) and local (e.g. .../project/.gitignore)</p>
<p>Adding only happens for <em>pattern</em>s that don't already exist in the file; removing <em>pattern</em>s that are not present does not do anything; editing fails if the file is not yet present.</p>
<p>At most 1 <em>context</em> and 1 <em>action</em> matter for the program, the ones selected are the latest encountered in the flag list.</p>
<p>Default behavior:</p>
<ul>
<li>
<p>If no flags or arguments are passed, prints gitignores of all 3 <em>context</em>s.</p>
</li>
<li>
<p>Passing arguments with no <em>action</em> implies addition of <em>pattern</em>s to gitignore.</p>
</li>
<li>
<p>If an <em>action</em> is chosen, but not <em>context</em>, <em>action</em> is performed on local gitignore.</p>
</li>
<li>
<p>If <em>context</em> is chosen but not action (not even implied), gitignore of chosen <em>context</em> is printed.</p>
</li>
</ul>
<h2 id="OPTIONS">OPTIONS</h2> <h2 id="OPTIONS">OPTIONS</h2>
<p> &lt;context&gt;</p> <p>&lt;context&gt;</p>
<p> -l, --local</p> <p>-l, --local</p>
<p> Sets the context to the .gitignore file in the current working directory. (default)</p> <p>Sets the context to the .gitignore file in the current working directory. (default)</p>
<p> -g, --global</p> <p>-g, --global</p>
<p> Sets the context to the global gitignore file for the current user.</p> <p>Sets the context to the global gitignore file for the current user.</p>
<p> -p, --private</p> <p>-p, --private</p>
<p> Sets the context to the private exclude file for the repository (<code>.git/info/exclude</code>).</p> <p>Sets the context to the private exclude file for the repository (<code>.git/info/exclude</code>).</p>
<p> &lt;pattern&gt;</p> <p>&lt;action&gt;</p>
<p> A space delimited list of patterns to append to the file in context.</p> <p>-e, --edit</p>
<p>Sets action to edit. This will open gitignore in a text editor (editor selection is the same as for git commit). If no <em>context</em> is provided local is assumed. Any <em>pattern</em>; provided in additional arguments is silently omitted.</p>
<p>-r, --remove</p>
<p>Sets action to remove. This will look for <em>pattern</em>s in gitignore of selected <em>context</em> and remove if finds them. If no <em>pattern</em> is provided does nothing. All characters in <em>pattern</em> are treated literally, no interpretation of metacharacters is performed as would be, for example, for a regex pattern.</p>
<p>&lt;pattern&gt;</p>
<p>A space delimited list of patterns to append to the file in context.</p>
<p>--</p>
<p>End of options delimeter. Everything to the right of it is treated as positional arguments or more precisely a <em>pattern</em>.</p>
<p>-h, --help</p>
<p>Print a brief usage summary.</p>
<h3 id="PATTERN-FORMAT">PATTERN FORMAT</h3> <h3 id="PATTERN-FORMAT">PATTERN FORMAT</h3>
<p>Pattern format as described in the git manual</p> <p>Pattern format as described in the git manual</p>
<ul> <ul>
<li><p>A blank line matches no files, so it can serve as a separator for readability. To append a blank line use empty quotes "".</p></li> <li>
<li><p>A line starting with # serves as a comment. For example, "# This is a comment"</p></li> <p>A blank line matches no files, so it can serve as a separator for readability. To append a blank line use empty quotes "".</p>
<li><p>An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again. If a negated pattern matches, this will override lower precedence patterns sources. To use an exclamation ! as command line argument it is best placed between single quotes ''. For example, '!src'</p></li> </li>
<li><p>If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in git).</p></li> <li>
<li><p>If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the top level of the work tree if not from a .gitignore file).</p></li> <p>A line starting with # serves as a comment. For example, "# This is a comment"</p>
<li><p>Otherwise, git treats the pattern as a shell glob suitable for consumption by <span class="man-ref">fnmatch<span class="s">(3)</span></span> with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, "Documentation/*.html" matches "Documentation/git.html" but not "Documentation/ppc/ppc.html" or "tools/perf/Documentation/perf.html".</p></li> </li>
<li><p>A leading slash matches the beginning of the pathname. For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".</p></li> <li>
<p>An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again. If a negated pattern matches, this will override lower precedence patterns sources. To use an exclamation ! as command line argument it is best placed between single quotes ''. For example, '!src'</p>
</li>
<li>
<p>If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory. In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in git).</p>
</li>
<li>
<p>If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the top level of the work tree if not from a .gitignore file).</p>
</li>
<li>
<p>Otherwise, git treats the pattern as a shell glob suitable for consumption by <span class="man-ref">fnmatch<span class="s">(3)</span></span> with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, "Documentation/*.html" matches "Documentation/git.html" but not "Documentation/ppc/ppc.html" or "tools/perf/Documentation/perf.html".</p>
</li>
<li>
<p>A leading slash matches the beginning of the pathname. For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".</p>
</li>
</ul> </ul>
<h2 id="EXAMPLES">EXAMPLES</h2> <h2 id="EXAMPLES">EXAMPLES</h2>
<p> All arguments are optional so calling git-ignore alone will display first the global then the local gitignore files:</p> <p>All arguments are optional so calling git-ignore alone will display global, local and private gitignore files in order:</p>
<pre><code>$ git ignore <pre><code>$ git ignore
Global gitignore: /home/alice/.gitignore Global gitignore: /home/alice/.gitignore
@ -146,9 +203,15 @@ Local gitignore: .gitignore
.settings .settings
.tmproj .tmproj
nbproject nbproject
---------------------------------
Private gitignore: .git/info/exclude
notes.txt
test.sh
.env.local
config.json
</code></pre> </code></pre>
<p>If you only want to see the global context use the --global argument (for local use --local):</p> <p>If you only want to see the global context use the --global argument (for local use --local, for private use --private):</p>
<pre><code>$ git ignore <pre><code>$ git ignore
Global gitignore: /home/alice/.gitignore Global gitignore: /home/alice/.gitignore
@ -165,14 +228,14 @@ Adding pattern(s) to: .gitignore
... adding '*.log' ... adding '*.log'
</code></pre> </code></pre>
<p>You can now configure any patterns without ever using an editor, with a context and pattern arguments: <p>You can now configure any patterns without ever using an editor (or use a shortcut to open file in editor when needed), with a context and pattern arguments:
The resulting configuration is also returned for your convenience.</p> The resulting configuration is also returned for your convenience.</p>
<pre><code>$ git ignore --local "" "# Temporary files" *.tmp "*.log" tmp/* "" "# Files I'd like to keep" '!work' "" <pre><code>$ git ignore --local "" "# Temporary files" *.tmp "*.log" tmp/* "" "# Files I'd like to keep" '!work' ""
Adding pattern(s) to: .gitignore Adding pattern(s) to: .gitignore
... adding '' ... adding ''
... adding '# Temporary files' ... adding '# Temporary files'
... adding 'index.tmp' ... adding '*.tmp'
... adding '*.log' ... adding '*.log'
... adding 'tmp/*' ... adding 'tmp/*'
... adding '' ... adding ''
@ -183,17 +246,39 @@ Adding pattern(s) to: .gitignore
Local gitignore: .gitignore Local gitignore: .gitignore
# Temporary files # Temporary files
index.tmp *.tmp
*.log *.log
tmp/*
# Files I'd like to keep # Files I'd like to keep
!work !work
</code></pre> </code></pre>
<p>Removing patterns works in a similar way. Select context and pass patterns to be removed from the gitignore file.</p>
<pre><code>$ git ignore -l -r "*.log"
Removing patterns from local gitignore: .gitignore
... removing '*.log'
Local gitignore: .gitignore
# Temporary files
*.tmp
tmp/*
# Files I'd like to keep
!work
</code></pre>
<p>If you want to open gitignore in an editor and do changes manually pass -e flag:</p>
<pre><code>$ git ignore -p -e
</code></pre>
<h2 id="AUTHOR">AUTHOR</h2> <h2 id="AUTHOR">AUTHOR</h2>
<p>Written by Tj Holowaychuk &lt;<a href="&#x6d;&#97;&#105;&#108;&#x74;&#111;&#58;&#116;&#x6a;&#x40;&#x76;&#105;&#115;&#105;&#111;&#110;&#x2d;&#x6d;&#x65;&#100;&#x69;&#97;&#46;&#x63;&#x61;" data-bare-link="true">&#116;&#106;&#x40;&#118;&#x69;&#x73;&#x69;&#x6f;&#110;&#45;&#109;&#101;&#100;&#105;&#97;&#x2e;&#99;&#97;</a>&gt; and Tema Bolshakov &lt;<a href="&#x6d;&#97;&#105;&#x6c;&#116;&#x6f;&#58;&#116;&#x77;&#101;&#101;&#107;&#x61;&#x6e;&#101;&#x40;&#x67;&#x6d;&#97;&#x69;&#108;&#46;&#x63;&#111;&#x6d;" data-bare-link="true">&#x74;&#119;&#x65;&#x65;&#107;&#x61;&#110;&#x65;&#x40;&#103;&#x6d;&#97;&#105;&#x6c;&#x2e;&#x63;&#x6f;&#x6d;</a>&gt; <p>Written by Tj Holowaychuk &lt;<a href="mailto:tj@vision-media.ca" data-bare-link="true">tj@vision-media.ca</a>&gt; and Tema Bolshakov &lt;<a href="mailto:tweekane@gmail.com" data-bare-link="true">tweekane@gmail.com</a>&gt;
and Nick Lombard &lt;<a href="&#109;&#97;&#x69;&#x6c;&#116;&#111;&#58;&#x67;&#105;&#116;&#104;&#117;&#x62;&#x40;&#x6a;&#105;&#103;&#x73;&#x6f;&#x66;&#116;&#46;&#x63;&#x6f;&#x2e;&#x7a;&#97;" data-bare-link="true">&#103;&#105;&#116;&#104;&#x75;&#98;&#x40;&#106;&#105;&#103;&#115;&#x6f;&#102;&#x74;&#x2e;&#x63;&#111;&#x2e;&#122;&#x61;</a>&gt;</p> and Nick Lombard &lt;<a href="mailto:github@jigsoft.co.za" data-bare-link="true">github@jigsoft.co.za</a>&amp;gt and Kirill Dergachev &lt;<a href="mailto:kdergachev1062@gmail.com" data-bare-link="true">kdergachev1062@gmail.com</a>&gt;</p>
<h2 id="REPORTING-BUGS">REPORTING BUGS</h2> <h2 id="REPORTING-BUGS">REPORTING BUGS</h2>
@ -203,10 +288,9 @@ and Nick Lombard &lt;<a href="&#109;&#97;&#x69;&#x6c;&#116;&#111;&#58;&#x67;&#10
<p>&lt;<a href="https://github.com/tj/git-extras" data-bare-link="true">https://github.com/tj/git-extras</a>&gt;</p> <p>&lt;<a href="https://github.com/tj/git-extras" data-bare-link="true">https://github.com/tj/git-extras</a>&gt;</p>
<ol class='man-decor man-foot man foot'> <ol class='man-decor man-foot man foot'>
<li class='tl'></li> <li class='tl'></li>
<li class='tc'>April 2018</li> <li class='tc'>June 2026</li>
<li class='tr'>git-ignore(1)</li> <li class='tr'>git-ignore(1)</li>
</ol> </ol>

View file

@ -1,13 +1,31 @@
git-ignore(1) -- Add .gitignore patterns git-ignore(1) -- Modify or display .gitignore files
======================================== ========================================
## SYNOPSIS ## SYNOPSIS
`git-ignore` [&lt;context&gt;] [&lt;pattern&gt; [&lt;pattern&gt;]...] `git-ignore` [&lt;context&gt;] [&lt;action&gt;] [--] [&lt;pattern&gt; [&lt;pattern&gt;]...]
## DESCRIPTION ## DESCRIPTION
Adds the given _pattern_s to a .gitignore file if it doesn't already exist. Modifies or shows .gitignore of selected *context*.
Possible actions are: addition of new *pattern*s, removal of existing *pattern*s from gitignore files, opening these files in text editor or simply displaying their contents in current terminal (for details see flags/usage).
Contexts are global (e.g. $HOME/.gitignore or $HOME/.local/.git/.gitignore), private (e.g. .../project/.git/info/exclude) and local (e.g. .../project/.gitignore)
Adding only happens for *pattern*s that don't already exist in the file; removing *pattern*s that are not present does not do anything; editing fails if the file is not yet present.
At most 1 *context* and 1 *action* matter for the program, the ones selected are the latest encountered in the flag list.
Default behavior:
* If no flags or arguments are passed, prints gitignores of all 3 *context*s.
* Passing arguments with no *action* implies addition of *pattern*s to gitignore.
* If an *action* is chosen, but not *context*, *action* is performed on local gitignore.
* If *context* is chosen but not action (not even implied), gitignore of chosen *context* is printed.
## OPTIONS ## OPTIONS
@ -25,10 +43,28 @@ Adds the given _pattern_s to a .gitignore file if it doesn't already exist.
Sets the context to the private exclude file for the repository (`.git/info/exclude`). Sets the context to the private exclude file for the repository (`.git/info/exclude`).
&lt;action&gt;
-e, --edit
Sets action to edit. This will open gitignore in a text editor (editor selection is the same as for git commit). If no *context* is provided local is assumed. Any *pattern*; provided in additional arguments is silently omitted.
-r, --remove
Sets action to remove. This will look for *pattern*s in gitignore of selected *context* and remove if finds them. If no *pattern* is provided does nothing. All characters in *pattern* are treated literally, no interpretation of metacharacters is performed as would be, for example, for a regex pattern.
&lt;pattern&gt; &lt;pattern&gt;
A space delimited list of patterns to append to the file in context. A space delimited list of patterns to append to the file in context.
--
End of options delimiter. Everything to the right of it is treated as positional arguments or more precisely a *pattern*.
-h, --help
Print a brief usage summary.
### PATTERN FORMAT ### PATTERN FORMAT
Pattern format as described in the git manual Pattern format as described in the git manual
@ -43,14 +79,14 @@ Pattern format as described in the git manual
* If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the top level of the work tree if not from a .gitignore file). * If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the top level of the work tree if not from a .gitignore file).
* Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, "Documentation/*.html" matches "Documentation/git.html" but not "Documentation/ppc/ppc.html" or "tools/perf/Documentation/perf.html". * Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM\_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. For example, "Documentation/\*.html" matches "Documentation/git.html" but not "Documentation/ppc/ppc.html" or "tools/perf/Documentation/perf.html".
* A leading slash matches the beginning of the pathname. For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". * A leading slash matches the beginning of the pathname. For example, "/\*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
## EXAMPLES ## EXAMPLES
All arguments are optional so calling git-ignore alone will display first the global then the local gitignore files: All arguments are optional so calling git-ignore alone will display global, local and private gitignore files in order:
$ git ignore $ git ignore
Global gitignore: /home/alice/.gitignore Global gitignore: /home/alice/.gitignore
@ -77,8 +113,14 @@ Pattern format as described in the git manual
.settings .settings
.tmproj .tmproj
nbproject nbproject
---------------------------------
Private gitignore: .git/info/exclude
notes.txt
test.sh
.env.local
config.json
If you only want to see the global context use the --global argument (for local use --local): If you only want to see the global context use the --global argument (for local use --local, for private use --private):
$ git ignore $ git ignore
Global gitignore: /home/alice/.gitignore Global gitignore: /home/alice/.gitignore
@ -93,34 +135,55 @@ To quickly append a new pattern to the default/local context simply:
Adding pattern(s) to: .gitignore Adding pattern(s) to: .gitignore
... adding '*.log' ... adding '*.log'
You can now configure any patterns without ever using an editor, with a context and pattern arguments: You can now configure any patterns without ever using an editor (or use a shortcut to open file in editor when needed), with a context and pattern arguments:
The resulting configuration is also returned for your convenience. The resulting configuration is also returned for your convenience.
$ git ignore --local "" "# Temporary files" *.tmp "*.log" tmp/* "" "# Files I'd like to keep" '!work' "" $ git ignore --local "" "# Temporary files" *.tmp "*.log" tmp/* "" "# Files I'd like to keep" '!work' ""
Adding pattern(s) to: .gitignore Adding pattern(s) to: .gitignore
... adding '' ... adding ''
... adding '# Temporary files' ... adding '# Temporary files'
... adding 'index.tmp' ... adding '*.tmp'
... adding '*.log' ... adding '*.log'
... adding 'tmp/*' ... adding 'tmp/*'
... adding '' ... adding ''
... adding '# Files I'd like to keep' ... adding '# Files I'd like to keep'
... adding '!work' ... adding '!work'
... adding '' ... adding ''
Local gitignore: .gitignore Local gitignore: .gitignore
# Temporary files # Temporary files
index.tmp *.tmp
*.log *.log
tmp/*
# Files I'd like to keep # Files I'd like to keep
!work !work
Removing patterns works in a similar way. Select context and pass patterns to be removed from the gitignore file.
$ git ignore -l -r "*.log"
Removing patterns from local gitignore: .gitignore
... removing '*.log'
Local gitignore: .gitignore
# Temporary files
*.tmp
tmp/*
# Files I'd like to keep
!work
If you want to open gitignore in an editor and do changes manually pass -e flag:
$ git ignore -p -e
## AUTHOR ## AUTHOR
Written by Tj Holowaychuk &lt;<tj@vision-media.ca>&gt; and Tema Bolshakov &lt;<tweekane@gmail.com>&gt; Written by Tj Holowaychuk &lt;<tj@vision-media.ca>&gt; and Tema Bolshakov &lt;<tweekane@gmail.com>&gt;
and Nick Lombard &lt;<github@jigsoft.co.za>&gt; and Nick Lombard &lt;<github@jigsoft.co.za>&gt and Kirill Dergachev &lt;<kdergachev1062@gmail.com>&gt;
## REPORTING BUGS ## REPORTING BUGS

View file

@ -28,7 +28,7 @@ The name of the remote to fetch from\. Defaults to \fBorigin\fR\.
<url> <url>
. .
.P .P
GitLab merge request URL in the format \fBhttps://gitlab\.tld/owner/repository/merge_requests/453\fR\. GitLab merge request URL in the format \fBhttps://gitlab\.tld/owner/repository/merge_requests/453\fR, or a Forgejo/Codeberg pull request URL in the format \fBhttps://codeberg\.tld/owner/repository/pulls/453\fR\.
. .
.SH "EXAMPLES" .SH "EXAMPLES"
This checks out merge request \fB!51\fR from remote \fBorigin\fR to branch \fBmr/51\fR\. This checks out merge request \fB!51\fR from remote \fBorigin\fR to branch \fBmr/51\fR\.
@ -46,6 +46,22 @@ Switched to branch \'mr/51\'
. .
.IP "" 0 .IP "" 0
. .
.P
This checks out pull request \fB#51\fR from a Forgejo/Codeberg URL to branch \fBmr/51\fR\.
.
.IP "" 4
.
.nf
$ git mr https://codeberg\.org/owner/repository/pulls/51
From codeberg\.org:owner/repository
* [new ref] refs/pull/51/head \-> mr/51
Switched to branch \'mr/51\'
.
.fi
.
.IP "" 0
.
.SH "AUTHOR" .SH "AUTHOR"
Written by Étienne BERSAC \fIbersace03@gmail\.com\fR from git\-pr(1)\. Written by Étienne BERSAC \fIbersace03@gmail\.com\fR from git\-pr(1)\.
. .

View file

@ -94,7 +94,9 @@
<p> &lt;url&gt;</p> <p> &lt;url&gt;</p>
<p> GitLab merge request URL in the format <p> GitLab merge request URL in the format
<code>https://gitlab.tld/owner/repository/merge_requests/453</code>.</p> <code>https://gitlab.tld/owner/repository/merge_requests/453</code>, or a
Forgejo/Codeberg pull request URL in the format
<code>https://codeberg.tld/owner/repository/pulls/453</code>.</p>
<h2 id="EXAMPLES">EXAMPLES</h2> <h2 id="EXAMPLES">EXAMPLES</h2>
@ -106,6 +108,14 @@ From gitlab.com:owner/repository
Switched to branch 'mr/51' Switched to branch 'mr/51'
</code></pre> </code></pre>
<p>This checks out pull request <code>#51</code> from a Forgejo/Codeberg URL to branch <code>mr/51</code>.</p>
<pre><code>$ git mr https://codeberg.org/owner/repository/pulls/51
From codeberg.org:owner/repository
* [new ref] refs/pull/51/head -&gt; mr/51
Switched to branch 'mr/51'
</code></pre>
<h2 id="AUTHOR">AUTHOR</h2> <h2 id="AUTHOR">AUTHOR</h2>
<p>Written by Étienne BERSAC <a href="&#109;&#x61;&#x69;&#x6c;&#116;&#x6f;&#58;&#x62;&#101;&#x72;&#115;&#97;&#x63;&#101;&#48;&#x33;&#64;&#103;&#x6d;&#x61;&#x69;&#108;&#x2e;&#99;&#x6f;&#109;" data-bare-link="true">&#x62;&#101;&#114;&#115;&#x61;&#x63;&#101;&#48;&#51;&#64;&#x67;&#109;&#97;&#x69;&#x6c;&#46;&#x63;&#111;&#109;</a> from <a class="man-ref" href="git-pr.html">git-pr<span class="s">(1)</span></a>.</p> <p>Written by Étienne BERSAC <a href="&#109;&#x61;&#x69;&#x6c;&#116;&#x6f;&#58;&#x62;&#101;&#x72;&#115;&#97;&#x63;&#101;&#48;&#x33;&#64;&#103;&#x6d;&#x61;&#x69;&#108;&#x2e;&#99;&#x6f;&#109;" data-bare-link="true">&#x62;&#101;&#114;&#115;&#x61;&#x63;&#101;&#48;&#51;&#64;&#x67;&#109;&#97;&#x69;&#x6c;&#46;&#x63;&#111;&#109;</a> from <a class="man-ref" href="git-pr.html">git-pr<span class="s">(1)</span></a>.</p>

View file

@ -21,7 +21,9 @@ git-mr(1) -- Checks out a merge request locally
&lt;url&gt; &lt;url&gt;
GitLab merge request URL in the format GitLab merge request URL in the format
`https://gitlab.tld/owner/repository/merge_requests/453`. `https://gitlab.tld/owner/repository/merge_requests/453`, or a
Forgejo/Codeberg pull request URL in the format
`https://codeberg.tld/owner/repository/pulls/453`.
## EXAMPLES ## EXAMPLES
@ -33,6 +35,13 @@ This checks out merge request `!51` from remote `origin` to branch `mr/51`.
* [new ref] refs/merge-requests/51/head -> mr/51 * [new ref] refs/merge-requests/51/head -> mr/51
Switched to branch 'mr/51' Switched to branch 'mr/51'
This checks out pull request `#51` from a Forgejo/Codeberg URL to branch `mr/51`.
$ git mr https://codeberg.org/owner/repository/pulls/51
From codeberg.org:owner/repository
* [new ref] refs/pull/51/head -> mr/51
Switched to branch 'mr/51'
## AUTHOR ## AUTHOR
Written by Étienne BERSAC <bersace03@gmail.com> from git-pr(1). Written by Étienne BERSAC <bersace03@gmail.com> from git-pr(1).

View file

@ -4,8 +4,8 @@ git-scp(1) -- Copy files to SSH compatible `git-remote`
## SYNOPSIS ## SYNOPSIS
`git scp` -h|help|? `git scp` -h|help|?
`git scp` <remote> [<commits>...|<path>...] `git scp` [options] <remote> [<commits>...|<path>...]
`git rscp` <remote> <path> `git rscp` [options] <remote> <path>
## DESCRIPTION ## DESCRIPTION
@ -29,6 +29,18 @@ Internally this script uses `rsync` and not `scp` as the name suggests.
The <paths> parameters, when given, are used to limit the diff to the named paths (you can give directory names and get diff for all files under them). The <paths> parameters, when given, are used to limit the diff to the named paths (you can give directory names and get diff for all files under them).
-v, --verbose
Print what will be synced before syncing: the diff for `git scp`, or a preview (rsync dry-run) for `git rscp`.
-i, --interactive
Same as `--verbose`, but also prompt for confirmation before syncing. Implies `--verbose`
-n, --dry-run
Show what would be synced, change nothing. Implies `--verbose`, and no staging or remote writes/deletes happen.
## GIT CONFIGS ## GIT CONFIGS
To sanitize files using `dos2unix` before copying files To sanitize files using `dos2unix` before copying files

View file

@ -18,6 +18,7 @@ git-count(1) git-count
git-cp(1) git-cp git-cp(1) git-cp
git-create-branch(1) git-create-branch git-create-branch(1) git-create-branch
git-delete-branch(1) git-delete-branch git-delete-branch(1) git-delete-branch
git-delete-gone-branches(1) git-delete-gone-branches
git-delete-merged-branches(1) git-delete-merged-branches git-delete-merged-branches(1) git-delete-merged-branches
git-delete-squashed-branches(1) git-delete-squashed-branches git-delete-squashed-branches(1) git-delete-squashed-branches
git-delete-submodule(1) git-delete-submodule git-delete-submodule(1) git-delete-submodule

View file

@ -1,9 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import re
import os
import argparse import argparse
import os
import re
import sys
from collections.abc import Callable # compat
from pathlib import Path from pathlib import Path
from typing import Callable, List, Dict, Any # compat from typing import Any
# This file checks Bash and Shell scripts for violations not found with # This file checks Bash and Shell scripts for violations not found with
# shellcheck or existing methods. You can use it in several ways: # shellcheck or existing methods. You can use it in several ways:
@ -21,7 +23,7 @@ from typing import Callable, List, Dict, Any # compat
# Check to ensure all regular expressions are working as intended: # Check to ensure all regular expressions are working as intended:
# $ ./scripts/checkstyle.py --internal-test-regex # $ ./scripts/checkstyle.py --internal-test-regex
Rule = Dict[str, Any] Rule = dict[str, Any]
class c: class c:
RED = '\033[91m' RED = '\033[91m'
@ -91,7 +93,7 @@ def noVerboseRedirectionFixer(line: str, m: Any) -> str:
return f'{prestr}&>/dev/null{poststr}' return f'{prestr}&>/dev/null{poststr}'
def lintfile(file: Path, rules: List[Rule], options: Dict[str, Any]): def lintfile(file: Path, rules: list[Rule], options: dict[str, Any]):
content_arr = file.read_text().split('\n') content_arr = file.read_text().split('\n')
for line_i, line in enumerate(content_arr): for line_i, line in enumerate(content_arr):
@ -100,15 +102,15 @@ def lintfile(file: Path, rules: List[Rule], options: Dict[str, Any]):
for rule in rules: for rule in rules:
should_run = False should_run = False
if 'sh' in rule['fileTypes']: if 'sh' in rule['fileTypes'] and file.name.endswith('.sh'):
if file.name.endswith('.sh'): should_run = True
should_run = True if 'bash' in rule['fileTypes'] and (
if 'bash' in rule['fileTypes']: file.name.endswith('.bash') or file.name.endswith('.bats') or file.name.startswith('git-')
if file.name.endswith('.bash') or file.name.endswith('.bats') or file.name.startswith('git-'): ):
should_run = True should_run = True
if options['verbose']: if options['verbose']:
print(f'{str(file)}: {should_run}') print(f'{file!s}: {should_run}')
if not should_run: if not should_run:
continue continue
@ -134,7 +136,7 @@ def lintfile(file: Path, rules: List[Rule], options: Dict[str, Any]):
file.write_text('\n'.join(content_arr)) file.write_text('\n'.join(content_arr))
def main(): def main():
rules: List[Rule] = [ rules: list[Rule] = [
{ {
'name': 'no-pwd-capture', 'name': 'no-pwd-capture',
'regex': '(?P<match>\\$\\(pwd\\))', 'regex': '(?P<match>\\$\\(pwd\\))',
@ -238,7 +240,12 @@ def main():
lintfile(p, rules, options) lintfile(p, rules, options)
else: else:
for file in Path.cwd().glob('**/*'): for file in Path.cwd().glob('**/*'):
if '.git' in str(file.absolute()): skip = False
for name in ('.git', 'vendor', '__pycache__'):
if name in str(file.absolute()):
skip = True
break
if skip:
continue continue
if file.is_file(): if file.is_file():
@ -255,8 +262,8 @@ def main():
# exit # exit
if grand_total == 0: if grand_total == 0:
exit(0) sys.exit(0)
else: else:
exit(2) sys.exit(2)
main() main()

View file

@ -1,6 +1,6 @@
# Testing # Testing
Originally, the tests were written in pytest. However, tests are in the process of being converted to Bats so coverage can be calculated. Tests are written in Bats so coverage can be calculated for shell commands.
## Bats Testing ## Bats Testing
@ -10,7 +10,7 @@ We require a somewhat recent version of Bats. Version v1.8.1 is tested in CI. On
bats ./tests bats ./tests
``` ```
We highly recommend adding tests for new features and fixes. We highly recommend adding Bats tests for new features and fixes.
### Code Coverage ### Code Coverage
@ -21,49 +21,3 @@ bashcov -- bats ./tests
``` ```
By default, the report will be generated in `./coverage/index.html`. By default, the report will be generated in `./coverage/index.html`.
## Python Testing
The test part depends on:
* python >= 3.11
* poetry >= 1.8.0
* pytest >= 8.1.2
* gitpython >= 3.1.43
So the versions are higher than above is recommended.
### How to run the tests
1. Install `poetry`
2. Install the dependencies via `poetry install`
3. Run `poetry run pytest`
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. 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
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,
1. The `git-alias` should have a test suite, so create `test_git_alias.py` in the directory `test`
2. Create a test class `TestGitAlias` in the `test_git_alias.py`
3. Create a test case `test_init`, and some test fixtures can be used, `temp_repo`, `named_temp_repo` etc.
* `temp_repo` is module scoped fixture which create a temporary directory and available in the test suite `test_git_alias.py`.
* `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
* [poetry](https://github.com/python-poetry/poetry)
* [pytest](https://github.com/pytest-dev/pytest/)
* [git python](https://github.com/gitpython-developers/GitPython)

View file

@ -1,45 +0,0 @@
# Sharing fixtures
# Ref: https://docs.pytest.org/en/6.2.x/fixture.html#scope-sharing-fixtures-across-classes-modules-packages-or-session
import pytest
from helper import TempRepository
def create_repo(dirname=None):
repo = TempRepository(dirname)
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(".")
git.config("--local", "user.name", "test")
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
repo = create_repo(dirname)
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

View file

@ -4,6 +4,7 @@ source "$BATS_TEST_DIRNAME/test_util.sh"
setup_file() { setup_file() {
test_util.setup_file test_util.setup_file
test_util.install_command archive-file
} }
setup() { setup() {
@ -37,7 +38,7 @@ setup() {
} }
@test "archive file on any not tags branch with default branch" { @test "archive file on any not tags branch with default branch" {
skip "Not working as expected" git config git-extras.default-branch main
run git archive-file run git archive-file
assert_success assert_success
@ -58,9 +59,32 @@ setup() {
} }
@test "archive file on dirname has backslash" { @test "archive file on dirname has backslash" {
skip local repo_dir="$BATS_TEST_TMPDIR/backslash\\dir"
mkdir "$repo_dir"
cd "$repo_dir"
test_util.git_init
printf '%s\n' 'data' > tmpfile
git add .
git commit -m 'test: add data'
git checkout -b default
run git archive-file
assert_success
local describe_output=
describe_output=$(git describe --always --long)
assert_file_exists "backslash-dir.$describe_output.default.zip"
} }
@test "archive file on tag name has slash" { @test "archive file on tag name has slash" {
skip git tag --delete 0.1.0
git tag 0.1.0/slash -m 'bump: 0.1.0'
run git archive-file
assert_success
local describe_output=
describe_output=$(git describe --always --long)
assert_file_exists "${PWD##*/}.${describe_output//\//-}.zip"
} }

View file

@ -26,6 +26,16 @@ get_ci_uri() {
fi fi
} }
mock_uname() {
local output=$1
local mock_bin="$BATS_TEST_TMPDIR/bin"
mkdir -p "$mock_bin"
printf '#!/usr/bin/env bash\nprintf "%%s\\n" "%s"\n' "$output" > "$mock_bin/uname"
chmod +x "$mock_bin/uname"
PATH="$mock_bin:$PATH"
}
@test "works with mac and github" { @test "works with mac and github" {
get_ci_uri 'github' get_ci_uri 'github'
local expected_url=$REPLY local expected_url=$REPLY
@ -96,6 +106,110 @@ get_ci_uri() {
assert_success assert_success
} }
@test "works with WSL and github" {
get_ci_uri 'github'
local expected_url=$REPLY
mock_uname microsoft
git remote add upstream https://github.com/tj/git-extras
OSTYPE=linux run git browse-ci upstream
assert_line "powershell.exe -NoProfile start $expected_url"
assert_success
}
@test "works with WSL and gitlab" {
get_ci_uri 'gitlab'
local expected_url=$REPLY
mock_uname microsoft
git remote add upstream https://gitlab.com/tj/git-extras
OSTYPE=linux run git browse-ci upstream
assert_line "powershell.exe -NoProfile start $expected_url"
assert_success
}
@test "works with WSL and bitbucket" {
get_ci_uri 'bitbucket'
local expected_url=$REPLY
mock_uname microsoft
git remote add upstream https://bitbucket.org/tj/git-extras
OSTYPE=linux run git browse-ci upstream
assert_line "powershell.exe -NoProfile start $expected_url"
assert_success
}
@test "works with linux without Microsoft kernel and github" {
get_ci_uri 'github'
local expected_url=$REPLY
mock_uname no-micro-soft
git remote add upstream https://github.com/tj/git-extras
OSTYPE=linux run git browse-ci upstream
assert_output "xdg-open $expected_url"
assert_success
}
@test "works with linux without Microsoft kernel and gitlab" {
get_ci_uri 'gitlab'
local expected_url=$REPLY
mock_uname no-micro-soft
git remote add upstream https://gitlab.com/tj/git-extras
OSTYPE=linux run git browse-ci upstream
assert_output "xdg-open $expected_url"
assert_success
}
@test "works with linux without Microsoft kernel and bitbucket" {
get_ci_uri 'bitbucket'
local expected_url=$REPLY
mock_uname no-micro-soft
git remote add upstream https://bitbucket.org/tj/git-extras
OSTYPE=linux run git browse-ci upstream
assert_output "xdg-open $expected_url"
assert_success
}
@test "falls back to xdg-open on an unknown OS with github" {
get_ci_uri 'github'
local expected_url=$REPLY
git remote add upstream https://github.com/tj/git-extras
OSTYPE=unique-system run git browse-ci upstream
assert_output "xdg-open $expected_url"
assert_success
}
@test "falls back to xdg-open on an unknown OS with gitlab" {
get_ci_uri 'gitlab'
local expected_url=$REPLY
git remote add upstream https://gitlab.com/tj/git-extras
OSTYPE=unique-system run git browse-ci upstream
assert_output "xdg-open $expected_url"
assert_success
}
@test "falls back to xdg-open on an unknown OS with bitbucket" {
get_ci_uri 'bitbucket'
local expected_url=$REPLY
git remote add upstream https://bitbucket.org/tj/git-extras
OSTYPE=unique-system run git browse-ci upstream
assert_output "xdg-open $expected_url"
assert_success
}
@test "opens an empty URL for an unknown site" {
git remote add upstream https://unknown-site.com/tj/git-extras.git
OSTYPE=unique-system run git browse-ci upstream
assert_output "xdg-open "
assert_success
}
@test "works with linux and gitlab" { @test "works with linux and gitlab" {
get_ci_uri 'gitlab' get_ci_uri 'gitlab'
local expected_url=$REPLY local expected_url=$REPLY

View file

@ -32,6 +32,16 @@ get_file_uri() {
fi fi
} }
mock_uname() {
local output=$1
local mock_bin="$BATS_TEST_TMPDIR/bin"
mkdir -p "$mock_bin"
printf '#!/usr/bin/env bash\nprintf "%%s\\n" "%s"\n' "$output" > "$mock_bin/uname"
chmod +x "$mock_bin/uname"
PATH="$mock_bin:$PATH"
}
@test "works with mac and github" { @test "works with mac and github" {
get_file_uri github ./browse_this get_file_uri github ./browse_this
local expected_url=$REPLY local expected_url=$REPLY
@ -102,6 +112,148 @@ get_file_uri() {
assert_success assert_success
} }
@test "works with WSL and github" {
get_file_uri github ./browse_this
local expected_url=$REPLY
mock_uname microsoft
git remote add upstream https://github.com/tj/git-extras
OSTYPE=linux run git browse upstream ./browse_this
assert_line "powershell.exe -NoProfile start $expected_url"
assert_success
}
@test "works with WSL and gitlab" {
get_file_uri gitlab ./browse_this
local expected_url=$REPLY
mock_uname microsoft
git remote add upstream https://gitlab.com/tj/git-extras
OSTYPE=linux run git browse upstream ./browse_this
assert_line "powershell.exe -NoProfile start $expected_url"
assert_success
}
@test "works with WSL and bitbucket" {
get_file_uri bitbucket ./browse_this
local expected_url=$REPLY
mock_uname microsoft
git remote add upstream https://bitbucket.org/tj/git-extras
OSTYPE=linux run git browse upstream ./browse_this
assert_line "powershell.exe -NoProfile start $expected_url"
assert_success
}
@test "works with linux without Microsoft kernel and github" {
get_file_uri github ./browse_this
local expected_url=$REPLY
mock_uname no-micro-soft
git remote add upstream https://github.com/tj/git-extras
OSTYPE=linux run git browse upstream ./browse_this
assert_output "xdg-open $expected_url"
assert_success
}
@test "works with linux without Microsoft kernel and gitlab" {
get_file_uri gitlab ./browse_this
local expected_url=$REPLY
mock_uname no-micro-soft
git remote add upstream https://gitlab.com/tj/git-extras
OSTYPE=linux run git browse upstream ./browse_this
assert_output "xdg-open $expected_url"
assert_success
}
@test "works with linux without Microsoft kernel and bitbucket" {
get_file_uri bitbucket ./browse_this
local expected_url=$REPLY
mock_uname no-micro-soft
git remote add upstream https://bitbucket.org/tj/git-extras
OSTYPE=linux run git browse upstream ./browse_this
assert_output "xdg-open $expected_url"
assert_success
}
@test "falls back to xdg-open on an unknown OS with github" {
get_file_uri github ./browse_this
local expected_url=$REPLY
git remote add upstream https://github.com/tj/git-extras
OSTYPE=unique-system run git browse upstream ./browse_this
assert_output "xdg-open $expected_url"
assert_success
}
@test "falls back to xdg-open on an unknown OS with gitlab" {
get_file_uri gitlab ./browse_this
local expected_url=$REPLY
git remote add upstream https://gitlab.com/tj/git-extras
OSTYPE=unique-system run git browse upstream ./browse_this
assert_output "xdg-open $expected_url"
assert_success
}
@test "falls back to xdg-open on an unknown OS with bitbucket" {
get_file_uri bitbucket ./browse_this
local expected_url=$REPLY
git remote add upstream https://bitbucket.org/tj/git-extras
OSTYPE=unique-system run git browse upstream ./browse_this
assert_output "xdg-open $expected_url"
assert_success
}
@test "adds github line numbers" {
get_file_uri github ./browse_this
local expected_url="$REPLY#L10-L20"
git remote add upstream https://github.com/tj/git-extras
OSTYPE=unique-system run git browse upstream ./browse_this 10 20
assert_output "xdg-open $expected_url"
assert_success
}
@test "adds gitlab line numbers" {
get_file_uri gitlab ./browse_this
local expected_url="$REPLY#L10-20"
git remote add upstream https://gitlab.com/tj/git-extras
OSTYPE=unique-system run git browse upstream ./browse_this 10 20
assert_output "xdg-open $expected_url"
assert_success
}
@test "adds bitbucket line numbers" {
get_file_uri bitbucket ./browse_this
local expected_url="$REPLY#lines-10:20"
git remote add upstream https://bitbucket.org/tj/git-extras
OSTYPE=unique-system run git browse upstream ./browse_this 10 20
assert_output "xdg-open $expected_url"
assert_success
}
@test "opens an unknown site repository" {
git remote add upstream https://unknown-site.com/tj/git-extras.git
OSTYPE=unique-system run git browse upstream
assert_output "xdg-open https://unknown-site.com/tj/git-extras"
assert_success
}
@test "does not add file or line information for an unknown site" {
git remote add upstream https://unknown-site.com/tj/git-extras.git
OSTYPE=unique-system run git browse upstream ./browse_this 10 20
assert_output "xdg-open https://unknown-site.com/tj/git-extras"
assert_success
}
@test "works with linux and gitlab" { @test "works with linux and gitlab" {
get_file_uri gitlab ./browse_this get_file_uri gitlab ./browse_this
local expected_url=$REPLY local expected_url=$REPLY

68
tests/git-mr.bats Normal file
View file

@ -0,0 +1,68 @@
# 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 ./tracked
git add ./tracked
git commit -m 'Initial commit'
# Local bare "remote" standing in for a real GitLab/Forgejo instance,
# with a merge-requests ref and a pull ref pointing at the same commit.
remote_dir="$BATS_TEST_TMPDIR/remote.git"
git init --bare --initial-branch main "$remote_dir"
git push "$remote_dir" HEAD:refs/merge-requests/51/head
git push "$remote_dir" HEAD:refs/pull/51/head
git remote add origin "$remote_dir"
}
@test "checks out a merge request by numeric id" {
run git mr 51
assert_success
assert_output --partial "refs/merge-requests/51/head"
run git rev-parse --abbrev-ref HEAD
assert_output 'mr/51'
run git config --get branch.mr/51.merge
assert_output 'refs/merge-requests/51/head'
}
@test "checks out a merge request by numeric id and explicit remote" {
run git mr 51 "$remote_dir"
assert_success
assert_output --partial "refs/merge-requests/51/head"
run git rev-parse --abbrev-ref HEAD
assert_output 'mr/51'
}
@test "checks out a Forgejo/Codeberg pull request by URL" {
# git-mr only recognizes http(s) URLs, so rewrite the fake Codeberg URL
# to our local bare "remote" via insteadOf rather than needing a real
# HTTP(S) endpoint.
git config --local url."$remote_dir".insteadOf "https://codeberg.org/owner/repository.git"
run git mr "https://codeberg.org/owner/repository/pulls/51"
assert_success
assert_output --partial "refs/pull/51/head"
run git rev-parse --abbrev-ref HEAD
assert_output 'mr/51'
run git config --get branch.mr/51.merge
assert_output 'refs/pull/51/head'
run git config --get branch.mr/51.remote
assert_output 'https://codeberg.org/owner/repository.git'
}

46
tests/git-scp.bats Normal file
View file

@ -0,0 +1,46 @@
# 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' 'hello' > tracked.txt
git add tracked.txt
git commit -m 'Initial commit'
# never created: neither test below should ever touch it
git remote add fake "$BATS_TEST_TMPDIR/never-created"
printf '%s\n' 'world' >> tracked.txt
}
@test "dry-run previews the sync without changing anything" {
run git scp -n fake
assert_success
assert_line -p 'tracked.txt'
assert_line -p 'DRY RUN'
run git status --short
assert_line -p 'M tracked.txt'
run test -e "$BATS_TEST_TMPDIR/never-created"
assert_failure
}
@test "interactive mode aborts without syncing when declined" {
run bash -c 'echo n | git scp -i fake'
assert_success
assert_line -p 'Aborted, nothing synced.'
run git status --short
assert_line -p 'M tracked.txt'
run test -e "$BATS_TEST_TMPDIR/never-created"
assert_failure
}

View file

@ -1,130 +0,0 @@
import os
import subprocess
import shutil
import tempfile
from git import Repo, GitCommandError
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
GIT_EXTRAS_BIN = os.path.abspath(os.path.join(CURRENT_DIR, "..", "bin"))
GIT_EXTRAS_HELPER = os.path.abspath(os.path.join(CURRENT_DIR, "..", "helper"))
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):
self._system_tmpdir = tempfile.gettempdir()
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._git_repo = Repo.init(repo_work_dir, b="default")
self._files = []
self.change_origin_to_github()
def switch_cwd_under_repo(self):
os.chdir(self._cwd)
print(f"The current work directory has switched to {self._cwd}")
def get_cwd(self):
return self._cwd
def get_repo_dirname(self):
return self._tempdirname
def get_repo_git(self):
return self._git_repo.git
def get_file(self, index):
return self._files[index]
def get_filename(self, index):
file = self._files[index]
return file[1:]
def get_files(self):
return self._files
def create_tmp_dir(self):
tmp_dir = tempfile.mkdtemp()
return tmp_dir
def create_tmp_file(self, temp_dir=None):
if temp_dir is None:
temp_dir = self._cwd
tmp_file = tempfile.mkstemp(dir=temp_dir)
self._files.append(tmp_file[1])
return tmp_file
def remove_tmp_file(self, file_path):
os.remove(file_path)
print(f"File {file_path} has been removed")
def writefile(self, temp_file, data):
if data is None:
return
with open(temp_file, "w", encoding="utf-8") as f:
f.write(data)
def teardown(self):
shutil.rmtree(self._cwd, ignore_errors=True)
print(f"The temp directory {self._cwd} has been removed")
def invoke_extras_command(self, name, *params):
command_name = "git-" + name
print(f"Invoke the git-extras command - {command_name} at {self._cwd}")
script = [os.path.join(GIT_EXTRAS_BIN, command_name), *list(params)]
print(f"Run the script \"{' '.join(script)}\"")
return subprocess.run(script, capture_output=True)
def invoke_installed_extras_command(self, name, *params):
command_name = "git-" + name
print(f"Invoke the git-extras command - {command_name} at {self._cwd}")
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"),
]
if not os.path.exists(temp_extras_command):
whole = []
with open(temp_extras_command, "w") as t:
for helper in helpers:
with open(helper) as h:
content = h.read()
whole.extend(content.splitlines())
with open(origin_extras_command) as o:
content = o.read()
first, *rest = content.splitlines()
whole.extend(rest)
whole.insert(0, first)
t.write("\n".join(whole))
print(f"Update file {temp_extras_command}")
os.chmod(temp_extras_command, 0o775)
script = [temp_extras_command, *params]
print(f'Run the script "{script}"')
return subprocess.run(script, capture_output=True)
def change_origin(self, origin_url):
try:
self._git_repo.git.remote("add", "origin", origin_url)
except GitCommandError as err:
print(err)
self._git_repo.git.remote("set-url", "origin", origin_url)
def change_origin_to_github(self):
self.change_origin(GITHUB_ORIGIN)
def change_origin_to_gitlab(self):
self.change_origin(GITLAB_ORIGIN)
def change_origin_to_bitbucket(self):
self.change_origin(BITBUCKET_ORIGIN)

32
tests/is-git-repo.bats Normal file
View file

@ -0,0 +1,32 @@
# shellcheck shell=bash
source "$BATS_TEST_DIRNAME/test_util.sh"
setup_file() {
test_util.setup_file
}
setup() {
test_util.cd_test
}
@test "is_git_repo succeeds inside a normal (non-bare) repository" {
test_util.git_init
run bash "$BATS_TEST_DIRNAME/../helper/is-git-repo"
assert_success
}
@test "is_git_repo succeeds inside a bare repository (see #1238)" {
git init --bare --initial-branch main repo.git
cd repo.git
run bash "$BATS_TEST_DIRNAME/../helper/is-git-repo"
assert_success
}
@test "is_git_repo fails outside of a repository" {
run bash "$BATS_TEST_DIRNAME/../helper/is-git-repo"
assert_failure
assert_output 'Not a git repo!'
}

148
tests/poetry.lock generated
View file

@ -1,148 +0,0 @@
# 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"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "gitdb"
version = "4.0.11"
description = "Git Object Database"
optional = false
python-versions = ">=3.7"
files = [
{file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"},
{file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"},
]
[package.dependencies]
smmap = ">=3.0.1,<6"
[[package]]
name = "gitpython"
version = "3.1.43"
description = "GitPython is a Python library used to interact with Git repositories"
optional = false
python-versions = ">=3.7"
files = [
{file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"},
{file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"},
]
[package.dependencies]
gitdb = ">=4.0.1,<5"
[package.extras]
doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"]
test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "packaging"
version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pytest"
version = "8.1.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.1.2-py3-none-any.whl", hash = "sha256:6c06dc309ff46a05721e6fd48e492a775ed8165d2ecdf57f156a80c7e95bb142"},
{file = "pytest-8.1.2.tar.gz", hash = "sha256:f3c45d1d5eed96b01a2aea70dee6a4a366d51d38f9957768083e4fecfc77f3ef"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.4,<2.0"
[package.extras]
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "smmap"
version = "5.0.1"
description = "A pure Python implementation of a sliding window memory map manager"
optional = false
python-versions = ">=3.7"
files = [
{file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"},
{file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
]
[[package]]
name = "testpath"
version = "0.6.0"
description = "Test utilities for code working with files and commands"
optional = false
python-versions = ">= 3.5"
files = [
{file = "testpath-0.6.0-py3-none-any.whl", hash = "sha256:8ada9f80a2ac6fb0391aa7cdb1a7d11cfa8429f693eda83f74dde570fe6fa639"},
{file = "testpath-0.6.0.tar.gz", hash = "sha256:2f1b97e6442c02681ebe01bd84f531028a7caea1af3825000f52345c30285e0f"},
]
[package.extras]
test = ["pytest"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "fcf0ba3dd6655735da5c1e21ba32b94011d32bc5a30fbdaf31146d3820c1442a"

View file

@ -1,91 +0,0 @@
[tool.poetry]
name = "git-extras-tests"
version = "0.1.0"
description = "tests for git extras"
package-mode = false
authors = ["vanpipy <vanpipy@gmail.com>"]
license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
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"
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"

View file

@ -1,66 +0,0 @@
from git import GitCommandError
class TestGitAbort:
def test_init(self, temp_repo):
git = temp_repo.get_repo_git()
tmp_file = temp_repo.get_file(0)
git.branch("A")
git.branch("B")
git.checkout("A")
temp_repo.writefile(tmp_file, "a")
git.add(".")
git.commit("-m", "A")
git.checkout("B")
temp_repo.writefile(tmp_file, "b")
git.add(".")
git.commit("-m", "B")
git.status()
def test_cherry_pick(self, temp_repo):
git = temp_repo.get_repo_git()
try:
git.cherry_pick("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
temp_repo.invoke_extras_command("abort")
result = git.status()
assert "nothing to commit, working tree clean" in result
def test_merge(self, temp_repo):
git = temp_repo.get_repo_git()
try:
git.merge("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
temp_repo.invoke_extras_command("abort")
result = git.status()
assert "nothing to commit, working tree clean" in result
def test_rebase(self, temp_repo):
git = temp_repo.get_repo_git()
try:
git.rebase("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
temp_repo.invoke_extras_command("abort")
result = git.status()
assert "nothing to commit, working tree clean" in result
def test_revert(self, temp_repo):
git = temp_repo.get_repo_git()
try:
git.revert("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
temp_repo.invoke_extras_command("abort")
result = git.status()
assert "nothing to commit, working tree clean" in result

View file

@ -1,70 +0,0 @@
class TestGitAlias:
def test_init(self, temp_repo):
git = temp_repo.get_repo_git()
git.config("--global", "alias.globalalias", "status")
git.config("--global", "alias.x", "status")
git.config("--local", "alias.localalias", "status")
git.config("--local", "alias.y", "status")
def test_list_all(self, temp_repo):
actual = temp_repo.invoke_extras_command("alias")
actual = actual.stdout.decode()
assert "globalalias = status" in actual
assert "x = status" in actual
assert "localalias = status" in actual
assert "y = status" in actual
def test_list_all_globally(self, temp_repo):
actual = temp_repo.invoke_extras_command("alias", "--global")
actual = actual.stdout.decode()
assert "globalalias = status" in actual
def test_list_all_locally(self, temp_repo):
actual = temp_repo.invoke_extras_command("alias", "--local")
actual = actual.stdout.decode()
assert "localalias = status" in actual
def test_search_globally(self, temp_repo):
actual = temp_repo.invoke_extras_command("alias", "--global", "global")
actual = actual.stdout.decode()
assert "globalalias = status" in actual
actual = temp_repo.invoke_extras_command("alias", "--global", "local")
actual = actual.stdout.decode()
assert "" == actual
def test_search_locally(self, temp_repo):
actual = temp_repo.invoke_extras_command("alias", "--local", "local")
actual = actual.stdout.decode()
assert "localalias = status" in actual
actual = temp_repo.invoke_extras_command("alias", "--local", "global")
actual = actual.stdout.decode()
assert "" == actual
def test_get_alias_globally_and_defaultly(self, temp_repo):
actual = temp_repo.invoke_extras_command("alias", "globalalias")
actual = actual.stdout.decode()
assert "globalalias = status" in actual
def test_set_alias_globally_and_defaultly(self, temp_repo):
temp_repo.invoke_extras_command("alias", "globalalias", "diff")
actual = temp_repo.invoke_extras_command("alias")
actual = actual.stdout.decode()
assert "globalalias = diff" in actual
def test_get_alias_locally(self, temp_repo):
actual = temp_repo.invoke_extras_command("alias", "--local", "localalias")
actual = actual.stdout.decode()
assert "localalias = status" in actual
def test_set_alias_locally(self, temp_repo):
temp_repo.invoke_extras_command("alias", "--local", "localalias", "diff")
actual = temp_repo.invoke_extras_command("alias")
actual = actual.stdout.decode()
assert "localalias = diff" in actual
def test_teardown(self, temp_repo):
git = temp_repo.get_repo_git()
git.config("--global", "--unset", "alias.globalalias")
git.config("--global", "--unset", "alias.x")
git.config("--local", "--unset", "alias.localalias")
git.config("--local", "--unset", "alias.y")

View file

@ -1,75 +0,0 @@
import os
import pytest
class TestGitArchiveFile:
def test_init(self, temp_repo):
git = temp_repo.get_repo_git()
tmp_file = temp_repo.get_file(0)
temp_repo.writefile(tmp_file, "data")
git.add(".")
git.commit("-m", "test: add data")
git.tag("0.1.0", "-m", "bump: 0.1.0")
def test_archive_file_on_tags_branch(self, temp_repo):
git = temp_repo.get_repo_git()
git.checkout("-b", "tags0.1.0")
temp_repo.invoke_installed_extras_command("archive-file")
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
):
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",
)
assert filename in os.listdir()
def test_archive_file_on_any_not_tags_branch_with_default_branch(self, temp_repo):
git = temp_repo.get_repo_git()
git.checkout("default")
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")
)
assert filename in os.listdir()
def test_archive_file_on_branch_name_has_slash(self, temp_repo):
git = temp_repo.get_repo_git()
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",
)
assert filename in os.listdir()
@pytest.mark.parametrize("named_temp_repo", ["backslash\\dir"], indirect=True)
def test_archive_file_on_dirname_has_backslash(self, named_temp_repo):
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"
)
assert filename in os.listdir()
def test_archive_file_on_tag_name_has_slash(self, temp_repo):
temp_repo.switch_cwd_under_repo()
git = temp_repo.get_repo_git()
git.checkout("default")
git.tag("--delete", "0.1.0")
git.tag("0.1.0/slash", "-m", "bump: 0.1.0")
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("/", "-")
)
assert filename in os.listdir()

View file

@ -1,46 +0,0 @@
expected_authors_list = (
"test <test@git-extras.com>\ntestagain <testagain@git-extras.com>\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()
tmp_file = temp_repo.get_file(0)
temp_repo.writefile(tmp_file, "A")
git.add(".")
git.commit("-m", "test: add data A")
git.config("--local", "user.name", "testagain")
git.config("--local", "user.email", "testagain@git-extras.com")
temp_repo.writefile(tmp_file, "B")
git.add(".")
git.commit("-m", "test: add data B")
def test_output_authors_has_email_without_any_parameter(self, temp_repo):
temp_repo.get_repo_git()
temp_repo.invoke_extras_command("authors")
with open(authors_file) as f:
content = f.read()
print(content)
print(expected_authors_list)
assert content == expected_authors_list
def test_list_authors_has_email_defaultly(self, temp_repo):
temp_repo.get_repo_git()
actual = temp_repo.invoke_extras_command("authors", "--list")
actual = actual.stdout.decode()
assert actual == expected_authors_list
actual = temp_repo.invoke_extras_command("authors", "-l")
actual = actual.stdout.decode()
assert actual == expected_authors_list
def test_list_authors_has_not_email(self, temp_repo):
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
actual = temp_repo.invoke_extras_command("authors", "-l", "--no-email")
actual = actual.stdout.decode()
assert actual == expected_authors_list_without_email

View file

@ -1,244 +0,0 @@
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
if mode == "gitlab":
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
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:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
def test_browse_gitlab_file_on_mac(self, temp_repo):
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:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
def test_browse_bitbucket_file_on_mac(self, temp_repo):
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:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
def test_browse_github_file_on_git_bash_on_window(self, temp_repo):
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:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
def test_browse_gitlab_file_on_git_bash_on_window(self, temp_repo):
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:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
def test_browse_bitbucket_file_on_git_bash_on_window(self, temp_repo):
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:
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
def test_browse_github_file_on_WSL_with_microsoft_key(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called(["-NoProfile", "start", expected_url])
def test_browse_gitlab_file_on_WSL_with_microsoft_key(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called(["-NoProfile", "start", expected_url])
def test_browse_bitbucket_file_on_WSL_with_microsoft_key(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called(["-NoProfile", "start", expected_url])
def test_browse_github_file_on_WSL_without_microsoft_key(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
def test_browse_gitlab_file_on_WSL_without_microsoft_key(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
def test_browse_bitbucket_file_on_WSL_without_microsoft_key(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
def test_browse_github_file_not_mac_or_msys_or_linux(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("github", filename, git)
openCommand.assert_called([expected_url])
def test_browse_gitlab_file_not_mac_or_msys_or_linux(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("gitlab", filename, git)
openCommand.assert_called([expected_url])
def test_browse_bitbucket_file_not_mac_or_msys_or_linux(self, temp_repo):
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,
):
temp_repo.invoke_extras_command("browse", "origin", filename)
expected_url = get_file_uri("bitbucket", filename, git)
openCommand.assert_called([expected_url])
def test_browse_github_file_with_line_number(self, temp_repo):
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,
):
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"])
def test_browse_gitlab_file_with_line_number(self, temp_repo):
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,
):
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"])
def test_browse_bitbucket_file_with_line_number(self, temp_repo):
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,
):
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)
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):
temp_repo.get_repo_git()
filename = temp_repo.get_filename(0)
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]])

View file

@ -1,166 +0,0 @@
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"
if mode == "gitlab":
return "https://gitlab.com/tj/git-extras/-/pipelines"
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:
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:
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:
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:
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:
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:
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,
):
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,
):
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,
):
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,
):
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,
):
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,
):
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,
):
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,
):
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,
):
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,
):
temp_repo.invoke_extras_command("browse-ci", "origin")
openCommand.assert_called([""])

View file

@ -1,84 +0,0 @@
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

View file

@ -13,6 +13,29 @@ test_util.setup_file() {
PATH="$BATS_TEST_DIRNAME/../bin:$PATH" PATH="$BATS_TEST_DIRNAME/../bin:$PATH"
} }
test_util.install_command() {
local command_name="git-$1"
local install_dir="$BATS_FILE_TMPDIR/bin"
local target="$install_dir/$command_name"
mkdir -p "$install_dir"
{
head -n 1 "$BATS_TEST_DIRNAME/../bin/$command_name"
cat "$BATS_TEST_DIRNAME/../helper/reset-env"
cat "$BATS_TEST_DIRNAME/../helper/git-extra-utility"
if ! grep -qx "$command_name" "$BATS_TEST_DIRNAME/../not_need_git_repo"; then
cat "$BATS_TEST_DIRNAME/../helper/is-git-repo"
fi
if grep -qx "$command_name" "$BATS_TEST_DIRNAME/../need_git_commit"; then
cat "$BATS_TEST_DIRNAME/../helper/has-git-commit"
fi
tail -n +2 "$BATS_TEST_DIRNAME/../bin/$command_name"
} > "$target"
chmod 775 "$target"
PATH="$install_dir:$PATH"
}
test_util.cd_test() { test_util.cd_test() {
cd "$BATS_TEST_TMPDIR" cd "$BATS_TEST_TMPDIR"
} }