getrevision.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. #!/bin/sh
  2. #
  3. # This file is part of the flashrom project.
  4. #
  5. # Copyright (C) 2005 coresystems GmbH <stepan@coresystems.de>
  6. # Copyright (C) 2009,2010 Carl-Daniel Hailfinger
  7. # Copyright (C) 2010 Chromium OS Authors
  8. # Copyright (C) 2013-2016 Stefan Tauner
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with this program; if not, write to the Free Software
  22. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. #
  24. EXIT_SUCCESS=0
  25. EXIT_FAILURE=1
  26. # Make sure we don't get translated output
  27. export LC_ALL=C
  28. # nor local times or dates
  29. export TZ=UTC0
  30. # List of important upstream branches...
  31. upstream_branches="stable staging"
  32. upstream_url="https://flashrom.org/git/flashrom.git"
  33. upstream_patterns="github\.com.flashrom/flashrom(\.git)?|flashrom\.org.git/flashrom(\.git)?"
  34. upcache_prefix="refs/flashrom_org/"
  35. # Generate upcache_refs
  36. for b in $upstream_branches ; do
  37. upcache_refs="$upcache_refs ${upcache_prefix}$b"
  38. done
  39. # We need to update our upstream information sometimes so that we can create detailed version information.
  40. # To that end the code below fetches parts of the upstream repository via https.
  41. # This takes about one second or less under normal circumstances.
  42. #
  43. # It can be called manually, but is usually called via
  44. # - the Makefile (implicitly via revision()) when there is no upstream information in any existing remote
  45. forced_update() {
  46. local rev_remote_refs
  47. for ref in $upcache_refs ; do
  48. rev_remote_refs="$rev_remote_refs +${ref##*/}:$ref"
  49. done
  50. git fetch -q "$upstream_url" --tags $rev_remote_refs && echo "Success."
  51. }
  52. update() {
  53. offline=$(git config flashrom.offline-builds 2>/dev/null)
  54. if [ -z "$offline" ]; then
  55. echo "To produce useful version information the build process needs access to the commit
  56. history from an upstream repository. If no git remote is pointing to one we
  57. can store the necessary information out of sight and update it on every build.
  58. To enable this functionality and fetch the upstream commits from $upstream_url
  59. please execute 'git config flashrom.offline-builds false' or add one of the
  60. upstream repositories as git remote to rely on that information.
  61. However, if you want to work completely offline and generate possibly meaningless
  62. version strings then disable it with 'git config flashrom.offline-builds true'
  63. You can force updating the local commit cache with '$0 --forced-update'">&2
  64. return 1
  65. elif [ "x$offline" = "xfalse" ]; then
  66. echo "Fetching commit history from upstream repository $upstream_url
  67. To disable any network activity execute 'git config flashrom.offline-builds true'.">&2
  68. forced_update >/dev/null
  69. else
  70. echo "Fetching commit history from upstream is disabled - version strings might be misleading.
  71. To ensure proper version strings and allow network access run 'git config flashrom.offline-builds false'.">&2
  72. fi
  73. return 0
  74. }
  75. # Helper functions
  76. # First argument is the path to inspect (usually optional; w/o it the whole repository will be considered)
  77. git_has_local_changes() {
  78. git update-index -q --refresh >/dev/null
  79. ! git diff-index --quiet HEAD -- "$1"
  80. }
  81. git_last_commit() {
  82. # git rev-parse --short HEAD would suffice if repository as a whole is of interest (no $1)
  83. git log --pretty=format:"%h" -1 -- "$1"
  84. }
  85. git_is_file_tracked() {
  86. git ls-files --error-unmatch -- "$1" >/dev/null 2>&1
  87. }
  88. is_file_tracked() {
  89. git_is_file_tracked "$1"
  90. }
  91. # Tries to find a remote source for the changes committed locally.
  92. # This includes the URL of the remote repository including the last commit and a suitable branch name.
  93. # Takes one optional argument: the path to inspect
  94. git_url() {
  95. last_commit=$(git_last_commit "$1")
  96. # get all remote branches containing the last commit (excluding origin/HEAD and git-svn branches/tags)
  97. branches=$(git branch -r --contains $last_commit | sed '/\//!d;/.*->.*/d;s/[\t ]*//')
  98. if [ -z "$branches" ] ; then
  99. echo "No remote branch contains a suitable commit">&2
  100. return
  101. fi
  102. # find "nearest" branch
  103. local mindiff=9000
  104. local target=
  105. for branch in $branches ; do
  106. curdiff=$(git rev-list --count $last_commit..$branch)
  107. if [ $curdiff -ge $mindiff ] ; then
  108. continue
  109. fi
  110. mindiff=$curdiff
  111. target=$branch
  112. done
  113. echo "$(git ls-remote --exit-code --get-url ${target%/*}) ${target#*/}"
  114. }
  115. # Returns a string indicating where others can get the current source code (excluding uncommitted changes)
  116. # Takes one optional argument: the path to inspect
  117. scm_url() {
  118. local url=
  119. if git_is_file_tracked "$1" ; then
  120. url="$(git_url "$1")"
  121. else
  122. return ${EXIT_FAILURE}
  123. fi
  124. echo "${url}"
  125. }
  126. # Retrieve timestamp since last modification. If the sources are pristine,
  127. # then the timestamp will match that of the SCM's most recent modification
  128. # date.
  129. timestamp() {
  130. local t
  131. # date syntaxes are manifold:
  132. # gnu date [-d input]... [+FORMAT]
  133. # netbsd date [-ajnu] [-d date] [-r seconds] [+format] [[[[[[CC]yy]mm]dd]HH]MM[.SS]]
  134. # freebsd date [-jnu] [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...]
  135. # dragonflybsd date [-jnu] [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...]
  136. # openbsd date [-aju] [-d dst] [-r seconds] [+format] [[[[[[cc]yy]mm]dd]HH]MM[.SS]] [...]
  137. if git_is_file_tracked "$2" ; then
  138. # are there local changes?
  139. if git_has_local_changes "$2" ; then
  140. t=$(date -u "${1}")
  141. else
  142. # No local changes, get date of the last commit
  143. case $(uname) in
  144. # Most BSD dates do not support parsing date values from user input with -d but all of
  145. # them support parsing epoch seconds with -r. Thanks to git we can easily use that:
  146. NetBSD|OpenBSD|DragonFly|FreeBSD)
  147. t=$(date -u -r "$(git log --pretty=format:%ct -1 -- $2)" "$1" 2>/dev/null);;
  148. *)
  149. t=$(date -d "$(git log --pretty=format:%cD -1 -- $2)" -u "$1" 2>/dev/null);;
  150. esac
  151. fi
  152. else
  153. t=$(date -u "$1")
  154. fi
  155. if [ -z "$t" ]; then
  156. echo "Warning: Could not determine timestamp." 2>/dev/null
  157. fi
  158. echo "${t}"
  159. }
  160. tag() {
  161. local t=
  162. if git_is_file_tracked "$1" ; then
  163. local sha=$(git_last_commit "$1")
  164. t=$(git describe --abbrev=0 "$sha")
  165. fi
  166. if [ -z "$t" ]; then
  167. t="unknown" # default to unknown
  168. fi
  169. echo "${t}"
  170. }
  171. find_upremote() {
  172. # Try to find upstream's remote name
  173. for remote in $(git remote) ; do
  174. local url=$(git ls-remote --get-url $remote)
  175. if echo "$url" | grep -q -E "$upstream_patterns" ; then
  176. echo "$remote"
  177. return
  178. fi
  179. done
  180. }
  181. revision() {
  182. local sha=$(git_last_commit "$1" 2>/dev/null)
  183. # No git no fun
  184. if [ -z "$sha" ]; then
  185. echo "unknown"
  186. return
  187. fi
  188. local r="$sha"
  189. if git_has_local_changes "$1" ; then
  190. r="$r-dirty"
  191. fi
  192. # sha + possibly dirty info is not exactly verbose, therefore the code below tries to use tags and
  193. # branches from the upstream repos to derive a more previse version string.
  194. # To that end we try to use the existing remotes first.
  195. # If the upstream repos (and its mirrors) are not available as remotes, use a shadow copy instead.
  196. local up_refs
  197. local up_remote=$(find_upremote)
  198. if [ -n "$up_remote" ]; then
  199. for b in $upstream_branches ; do
  200. up_refs="$up_refs ${up_remote}/${b}"
  201. done
  202. else
  203. update || { echo "offline" ; return ; }
  204. up_refs=$upcache_refs
  205. fi
  206. # Find nearest commit contained in this branch that is also in any of the up_refs, i.e. the branch point
  207. # of the current branch. This might be the latest commit if it's in any of the upstream branches.
  208. local merge_point=$(git merge-base ${sha} ${up_refs})
  209. local upstream_branch
  210. if [ -z "$merge_point" ]; then
  211. echo "$sha"
  212. return
  213. fi
  214. # If the current commit is reachable from any remote branch, append the branch name and its
  215. # distance to the nearest earlier tag to that tag name itself (tag-distance-branch).
  216. # If multiple branches are reachable then we use the newest one (by commit date).
  217. # If none is reachable we use the nearest tag and ? for distances and remote branch name.
  218. local cnt_upstream_branch2sha=$(git rev-list --count "${merge_point}..${sha}" 2>/dev/null)
  219. local lasttag=$(git describe --abbrev=0 "$merge_point" 2>/dev/null)
  220. if [ -z "$lasttag" ]; then
  221. echo "Could not find tag reachable from merge point!">&2
  222. echo "$sha"
  223. return
  224. fi
  225. local cnt_tag2upstream_branch
  226. for ref in $up_refs ; do
  227. if git merge-base --is-ancestor ${merge_point} ${ref}; then
  228. upstream_branch=${ref##*/} # remove everything till last /
  229. cnt_tag2upstream_branch=$(git rev-list --count "${lasttag}..${merge_point}" 2>/dev/null)
  230. break
  231. fi
  232. done
  233. if [ "$cnt_upstream_branch2sha" -gt 0 ]; then
  234. r="$cnt_upstream_branch2sha-$r"
  235. fi
  236. if [ "$cnt_tag2upstream_branch" -gt 0 ]; then
  237. if [ -n "$upstream_branch" ]; then
  238. r="$upstream_branch-$r"
  239. fi
  240. r="$cnt_tag2upstream_branch-$r"
  241. fi
  242. r="$lasttag-$r"
  243. echo "${r}"
  244. }
  245. is_tracked() {
  246. is_file_tracked "$1"
  247. }
  248. show_help() {
  249. echo "Usage:
  250. ${0} <command> [path]
  251. Commands
  252. -h or --help
  253. this message
  254. -c or --check
  255. test if path is under version control at all
  256. -T or --tag
  257. returns the name of the last release/tag
  258. -r or --revision
  259. return unique revision information including the last tag and an indicator for uncommitted changes
  260. -u or --url
  261. URL associated with the latest commit
  262. -d or --date
  263. date of most recent modification
  264. -t or --timestamp
  265. timestamp of most recent modification
  266. -U or --update
  267. update local shadow copy of upstream commits if need be and offline builds are not enforced
  268. --forced-update
  269. force updating the local shadow copy of upstream commits
  270. "
  271. return
  272. }
  273. check_action() {
  274. if [ -n "$action" ]; then
  275. echo "Error: Multiple actions given.">&2
  276. exit ${EXIT_FAILURE}
  277. fi
  278. }
  279. main() {
  280. local query_path=
  281. local action=
  282. # Argument parser loop
  283. while [ $# -gt 0 ];
  284. do
  285. case ${1} in
  286. -h|--help)
  287. action=show_help;
  288. shift;;
  289. -T|--tag)
  290. check_action $1
  291. action=tag
  292. shift;;
  293. -r|--revision)
  294. check_action $1
  295. action=revision
  296. shift;;
  297. -u|--url)
  298. check_action $1
  299. action=scm_url
  300. shift;;
  301. -d|--date)
  302. check_action $1
  303. action="timestamp +%Y-%m-%d" # refrain from suffixing 'Z' to indicate it's UTC
  304. shift;;
  305. -t|--timestamp)
  306. check_action $1
  307. action="timestamp +%Y-%m-%dT%H:%M:%SZ" # There is only one valid time format! ISO 8601
  308. shift;;
  309. -U|--update)
  310. check_action $1
  311. action=update
  312. shift;;
  313. --forced-update)
  314. check_action $1
  315. action=forced_update
  316. shift;;
  317. -c|--check)
  318. check_action $1
  319. action=is_tracked
  320. shift;;
  321. -*)
  322. show_help;
  323. echo "Error: Invalid option: ${1}"
  324. exit ${EXIT_FAILURE};;
  325. *)
  326. if [ -z "$query_path" ] ; then
  327. if [ ! -e "$1" ] ; then
  328. echo "Error: Path \"${1}\" does not exist.">&2
  329. exit ${EXIT_FAILURE}
  330. fi
  331. query_path=$1
  332. else
  333. echo "Warning: Ignoring overabundant parameter: \"${1}\"">&2
  334. fi
  335. shift;;
  336. esac;
  337. done
  338. # default to current directory (usually equals the whole repository)
  339. if [ -z "$query_path" ] ; then
  340. query_path=.
  341. fi
  342. if [ -z "$action" ] ; then
  343. show_help
  344. echo "Error: No actions specified"
  345. exit ${EXIT_FAILURE}
  346. fi
  347. $action "$query_path"
  348. }
  349. main $@