tj.git-extras/bin/git-ignore
2026-06-03 00:59:19 +03:00

263 lines
5.9 KiB
Bash
Executable file

#!/usr/bin/env bash
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
show_contents() {
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
echo "$1 gitignore: $2" && cat "$file"
else
echo "There is no $1 .gitignore yet"
fi
}
cd_to_git_root() {
local error_level="$1"
if ! git rev-parse --git-dir &>/dev/null; then
if [ "$error_level" = '--warn' ]; then
echo "Warning: Not currently in a Git repository" >&2
elif [ "$error_level" = '--error' ]; then
echo "Error: Not currently in a Git repository" >&2
exit 1
fi
fi
local result=
if result=$(git rev-parse --show-toplevel 2>/dev/null); then
cd "$result" || exit
fi
}
get_tmp_file() {
local tmpfile=
if ! git rev-parse --git-dir &>/dev/null || [ -z "$GIT_DIR" ]; then
tmpfile="$(mktemp -q "${TMPDIR:-/tmp}"/git-extras-ignore.XXXXXX 2>/dev/null)"
else
cd_to_git_root --warn &>/dev/null
tmpfile="$(mktemp -q "$GIT_DIR"/git-extras-ignore.XXXXXX 2>/dev/null)"
fi
echo "${tmpfile:-$(mktemp -q)}"
}
global_ignore() {
if ! git config --global core.excludesFile 2>/dev/null; then
if [ -f "$HOME/.gitignore" ]; then
echo "$HOME/.gitignore"
else
echo "${XDG_CONFIG_HOME:-$HOME/.config}/git/ignore"
fi
fi
}
show_global() {
show_contents Global "$(global_ignore)"
}
add_global() {
local global_gitignore
global_gitignore="$(global_ignore)"
if [ -z "$global_gitignore" ]; then
echo "Can't find global .gitignore."
echo ""
echo "Use 'git config --global --add core.excludesfile ~/.gitignore-global' to set the path to your global gitignore file to '~/.gitignore-global'."
echo ""
else
add_patterns "$global_gitignore" "$@"
fi
}
show_local() {
cd_to_git_root --warn
show_contents Local .gitignore
}
add_local() {
cd_to_git_root --warn
add_patterns .gitignore "$@"
}
show_private() {
cd_to_git_root --error
show_contents Private "${GIT_DIR}/info/exclude"
}
add_private() {
cd_to_git_root --error
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
add_patterns "${GIT_DIR}/info/exclude" "$@"
}
add_patterns() {
echo "Adding pattern(s) to: $1"
local file="${1/#~/$HOME}"
dir_name=$(dirname "$file")
if [ ! -d "$dir_name" ]; then
mkdir -p "$dir_name"
fi
if [ -s "$file" ]; then
# If the content of $file doesn't end with a newline, add one
test "$(tail -c 1 "$file")" != "" && echo "" >> "$file"
fi
for pattern in "${@:2}"; do
echo "... adding '$pattern'"
(test -f "$file" && test "$pattern" && grep -q -F -x -- "$pattern" "$file") || echo "$pattern" >> "$file"
done
}
# TODO: create a file if does not exist??
edit_file() {
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
echo "Editing $1 gitignore ($2)..." && $(git var GIT_EDITOR) "$file"
echo "Done."
else
echo "There is no $1 .gitignore yet." >&2
exit 1
fi
}
_usage() {
# TODO
echo "Under construction."
}
remove_patterns() {
declare -a args
local tmpfile
args=( "${@:3}" )
tmpfile="$(get_tmp_file)"
local file="${2/#~/$HOME}"
if [ -f "$file" ]; then
echo "Removing patterns from $1 gitignore ($2)..."
while read -r line; do
local drop=
for pattern in "${args[@]}"; do
test "$pattern" = "$line" && drop="yes"
done
if [ -z "$drop" ]; then
echo "$line" >> "$tmpfile"
else
echo "Removed $line pattern."
fi
done < "$file"
mv "$tmpfile" "$file" # tmp file created in get_tmp_file 'deleted'
echo "Done."
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 "---------------------------------"
show_local
echo "---------------------------------"
show_private
else
# parse flags and args
level="none"
action="none"
declare -a args
while [ "$1" != "" ]; 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
;;
global)
global_gitignore="$(global_ignore)"
edit_file global "$global_gitignore"
;;
private)
cd_to_git_root --error
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
edit_file private "${GIT_DIR}/info/exclude"
;;
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[@]}"
;;
global)
global_gitignore="$(global_ignore)"
remove_patterns global "$global_gitignore" "${args[@]}"
;;
private)
cd_to_git_root --error
remove_patterns private "${GIT_DIR}/info/exclude" "${args[@]}"
;;
esac
exit 0
fi
fi