#!/usr/bin/env bash # # Change files modification time to their last commit date # # Bash unofficial strict mode set -euo pipefail IFS=$'\n\t' if [[ "${1:-}" == "--newer" ]]; then op=le shift else op=eq fi # BSD systems if date -j &>/dev/null; then stat_flags="-f %m" date_flags="-r" else # Non-BSD systems stat_flags="-c %Y" date_flags="-d@" fi bash_opts=() if bash --help 2>&1 | grep -q -- '--noprofile'; then bash_opts+=(--noprofile) fi if bash --help 2>&1 | grep -q -- '--norc'; then bash_opts+=(--norc) fi status_opts=(--porcelain --short) # %ct: committer date, UNIX timestamp / %at: author date, UNIX timestamp log_opts=(--format='%ct') if git status --help 2>&1 | grep -q -- "--no-renames"; then status_opts+=(--no-renames) log_opts+=(--no-renames) fi if git status --help 2>&1 | grep -q -- "--untracked-files"; then status_opts+=(--untracked-files=no) fi if git status --help 2>&1 | grep -q -- "--ignored"; then status_opts+=(--ignored=no) fi prefix="$(git rev-parse --show-prefix) " strip="${#prefix}" tmpfile=$(mktemp) # shellcheck disable=SC2064 trap "rm -f '${tmpfile}'" 0 awk_flags=( -F'\t' -v date_flags="${date_flags}" -v op="${op}" -v stat_flags="${stat_flags}" -v strip="${strip}" -v tmpfile="${tmpfile}" ) # sanity check, not required: if awk --help 2>&1 | grep -q -- '--posix'; then awk_flags+=(--posix) fi read -r -d '' awk_script <<"EOF" || true BEGIN { seen[""]=1 print "#!/usr/bin/env bash" print "set +e" print "t() {" print " test -e \"$2\" || return 0" printf(" test \"$(stat %s \"$2\" 2>/dev/null)\" -%s \"$1\" && return 0\n", stat_flags, op) if (date_flags == "-d@") { print " echo \"+ touch -h -d@$1 $2\"" print " touch -h -d@$1 \"$2\"" } else { print " t=$(date -r$1 \"+%Y%m%d%H%M.%S\")" print " echo \"+ touch -h -t $t $2\"" print " touch -h -t $t \"$2\"" } print "}" } FILENAME==tmpfile { skip[$1]=1 next } # skip blank lines !/^$/ { # skip deletes if (substr($1, length($1), 1) ~ /D/) { next } if (NF == 1) { ct=$1 next } $2 = substr($2, strip, length($2)- strip + 1) if ($2 in seen) { next } if ($2 in skip) { next } seen[$2]=1 # remove enclosing double quotes that git adds: if (substr($2, 1, 1) == "\"" && substr($2, length($2), 1) == "\"") { $2 = substr($2, 2, length($2) - 2) # unescape remaining double quotes gsub(/\\"/, "\"", $2) # unescape escaped backslashes gsub(/\\\\/, "\\", $2) } # escape apostrophes: ' => '\'' gsub(/'/, "'\\''", $2) printf("t %s '%s'\n", ct, $2) } EOF # prefix is stripped: git --no-pager status "${status_opts[@]}" . \ | cut -c 4- >"${tmpfile}" # prefix is not stripped: git --no-pager log --raw --no-merges "${log_opts[@]}" . \ | awk "${awk_flags[@]}" "${awk_script}" "${tmpfile}" - \ | BASH_ENV='' bash "${bash_opts[@]}" -