#!/usr/bin/env bash 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 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 } 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)" && eval "$(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}" ) 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 "---------------------------------" show_local 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 fi