Compare commits

..

No commits in common. "main" and "7.5.0" have entirely different histories.
main ... 7.5.0

49 changed files with 1487 additions and 1746 deletions

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code.
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: 'Get Changed Files'
@ -26,26 +26,7 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BEFORE_SHA: "${{ github.event.before }}"
- 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'
- uses: 'actions/setup-go@v6'
with:
go-version: '1.20'
- name: 'Install EditorConfig Lint'
@ -59,25 +40,63 @@ jobs:
run: ./scripts/checkstyle.py
- name: Shellcheck
run: shellcheck --severity=error bin/* ./*.sh
- name: Lint and format Python with Ruff
uses: astral-sh/ruff-action@v3
typo:
runs-on: ubuntu-latest
steps:
- name: Check out code.
uses: actions/checkout@v7
- name: Install codespell
run: python3 -m pip install codespell==2.4
uses: actions/checkout@v6
- name: Install poetry
run: pip install poetry
- 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
run: |
git grep --cached -l '' . | \
cd tests
git grep --cached -l '' .. | \
grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \
xargs codespell --ignore-words=.github/.ignore_words
xargs poetry run codespell --ignore-words=../.github/.ignore_words
test-pytest:
name: 'Test with Pytest'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install poetry
run: pip install poetry
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'poetry'
cache-dependency-path: "tests/pyproject.toml"
python-version-file: "tests/pyproject.toml"
- name: Install Python Dependencies
run: |
cd tests || exit
poetry install --only test
- name: Test with Pytest
run: |
cd tests
poetry run pytest
test-bats:
name: 'Test with Bats'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Bats
@ -101,7 +120,7 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- name: Check out code.
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Linux Install
if: matrix.platform == 'ubuntu-latest'
run: sudo apt-get install -y bsdmainutils

View file

@ -11,7 +11,7 @@ jobs:
runs-on: "ubuntu-latest"
if: github.repository_owner == 'tj'
steps:
- uses: "actions/stale@v11"
- uses: "actions/stale@v10"
with:
close-pr-message: "This PR was closed because it has been stalled for 365 days with no activity. Feel free to make a new PR if you wish to continue"
days-before-pr-stale: 350

View file

@ -18,7 +18,6 @@
- [`git cp`](#git-cp)
- [`git create-branch`](#git-create-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-squashed-branches`](#git-delete-squashed-branches)
- [`git delete-submodule`](#git-delete-submodule)
@ -933,21 +932,6 @@ Delete local and remote tag `name`:
$ 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
Deletes branches that are listed in `git branch --merged`.
@ -1203,14 +1187,6 @@ 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.
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
Copy unstaged files to remote. Useful when you want to make quick test without making any commits
@ -1428,8 +1404,8 @@ $ git reset-file .htaccess dc82b19
## git mr
Checks out a merge request from GitLab, or a pull request from Forgejo/Codeberg.
Usage: `git mr <ID|URL> [REMOTE]`. Default remote is `origin`.
Checks out a merge request from GitLab. Usage: `git mr <ID|URL> [REMOTE]`.
Default remote is `origin`.
``` bash
$ git mr 51
@ -1447,15 +1423,6 @@ From gitlab.com:owner/repository
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
`mr/` branches. Ensure current branch is not one.

View file

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

View file

@ -1,60 +0,0 @@
#!/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)
if [ -n "$branches" ]
then
echo "$branches" | xargs git branch -d
echo "$branches" | xargs -n 1 git branch -d
fi

View file

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

View file

@ -2,26 +2,6 @@
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() {
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
@ -114,66 +94,6 @@ add_patterns() {
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
show_global
echo "---------------------------------"
@ -181,105 +101,22 @@ if test $# -eq 0; then
echo "---------------------------------"
show_private
else
# parse flags and args
level="none"
action="none"
declare -a args
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
_usage
exit 0
;;
-l|--local)
level="local"
;;
-g|--global)
level="global"
;;
-p|--private)
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
case "$1" in
-l|--local)
test $# -gt 1 && add_local "${@:2}" && echo
show_local
;;
-g|--global)
test $# -gt 1 && add_global "${@:2}" && echo
show_global
;;
-p|--private)
test $# -gt 1 && add_private "${@:2}" && echo
show_private
;;
*)
add_local "$@"
;;
esac
fi

View file

@ -7,8 +7,6 @@ if [ -z "${1-}" ] ; then
exit 1
fi
remote_ref_kind=merge-requests
if test "$1" = "clean"; then
git for-each-ref refs/heads/mr/* --format='%(refname)' | while read -r ref; do
git branch -D "${ref#refs/heads/}"
@ -17,19 +15,13 @@ if test "$1" = "clean"; then
elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then
remote=${BASH_REMATCH[1]}.git
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
id=$1
remote=${2:-origin}
fi
branch=mr/$id
remote_ref=refs/$remote_ref_kind/$id/head
remote_ref=refs/merge-requests/$id/head
git fetch -fu "$remote" "$remote_ref:$branch"
git checkout "$branch"
git config --local --replace "branch.$branch.merge" "$remote_ref"

View file

@ -77,17 +77,6 @@ function _sanitize()
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"
shift
@ -105,116 +94,46 @@ function scp_and_stage
list=$(git ls-files "$@")" "$(git ls-files -o "$@")
elif [ -n "$refhead" ]
then
[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
git diff --stat "$refhead"
list=$(git diff "$refhead" --name-only)
else
[ "$verbose" -eq 1 ] && git --no-pager diff
git diff
list=$(git diff --name-only)
fi
deleted=$(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" ]
then
local _TMP=${0///}
# shellcheck disable=SC2086
echo "$list" > "$_TMP"
if [ "$dry_run" -eq 1 ] || _sanitize $list
then
_info "Pushing to $remote ($(git config "remote.$remote.url"))"
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
echo "$list" > "$_TMP" &&
_sanitize $list &&
_info "Pushing to $remote ($(git config "remote.$remote.url"))" &&
rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" &&
git add --force $list &&
rm "$_TMP"
fi
deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done)
if [ -n "$deleted" ]
then
COLOR_RED
echo Deleted remote files
if [ "$dry_run" -eq 1 ]
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"
[ -n "$deleted" ] &&
COLOR_RED &&
echo Deleted remote files &&
ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted" &&
echo "$deleted"
COLOR_RESET
}
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"
shift
local _TMP=${0///}
echo "$@" > "$_TMP"
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")/" ./ &&
echo "$@" > "$_TMP" &&
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
rm "$_TMP"
}
@ -229,14 +148,9 @@ function _usage()
{
echo "Usage:
git scp -h|help|?
git scp [options] <remote> [ref|file..] # scp and stage your files to specified remote
git scp [options] <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
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)
git scp <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 rscp <remote> [<file|directory>] # copy <remote> files to current working directory
"
case $1 in

View file

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

View file

@ -213,11 +213,6 @@ _git-delete-branch() {
__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() {
_arguments \
':branch-name:__gitex_branch_names'
@ -317,11 +312,9 @@ _git-guilt() {
_git-ignore() {
_arguments -C \
'(--local -l)'{--local,-l}'[set context to local gitignore]' \
'(--global -g)'{--global,-g}'[set context to global 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]' \
'(--local -l)'{--local,-l}'[show local gitignore]' \
'(--global -g)'{--global,-g}'[show global gitignore]' \
'(--private -p)'{--private,-p}'[show repo gitignore]' \
'*:filename:_files'
}
@ -428,7 +421,6 @@ zstyle ':completion:*:*:git:*' user-commands $existing_user_commands \
count:'show commit count' \
create-branch:'create branches' \
delete-branch:'delete branches' \
delete-gone-branches:'delete branches whose remote is gone' \
delete-merged-branches:'delete merged branches' \
delete-squashed-branches:'delete squashed branches' \
delete-submodule:'delete submodules' \
@ -445,7 +437,7 @@ zstyle ':completion:*:*:git:*' user-commands $existing_user_commands \
graft:'merge and destroy a given branch' \
guilt:'calculate change between two revisions' \
ignore-io:'get sample gitignore file' \
ignore:'Modify or display .gitignore files' \
ignore:'add .gitignore patterns' \
info:'returns information on current repository' \
local-commits:'list local commits' \
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" \
"guilt:calculate change between two revisions" \
"ignore-io:Get sample gitignore file" \
"ignore:Modify or display .gitignore files" \
"ignore:Add .gitignore patterns" \
"info:Returns information on current repository" \
"local-commits:List local commits" \
"lock:Lock a file excluded from version control" \
@ -162,11 +162,9 @@ 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 h -d 'output usage information'
# ignore
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 'set context to global 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'
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 g -l global -d 'show global gitignore'
complete -c git -f -n '__fish_git_using_command ignore' -s p -l private -d 'show repo gitignore'
# ignore-io
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

View file

@ -3,9 +3,7 @@
#
is_git_repo() {
# --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
git rev-parse --show-toplevel > /dev/null 2>&1
result=$?
if test $result != 0; then
>&2 echo 'Not a git repo!'

View file

@ -39,34 +39,13 @@ goto :defaultpath
rem remove the last slash
SET bindir=%bindir:~0,-1%
for %%G in ("%bindir%") do set installdir=%%~dpG
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
)
set PREFIX=%installdir%mingw64
goto :foundprefix
:defaultpath
:: default for Git for Windows 2.x
if exist "%ProgramFiles%\Git" (
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
)
set PREFIX=%ProgramFiles%\Git\mingw64
)
:foundprefix
@ -76,11 +55,9 @@ IF NOT "%~1"=="" (
REM just supplying the git dir is enough...
if exist "%~1\mingw64" (
set PREFIX=%~1\mingw64
) else if exist "%~1\clangarm64" (
set PREFIX=%~1\clangarm64
) else (
echo Using git install path "%~1" as PREFIX, please make sure it's really a
echo path to the mingw64 or clangarm64 directory...
echo path to the mingw64 directory...
echo.
SET PREFIX=%~1
)
@ -95,14 +72,12 @@ set GIT_INSTALL_DIR=!GIT_INSTALL_DIR:"=!
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%\clangarm64" (
echo No mingw64 or clangarm64 folder found in %GIT_INSTALL_DIR%.
echo.
echo Please supply a proper "Git for Windows 2.x" install path:
echo "install.cmd c:\[git-install-path]"
set ERROR=1
goto :exit
)
echo No mingw64 folder found in %GIT_INSTALL_DIR%.
echo.
echo Please supply a proper "Git for Windows 2.x" install path:
echo "install.cmd c:\[git-install-path]"
set ERROR=1
goto :exit
)
echo Installing to %PREFIX%
@ -141,7 +116,7 @@ FOR /R "%GITEXTRAS%\bin" %%i in (*.*) DO (
TYPE "%GITEXTRAS%\helper\reset-env" >> "%PREFIX%\bin\%%~ni"
TYPE "%GITEXTRAS%\helper\git-extra-utility" >> "%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
MORE /E +2 "%GITEXTRAS%\bin\%%~ni" >> "%PREFIX%\bin\%%~ni"
)
@ -152,7 +127,7 @@ FOR %%i in (%COMMANDS_WITHOUT_REPO%) DO (
ECHO #^^!/usr/bin/env bash > "%PREFIX%\bin\%%i"
TYPE "%GITEXTRAS%\helper\reset-env" >> "%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
MORE /E +2 "%GITEXTRAS%\bin\%%i" >> "%PREFIX%\bin\%%i"
)

View file

@ -1,54 +0,0 @@
.\" 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

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

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

@ -1,183 +0,0 @@
.\" 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,9 +111,11 @@
<li>
<strong><a class="man-ref" href="git-authors.html">git-authors<span class="s">(1)</span></a></strong> Generate authors report</li>
<li>
<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>
<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>
</li>
<li>
<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>
<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>
</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>
<li>
@ -271,7 +273,7 @@
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>May 2026</li>
<li class='tc'>February 2026</li>
<li class='tr'>git-extras(1)</li>
</ol>

View file

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

View file

@ -1,88 +1,77 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
.TH "GIT\-IGNORE" "1" "June 2026" "" "Git Extras"
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "GIT\-IGNORE" "1" "April 2018" "" "Git Extras"
.
.SH "NAME"
\fBgit\-ignore\fR \- Modify or display \.gitignore files
\fBgit\-ignore\fR \- Add \.gitignore patterns
.
.SH "SYNOPSIS"
\fBgit\-ignore\fR [<context>] [<action>] [\-\-] [<pattern> [<pattern>]\|\.\|\.\|\.]
\fBgit\-ignore\fR [<context>] [<pattern> [<pattern>]\.\.\.]
.
.SH "DESCRIPTION"
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
Adds the given _pattern_s to a \.gitignore file if it doesn\'t already exist\.
.
.SH "OPTIONS"
<context>
.
.P
\-l, \-\-local
.
.P
Sets the context to the \.gitignore file in the current working directory\. (default)
.
.P
\-g, \-\-global
.
.P
Sets the context to the global gitignore file for the current user\.
.
.P
\-p, \-\-private
.
.P
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
<pattern>
.
.P
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"
Pattern format as described in the git manual
.
.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 ""\.
.
.IP "\(bu" 4
A line starting with # serves as a comment\. For example, "# This is a comment"
.
.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
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
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
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
A leading slash matches the beginning of the pathname\. For example, "/*\.c" matches "cat\-file\.c" but not "mozilla\-sha1/sha1\.c"\.
.
.IP "" 0
.
.SH "EXAMPLES"
All arguments are optional so calling git\-ignore alone will display global, local and private gitignore files in order:
All arguments are optional so calling git\-ignore alone will display first the global then the local gitignore files:
.
.IP "" 4
.
.nf
$ git ignore
Global gitignore: /home/alice/\.gitignore
# Numerous always\-ignore extensions
@ -97,101 +86,92 @@ Global gitignore: /home/alice/\.gitignore
*\.sass\-cache
# OS or Editor folders
\&\.DS_Store
\&\.Trashes
\&\._*
\.DS_Store
\.Trashes
\._*
Thumbs\.db
\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
Local gitignore: \.gitignore
\&\.cache
\&\.project
\&\.settings
\&\.tmproj
\.cache
\.project
\.settings
\.tmproj
nbproject
\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
Private gitignore: \.git/info/exclude
notes\.txt
test\.sh
\&\.env\.local
config\.json
.
.fi
.
.IP "" 0
.
.P
If you only want to see the global context use the \-\-global argument (for local use \-\-local, for private use \-\-private):
If you only want to see the global context use the \-\-global argument (for local use \-\-local):
.
.IP "" 4
.
.nf
$ git ignore
Global gitignore: /home/alice/\.gitignore
\&\.DS_Store
\&\.Trashes
\&\._*
\.DS_Store
\.Trashes
\._*
Thumbs\.db
.
.fi
.
.IP "" 0
.
.P
To quickly append a new pattern to the default/local context simply:
.
.IP "" 4
.
.nf
$ git ignore *\.log
Adding pattern(s) to: \.gitignore
\|\.\|\.\|\. adding '*\.log'
\.\.\. adding \'*\.log\'
.
.fi
.
.IP "" 0
.
.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\.
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\.
.
.IP "" 4
.
.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 ''
\|\.\|\.\|\. adding '# Temporary files'
\|\.\|\.\|\. adding '*\.tmp'
\|\.\|\.\|\. adding '*\.log'
\|\.\|\.\|\. adding 'tmp/*'
\|\.\|\.\|\. adding ''
\|\.\|\.\|\. adding '# Files I'd like to keep'
\|\.\|\.\|\. adding '!work'
\|\.\|\.\|\. adding ''
\.\.\. adding \'\'
\.\.\. adding \'# Temporary files\'
\.\.\. adding \'index\.tmp\'
\.\.\. adding \'*\.log\'
\.\.\. adding \'tmp/*\'
\.\.\. adding \'\'
\.\.\. adding \'# Files I\'d like to keep\'
\.\.\. adding \'!work\'
\.\.\. adding \'\'
Local gitignore: \.gitignore
# Temporary files
*\.tmp
index\.tmp
*\.log
tmp/*
# Files I'd like to keep
# Files I\'d like to keep
!work
.
.fi
.
.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"
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>
Written by Tj Holowaychuk <\fItj@vision\-media\.ca\fR> and Tema Bolshakov <\fItweekane@gmail\.com\fR> and Nick Lombard <\fIgithub@jigsoft\.co\.za\fR>
.
.SH "REPORTING BUGS"
<\fIhttps://github\.com/tj/git\-extras/issues\fR>
.
.SH "SEE ALSO"
<\fIhttps://github\.com/tj/git\-extras\fR>

View file

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' content='text/html;charset=utf-8'>
<meta name='generator' content='Ronn-NG/v0.10.1 (http://github.com/apjanke/ronn-ng/tree/0.10.1)'>
<title>git-ignore(1) - Modify or display .gitignore files</title>
<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-ignore(1) - Add .gitignore patterns</title>
<style type='text/css' media='all'>
/* style: man */
body#manpage {margin:0}
@ -69,114 +69,57 @@
<li class='tr'>git-ignore(1)</li>
</ol>
<h2 id="NAME">NAME</h2>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>git-ignore</code> - <span class="man-whatis">Modify or display .gitignore files</span>
<code>git-ignore</code> - <span class="man-whatis">Add .gitignore patterns</span>
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>git-ignore</code> [&lt;context&gt;] [&lt;action&gt;] [--] [&lt;pattern&gt; [&lt;pattern&gt;]...]</p>
<p><code>git-ignore</code> [&lt;context&gt;] [&lt;pattern&gt; [&lt;pattern&gt;]...]</p>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<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>
<p>Adds the given _pattern_s to a .gitignore file if it doesn't already exist.</p>
<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;action&gt;</p>
<p> &lt;pattern&gt;</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>
<p> A space delimited list of patterns to append to the file in context.</p>
<h3 id="PATTERN-FORMAT">PATTERN FORMAT</h3>
<p>Pattern format as described in the git manual</p>
<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>
<p>A line starting with # serves as a comment. For example, "# This is a comment"</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>
<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><p>A line starting with # serves as a comment. For example, "# This is a comment"</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>
<h2 id="EXAMPLES">EXAMPLES</h2>
<p>All arguments are optional so calling git-ignore alone will display global, local and private gitignore files in order:</p>
<p> All arguments are optional so calling git-ignore alone will display first the global then the local gitignore files:</p>
<pre><code>$ git ignore
Global gitignore: /home/alice/.gitignore
@ -203,15 +146,9 @@ Local gitignore: .gitignore
.settings
.tmproj
nbproject
---------------------------------
Private gitignore: .git/info/exclude
notes.txt
test.sh
.env.local
config.json
</code></pre>
<p>If you only want to see the global context use the --global argument (for local use --local, for private use --private):</p>
<p>If you only want to see the global context use the --global argument (for local use --local):</p>
<pre><code>$ git ignore
Global gitignore: /home/alice/.gitignore
@ -228,14 +165,14 @@ Adding pattern(s) to: .gitignore
... adding '*.log'
</code></pre>
<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:
<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.</p>
<pre><code>$ git ignore --local "" "# Temporary files" *.tmp "*.log" tmp/* "" "# Files I'd like to keep" '!work' ""
Adding pattern(s) to: .gitignore
... adding ''
... adding '# Temporary files'
... adding '*.tmp'
... adding 'index.tmp'
... adding '*.log'
... adding 'tmp/*'
... adding ''
@ -246,39 +183,17 @@ Adding pattern(s) to: .gitignore
Local gitignore: .gitignore
# Temporary files
*.tmp
index.tmp
*.log
tmp/*
# Files I'd like to keep
!work
</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>
<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="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>
<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;
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>
<h2 id="REPORTING-BUGS">REPORTING BUGS</h2>
@ -288,9 +203,10 @@ and Nick Lombard &lt;<a href="mailto:github@jigsoft.co.za" data-bare-link="true"
<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'>June 2026</li>
<li class='tc'>April 2018</li>
<li class='tr'>git-ignore(1)</li>
</ol>

View file

@ -1,31 +1,13 @@
git-ignore(1) -- Modify or display .gitignore files
git-ignore(1) -- Add .gitignore patterns
========================================
## SYNOPSIS
`git-ignore` [&lt;context&gt;] [&lt;action&gt;] [--] [&lt;pattern&gt; [&lt;pattern&gt;]...]
`git-ignore` [&lt;context&gt;] [&lt;pattern&gt; [&lt;pattern&gt;]...]
## DESCRIPTION
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.
Adds the given _pattern_s to a .gitignore file if it doesn't already exist.
## OPTIONS
@ -43,28 +25,10 @@ Default behavior:
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;
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 as described in the git manual
@ -79,14 +43,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).
* 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
All arguments are optional so calling git-ignore alone will display global, local and private gitignore files in order:
All arguments are optional so calling git-ignore alone will display first the global then the local gitignore files:
$ git ignore
Global gitignore: /home/alice/.gitignore
@ -113,14 +77,8 @@ Pattern format as described in the git manual
.settings
.tmproj
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, for private use --private):
If you only want to see the global context use the --global argument (for local use --local):
$ git ignore
Global gitignore: /home/alice/.gitignore
@ -135,55 +93,34 @@ To quickly append a new pattern to the default/local context simply:
Adding pattern(s) to: .gitignore
... adding '*.log'
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:
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.
$ git ignore --local "" "# Temporary files" *.tmp "*.log" tmp/* "" "# Files I'd like to keep" '!work' ""
Adding pattern(s) to: .gitignore
... adding ''
... adding '# Temporary files'
... adding '*.tmp'
... adding 'index.tmp'
... adding '*.log'
... adding 'tmp/*'
... adding ''
... adding '# Files I'd like to keep'
... adding '!work'
... adding ''
Local gitignore: .gitignore
# Temporary files
*.tmp
index.tmp
*.log
tmp/*
# Files I'd like to keep
!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
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 Kirill Dergachev &lt;<kdergachev1062@gmail.com>&gt;
and Nick Lombard &lt;<github@jigsoft.co.za>&gt;
## REPORTING BUGS

View file

@ -28,7 +28,7 @@ The name of the remote to fetch from\. Defaults to \fBorigin\fR\.
<url>
.
.P
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\.
GitLab merge request URL in the format \fBhttps://gitlab\.tld/owner/repository/merge_requests/453\fR\.
.
.SH "EXAMPLES"
This checks out merge request \fB!51\fR from remote \fBorigin\fR to branch \fBmr/51\fR\.
@ -46,22 +46,6 @@ Switched to branch \'mr/51\'
.
.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"
Written by Étienne BERSAC \fIbersace03@gmail\.com\fR from git\-pr(1)\.
.

View file

@ -94,9 +94,7 @@
<p> &lt;url&gt;</p>
<p> GitLab merge request URL in the format
<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>
<code>https://gitlab.tld/owner/repository/merge_requests/453</code>.</p>
<h2 id="EXAMPLES">EXAMPLES</h2>
@ -108,14 +106,6 @@ From gitlab.com:owner/repository
Switched to branch 'mr/51'
</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>
<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,9 +21,7 @@ git-mr(1) -- Checks out a merge request locally
&lt;url&gt;
GitLab merge request URL in the format
`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`.
`https://gitlab.tld/owner/repository/merge_requests/453`.
## EXAMPLES
@ -35,13 +33,6 @@ This checks out merge request `!51` from remote `origin` to branch `mr/51`.
* [new ref] refs/merge-requests/51/head -> 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
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
`git scp` -h|help|?
`git scp` [options] <remote> [<commits>...|<path>...]
`git rscp` [options] <remote> <path>
`git scp` <remote> [<commits>...|<path>...]
`git rscp` <remote> <path>
## DESCRIPTION
@ -29,18 +29,6 @@ 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).
-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
To sanitize files using `dos2unix` before copying files

View file

@ -18,7 +18,6 @@ git-count(1) git-count
git-cp(1) git-cp
git-create-branch(1) git-create-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-squashed-branches(1) git-delete-squashed-branches
git-delete-submodule(1) git-delete-submodule

View file

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

View file

@ -1,6 +1,6 @@
# Testing
Tests are written in Bats so coverage can be calculated for shell commands.
Originally, the tests were written in pytest. However, tests are in the process of being converted to Bats so coverage can be calculated.
## Bats Testing
@ -10,7 +10,7 @@ We require a somewhat recent version of Bats. Version v1.8.1 is tested in CI. On
bats ./tests
```
We highly recommend adding Bats tests for new features and fixes.
We highly recommend adding tests for new features and fixes.
### Code Coverage
@ -21,3 +21,49 @@ bashcov -- bats ./tests
```
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)

45
tests/conftest.py Normal file
View file

@ -0,0 +1,45 @@
# 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,7 +4,6 @@ source "$BATS_TEST_DIRNAME/test_util.sh"
setup_file() {
test_util.setup_file
test_util.install_command archive-file
}
setup() {
@ -38,7 +37,7 @@ setup() {
}
@test "archive file on any not tags branch with default branch" {
git config git-extras.default-branch main
skip "Not working as expected"
run git archive-file
assert_success
@ -59,32 +58,9 @@ setup() {
}
@test "archive file on dirname has backslash" {
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"
skip
}
@test "archive file on tag name has slash" {
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"
skip
}

View file

@ -26,16 +26,6 @@ get_ci_uri() {
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" {
get_ci_uri 'github'
local expected_url=$REPLY
@ -106,110 +96,6 @@ mock_uname() {
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" {
get_ci_uri 'gitlab'
local expected_url=$REPLY

View file

@ -32,16 +32,6 @@ get_file_uri() {
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" {
get_file_uri github ./browse_this
local expected_url=$REPLY
@ -112,148 +102,6 @@ mock_uname() {
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" {
get_file_uri gitlab ./browse_this
local expected_url=$REPLY

View file

@ -1,68 +0,0 @@
# 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'
}

View file

@ -1,46 +0,0 @@
# 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
}

130
tests/helper.py Normal file
View file

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

View file

@ -1,32 +0,0 @@
# 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 Normal file
View file

@ -0,0 +1,148 @@
# 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"

91
tests/pyproject.toml Normal file
View file

@ -0,0 +1,91 @@
[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"

66
tests/test_git_abort.py Normal file
View file

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

70
tests/test_git_alias.py Normal file
View file

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

@ -0,0 +1,75 @@
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()

46
tests/test_git_authors.py Normal file
View file

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

244
tests/test_git_browse.py Normal file
View file

@ -0,0 +1,244 @@
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]])

166
tests/test_git_browse_ci.py Normal file
View file

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

@ -0,0 +1,84 @@
from git import GitCommandError
class TestGitContinue:
@classmethod
def _init_repo(cls, repo):
git = repo.get_repo_git()
tmp_file = repo.get_file(0)
git.branch("A")
git.branch("B")
git.branch("C")
git.checkout("A")
repo.writefile(tmp_file, "a")
git.add(".")
git.commit("-m", "A")
git.checkout("B")
repo.writefile(tmp_file, "b")
git.add(".")
git.commit("-m", "B")
def test_init(self, temp_repo_clean):
TestGitContinue._init_repo(temp_repo_clean)
git = temp_repo_clean.get_repo_git()
git.status()
def test_cherry_pick(self, temp_repo_clean):
TestGitContinue._init_repo(temp_repo_clean)
git = temp_repo_clean.get_repo_git()
try:
git.cherry_pick("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
git.add(".")
temp_repo_clean.invoke_extras_command("continue")
result = git.status()
assert "nothing to commit, working tree clean" in result
def test_merge(self, temp_repo_clean):
TestGitContinue._init_repo(temp_repo_clean)
git = temp_repo_clean.get_repo_git()
try:
git.merge("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
git.add(".")
git.commit("-m", "resolve conflict")
temp_repo_clean.invoke_extras_command("continue")
result = git.status()
assert "nothing to commit, working tree clean" in result
def test_rebase(self, temp_repo_clean):
TestGitContinue._init_repo(temp_repo_clean)
git = temp_repo_clean.get_repo_git()
try:
git.rebase("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
git.add(".")
git.commit("-m", "resolve conflict")
temp_repo_clean.invoke_extras_command("continue")
result = git.status()
assert "nothing to commit, working tree clean" in result
def test_revert(self, temp_repo_clean):
TestGitContinue._init_repo(temp_repo_clean)
git = temp_repo_clean.get_repo_git()
try:
git.revert("A")
except GitCommandError as err:
print(err)
result = git.status()
assert "Unmerged path" in result
git.add(".")
git.commit("-m", "resolve conflict")
temp_repo_clean.invoke_extras_command("continue")
result = git.status()
assert "nothing to commit, working tree clean" in result

View file

@ -13,29 +13,6 @@ test_util.setup_file() {
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() {
cd "$BATS_TEST_TMPDIR"
}