#!/bin/bash
# List files which exist in the working tree and are ignored by this
# repository's own rules: any .gitignore file, plus $GIT_DIR/info/exclude.
#
# Unlike `git status --ignored`, the global core.excludesfile is not consulted
# unless --global is given, so the output only contains paths this repository
# chose to ignore.
#
# Works inside a linked worktree, where .git is a file and info/exclude lives in
# the common git directory rather than next to the worktree.
#
# Usage: git-ls-ignored [<options>] [--] [<pathspec>...]
#
#   -d, --directory  list an ignored directory by name instead of its contents
#   -g, --global     also apply the global core.excludesfile
#   -z               terminate entries with NUL instead of newline
#   -h, --help       show this message
#
# Paths are relative to the top of the working tree. Without a <pathspec> the
# whole working tree is listed, not just the current directory.

# Print the comment block above, minus the shebang, as the help text.
usage() {
  awk 'NR == 1 { next } /^#/ { sub(/^# ?/, ""); print; next } { exit }' "$0"
}

global=false
options=()
paths=()

while [ $# -gt 0 ]; do
  case "$1" in
    -d | --directory) options+=(--directory) ;;
    -g | --global) global=true ;;
    -z) options+=(-z) ;;
    -h | --help)
      usage
      exit 0
      ;;
    --)
      shift
      paths+=("$@")
      break
      ;;
    -*)
      echo "error: unknown option: $1" >&2
      usage >&2
      exit 1
      ;;
    *) paths+=("$1") ;;
  esac
  shift
done

# In a worktree .git is a file pointing at .git/worktrees/<name>, which has no
# info/exclude of its own; Git reads the one in the common directory, so that is
# the file to point --exclude-from at. The common directory is reported relative
# to the current directory unless it happens to be absolute.
common_dir=$(git rev-parse --git-common-dir) || exit $?
case "$common_dir" in
  /*) ;;
  *) common_dir="$PWD/$common_dir" ;;
esac

if $global; then
  # .gitignore, info/exclude and core.excludesfile.
  excludes=(--exclude-standard)
else
  excludes=(--exclude-per-directory=.gitignore)
  # --exclude-from is fatal on a missing file, and info/exclude is optional.
  if [ -f "$common_dir/info/exclude" ]; then
    excludes+=("--exclude-from=$common_dir/info/exclude")
  fi
fi

git ls-files --others --ignored --full-name "${excludes[@]}" "${options[@]}" \
  -- "${paths[@]:-:/}"
