1#!/usr/bin/env sh 2# SPDX-License-Identifier: GPL-2.0-or-later 3# 4# From the flashrom project but heavily modified since then. 5 6EXIT_SUCCESS=0 7EXIT_FAILURE=1 8 9# Make sure we don't get translated output 10export LC_ALL=C 11# nor local times or dates 12export TZ=UTC0 13 14# Helper functions 15git_has_local_changes() { 16 git update-index -q --refresh >/dev/null 17 ! git diff-index --quiet HEAD -- "$1" 18} 19 20git_last_commit() { 21 git log --pretty=format:"%h" -1 -- "$1" 22} 23 24git_is_file_tracked() { 25 git ls-files --error-unmatch -- "$1" >/dev/null 2>&1 26} 27 28is_file_tracked() { 29 git_is_file_tracked "$1" 30} 31 32# Tries to find a remote source for the changes committed locally. 33# This includes the URL of the remote repository including the last commit and a suitable branch name. 34# Takes one optional argument: the path to inspect 35git_url() { 36 # Note: This may not work as expected if multiple remotes are fetched from. 37 echo $(git remote -v | grep "^origin\>" | \ 38 awk '/fetch/ {print $2; exit 0}' | sed "s,^.*@,,") 39} 40 41# Returns a string indicating where others can get the current source code (excluding uncommitted changes) 42# Takes one optional argument: the path to inspect 43scm_url() { 44 local url 45 46 url="$(git_url "$1")" 47 48 echo "${url}" 49} 50 51# Retrieve timestamp since last modification. If the sources are pristine, 52# then the timestamp will match that of the SCM's most recent modification 53# date. 54timestamp() { 55 local t 56 57 # date syntaxes are manifold: 58 # gnu date [-d input]... [+FORMAT] 59 # netbsd date [-ajnu] [-d date] [-r seconds] [+format] [[[[[[CC]yy]mm]dd]HH]MM[.SS]] 60 # freebsd date [-jnu] [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...] 61 # dragonflybsd date [-jnu] [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...] 62 # openbsd date [-aju] [-d dst] [-r seconds] [+format] [[[[[[cc]yy]mm]dd]HH]MM[.SS]] [...] 63 if git_is_file_tracked "$2" ; then 64 # are there local changes? 65 if git_has_local_changes "$2" ; then 66 t=$(date -u "${1}") 67 else 68 # No local changes, get date of the last commit 69 case $(uname) in 70 # Most BSD dates do not support parsing date values from user input with -d but all of 71 # them support parsing epoch seconds with -r. Thanks to git we can easily use that: 72 NetBSD|OpenBSD|DragonFly|FreeBSD) 73 t=$(date -u -r "$(git log --pretty=format:%ct -1 -- $2)" "$1" 2>/dev/null);; 74 *) 75 t=$(date -d "$(git log --pretty=format:%cD -1 -- $2)" -u "$1" 2>/dev/null);; 76 esac 77 fi 78 else 79 t=$(date -u "$1") 80 fi 81 82 if [ -z "$t" ]; then 83 echo "Warning: Could not determine timestamp." 2>/dev/null 84 fi 85 86 # output the time, changing colons to underscores. 87 # gnu make doesn't work in directories with colons 88 echo "${t}" | tr ':' '_' 89} 90 91# Retrieve local SCM revision info. This is useful if we're working in a different SCM than upstream and/or 92# have local changes. 93local_revision() { 94 local r 95 96 if git_is_file_tracked "$1" ; then 97 r=$(git_last_commit "$1") 98 99 if git_has_local_changes "$1" ; then 100 r="$r-dirty" 101 fi 102 else 103 return ${EXIT_FAILURE} 104 fi 105 106 echo "${r}" 107} 108 109# Similar to local_revision but uses "git describe" instead of "git log" which 110# includes number of commits since most recent tag. 111tagged_revision() { 112 local r 113 114 if git_is_file_tracked "$1" ; then 115 r=$(git describe --tags --dirty) 116 else 117 return ${EXIT_FAILURE} 118 fi 119 120 echo "${r}" 121} 122 123upstream_revision() { 124 local r= 125 126 r=$(git log remotes/origin/main -1 --format=format:%h) 127 128 if [ -z "$r" ]; then 129 r="unknown" # default to unknown 130 fi 131 echo "${r}" 132} 133 134show_help() { 135 echo "Usage: 136 ${0} <command> [path] 137 138Commands 139 -h or --help 140 this message 141 -l or --local 142 local revision information including an indicator for uncommitted changes 143 -u or --upstream 144 upstream revision 145 -T or --tags 146 similar to -l, but uses \"git describe\" to obtain revision info with tags 147 -U or --url 148 URL associated with the latest commit 149 -d or --date 150 date of most recent modification 151 -t or --timestamp 152 timestamp of most recent modification 153" 154 return 155} 156 157check_action() { 158 if [ -n "$action" ]; then 159 echo "Error: Multiple actions given.">&2 160 exit ${EXIT_FAILURE} 161 fi 162} 163 164main() { 165 local query_path= 166 local action= 167 168 # The is the main loop 169 while [ $# -gt 0 ]; 170 do 171 case ${1} in 172 -h|--help) 173 action=show_help; 174 shift;; 175 -l|--local) 176 check_action $1 177 action=local_revision 178 shift;; 179 -T|--tags) 180 check_action $1 181 action=tagged_revision 182 shift;; 183 -u|--upstream) 184 check_action $1 185 action=upstream_revision 186 shift;; 187 -U|--url) 188 check_action $1 189 action=scm_url 190 shift;; 191 -d|--date) 192 check_action $1 193 action="timestamp +%Y-%m-%d" # refrain from suffixing 'Z' to indicate it's UTC 194 shift;; 195 -t|--timestamp) 196 check_action $1 197 action="timestamp +%Y-%m-%dT%H:%M:%SZ" # There is only one valid time format! ISO 8601 198 shift;; 199 -*) 200 show_help; 201 echo "Error: Invalid option: ${1}" 202 exit ${EXIT_FAILURE};; 203 *) 204 if [ -z "$query_path" ] ; then 205 if [ ! -e "$1" ] ; then 206 echo "Error: Path \"${1}\" does not exist.">&2 207 exit ${EXIT_FAILURE} 208 fi 209 query_path=$1 210 else 211 echo "Warning: Ignoring over-abundant parameter: \"${1}\"">&2 212 fi 213 shift;; 214 esac; 215 done 216 217 # default to current directory (usually equals the whole repository) 218 if [ -z "$query_path" ] ; then 219 query_path=. 220 fi 221 if ! is_file_tracked "$query_path" ; then 222 echo "Warning: Path \"${query_path}\" is not under version control.">&2 223 fi 224 if [ -z "$action" ] ; then 225 show_help 226 echo "Error: No actions specified" 227 exit ${EXIT_FAILURE} 228 fi 229 230 $action "$query_path" 231} 232 233main $@ 234