diff --git a/.git-keywords/README.rst b/.git-keywords/README.rst new file mode 100644 index 00000000000..2bf23326cb8 --- /dev/null +++ b/.git-keywords/README.rst @@ -0,0 +1,107 @@ +Helper scripts for keyword expansion in git +########################################### + +:date: 2015-05-14 +:tags: git, keywords +:author: Roland Smith + +.. Last modified: 2015-05-14 18:02:25 +0200 + +One of the things I liked about the old rcs_ revision control system was that +it supported keyword expansion in files. Unlike systems like ``rcs``, ``cvs`` +and ``subversion``, the ``git`` revision control system cannot provide keyword +expansion. The cause for this is that you can't modify a file with information +about the commit after you've committed, because ``git`` checksums the file +first. + +.. _rcs: http://en.wikipedia.org/wiki/Revision_Control_System + +Git will let you inject text in a file when it is checked out, and remove it +when it is checked in. There are two ways of doing this. First, you can use +the ``ident`` attribute_. For any file type that has the ``ident`` attribute +set (in ``.gitattributes``), git will look for the string ``$Id$`` on checkout +and add the SHA-1 of the blob to it like this: ``$Id: +daf7affdeadc31cbcf8689f2ac5fcb6ecb6fd85e $``. While this unambiguously +identifies the commit, it is not all that practical. + +* It cannot tell you the relative order of two commits. +* It doesn't tell you the commit date. + +Luckily, keyword expansion can be done with git using attributes_. + +.. _attribute: http://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes +.. _attributes: http://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes + +In my global git configuration file (``~/.gitconfig``) I have defined a +filter_ called "kw": + +.. _filter: http://git-scm.com/docs/gitattributes + +.. code-block:: ini + + [filter "kw"] + clean = kwclean + smudge = kwset + +This configuration uses two programs (which should be in your ``\$PATH``) +called ``kwset`` and ``kwclean`` to expand and contract keywords. These are +two scripts written in python_ 3. + +.. _python: http://python.org/ + +To *enable* these substitutions, you have to use git attributes. E.g. to have +keyword substitutions in *all* files in a repository, you need to add the +following to the ``.gitattributes`` file in that repository; + +.. code-block:: ini + + * filter=kw + +Such a general use of filters can be problematic with e.g. binary files like +pictures. As a rule, modifying the contents of a binary (especially adding or +removing bytes) tends to *break* them. + +It is therefore better to be explicit and specific as to what types of file +the filter should apply to; + +.. code-block:: ini + + *.py filter=kw + *.txt filter=kw + +With this filter setup, file types that contain keywords and which are listed +as such in the ``.gitattributes`` file will have them expanded on checkout. + +To make these updated keywords visible in the working directory, changed +objects will have to be checked out after their changes have been committed. +To accomplish this, we can use the ``post-commit`` hook_. There are several +possible choices here. You can e.g.: + +.. _hook: http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks + +* Check out the files which have changed since the previous commit. +* Check out *all* files. + +The first one is probably the most common case. I wrote the script +``update-modified-keywords.py`` for it. After a check-in it checks out all the +files that were modified in the last commit. + +But if all the directories in one file are part of one project, you probably +want all files to carry the same date/revision. This is what the +``update-all-keywords.py`` script is for. After a check-in it checks out all the +files that are under git's control. + +Put both these scripts in a location in your ``$PATH``, and then make symbolic +links from ``.git/hooks/post-commit`` to the appropriate script. + +.. NOTE:: + + .. image:: http://i.creativecommons.org/p/zero/1.0/88x31.png + :alt: CC0 + :align: center + :target: http://creativecommons.org/publicdomain/zero/1.0/ + + To the extent possible under law, Roland Smith has waived all copyright and + related or neighboring rights to ``kwset.py``, ``kwclean.py``, + ``update-all-keywords.py`` and ``update-modified-keywords.py``. These + works are published from the Netherlands. diff --git a/.git-keywords/kwclean.py b/.git-keywords/kwclean.py new file mode 100644 index 00000000000..4f6c8c04acb --- /dev/null +++ b/.git-keywords/kwclean.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# vim:fileencoding=utf-8:ft=python +# +# Author: R.F. Smith +# Last modified: 2015-05-03 22:06:55 +0200 +# +# To the extent possible under law, Roland Smith has waived all copyright and +# related or neighboring rights to kwclean.py. This work is published from the +# Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/ + +"""Remove the Date and Revision keyword contents from the standard input.""" + +import io +import re +import sys + +if __name__ == '__main__': + dre = re.compile(''.join([r'\$', r'Date.*\$'])) + drep = ''.join(['$', 'Date', '$']) + rre = re.compile(''.join([r'\$', r'Revision.*\$'])) + rrep = ''.join(['$', 'Revision', '$']) + input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') + for line in input_stream: + line = dre.sub(drep, line) + print(rre.sub(rrep, line), end="") diff --git a/.git-keywords/kwset.py b/.git-keywords/kwset.py new file mode 100644 index 00000000000..d4d068f0c08 --- /dev/null +++ b/.git-keywords/kwset.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# vim:fileencoding=utf-8:ft=python +# +# Author: R.F. Smith +# Last modified: 2015-09-23 22:18:34 +0200 +# +# To the extent possible under law, Roland Smith has waived all copyright and +# related or neighboring rights to kwset.py. This work is published from +# the Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/ + +"""Fill the Date and Revision keywords from the latest git commit and tag and + subtitutes them in the standard input.""" + +import io +import os +import re +import subprocess +import sys + + +def main(): + """Main program. + """ + dre = re.compile(''.join([r'\$', r'Date:?\$'])) + rre = re.compile(''.join([r'\$', r'Revision:?\$'])) + currp = os.getcwd() + if not os.path.exists(currp + '/.git'): + print >> sys.stderr, 'This directory is not controlled by git!' + sys.exit(1) + date = gitdate() + rev = gitrev() + input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') + for line in input_stream: + line = dre.sub(date, line) + print(rre.sub(rev, line), end="") + + +def gitdate(): + """Get the date from the latest commit in ISO8601 format. + """ + args = ['git', 'log', '-1', '--date=iso'] + outdata = subprocess.check_output(args, universal_newlines=True) + outlines = outdata.splitlines() + dline = [l for l in outlines if l.startswith('Date')] + try: + dat = dline[0][5:].strip() + return ''.join(['$', 'Date: ', dat, ' $']) + except IndexError: + raise ValueError('Date not found in git output') + + +def gitrev(): + """Get the latest tag and use it as the revision number. This presumes the + habit of using numerical tags. Use the short hash if no tag available. + """ + args = ['git', 'describe', '--tags', '--always'] + try: + r = subprocess.check_output(args, + stderr=subprocess.DEVNULL, + universal_newlines=True)[:-1] + except subprocess.CalledProcessError: + return ''.join(['$', 'Revision', '$']) + return ''.join(['$', 'Revision: ', r, ' $']) + + +if __name__ == '__main__': + main() diff --git a/.git-keywords/update-all-keywords.py b/.git-keywords/update-all-keywords.py new file mode 100644 index 00000000000..10621c427f1 --- /dev/null +++ b/.git-keywords/update-all-keywords.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# vim:fileencoding=utf-8:ft=python +# +# Author: R.F. Smith +# Last modified: 2015-09-23 21:17:05 +0200 +# +# To the extent possible under law, Roland Smith has waived all copyright and +# related or neighboring rights to update-all-keywords.py. This work is +# published from the Netherlands. +# See http://creativecommons.org/publicdomain/zero/1.0/ + +"""Remove and check out all files under git's control that contain keywords in +the current working directory.""" + +from base64 import b64decode +import mmap +import os +import subprocess +import sys + + +def main(args): + """Main program. + + Arguments: + args: command line arguments + """ + # Check if git is available. + checkfor(['git', '--version']) + # Check if .git exists + if not os.access('.git', os.F_OK): + print('No .git directory found!') + sys.exit(1) + # Get all files that are controlled by git. + files = git_ls_files() + # Remove those that aren't checked in + mod = git_not_checkedin() + if mod: + files = [f for f in files if f not in mod] + if not files: + print('{}: Only uncommitted changes, nothing to do.'.format(args[0])) + sys.exit(0) + files.sort() + # Find files that have keywords in them + kwfn = keywordfiles(files) + if kwfn: + print('{}: Updating all files.'.format(args[0])) + for fn in kwfn: + os.remove(fn) + sargs = ['git', 'checkout', '-f'] + kwfn + subprocess.call(sargs) + else: + print('{}: Nothing to update.'.format(args[0])) + + +def checkfor(args): + """Make sure that a program necessary for using this script is + available. + + Arguments: + args: String or list of strings of commands. A single string may + not contain spaces. + """ + if isinstance(args, str): + if ' ' in args: + raise ValueError('No spaces in single command allowed.') + args = [args] + try: + subprocess.check_call(args, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError: + print("Required program '{}' not found! exiting.".format(args[0])) + sys.exit(1) + + +def git_ls_files(): + """Find ordinary files that are controlled by git. + + Returns: + A list of files + """ + args = ['git', 'ls-files'] + flist = subprocess.check_output(args).decode('utf8').splitlines() + return flist + + +def git_not_checkedin(): + """Find files that are modified but are not checked in. + + Returns: + A list of modified files that are not checked in. + """ + lns = subprocess.check_output(['git', 'status', '-s']) + lns.decode('utf8').splitlines() + lns = [l.split()[-1] for l in lns] + return lns + + +def keywordfiles(fns): + """Filter those files that have keywords in them + + Arguments: + fns: A list of filenames. + + Returns: + A list for filenames for files that contain keywords. + """ + # These lines are encoded otherwise they would be mangled if this file + # is checked in! + datekw = b64decode('JERhdGU=') + revkw = b64decode('JFJldmlzaW9u') + rv = [] + for fn in fns: + with open(fn, 'rb') as f: + try: + mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) + if mm.find(datekw) > -1 or mm.find(revkw) > -1: + rv.append(fn) + mm.close() + except ValueError: + pass + return rv + + +if __name__ == '__main__': + main(sys.argv) diff --git a/.git-keywords/update-modified-keywords.py b/.git-keywords/update-modified-keywords.py new file mode 100644 index 00000000000..f6d54c7fabb --- /dev/null +++ b/.git-keywords/update-modified-keywords.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# vim:fileencoding=utf-8:ft=python +# +# Author: R.F. Smith +# Last modified: 2015-09-23 21:19:02 +0200 +# +# To the extent possible under law, Roland Smith has waived all copyright and +# related or neighboring rights to update-modified-keywords.py. This work is +# published from the Netherlands. +# See http://creativecommons.org/publicdomain/zero/1.0/ + +"""Remove and check out those files that that contain keywords and have +changed since in the last commit in the current working directory.""" + +from base64 import b64decode +import mmap +import os +import subprocess +import sys + + +def main(args): + """Main program. + + Arguments: + args: command line arguments + """ + # Check if git is available. + checkfor(['git', '--version']) + # Check if .git exists + if not os.access('.git', os.F_OK): + print('No .git directory found!') + sys.exit(1) + print('{}: Updating modified files.'.format(args[0])) + # Get modified files + files = modifiedfiles() + if not files: + print('{}: No modified files.'.format(args[0])) + sys.exit(0) + files.sort() + # Find files that have keywords in them + kwfn = keywordfiles(files) + if not kwfn: + print('{}: No keyword files modified.'.format(args[0])) + sys.exit(0) + for fn in kwfn: + os.remove(fn) + sargs = ['git', 'checkout', '-f'] + kwfn + subprocess.call(sargs) + + +def checkfor(args): + """Make sure that a program necessary for using this script is + available. + + Arguments: + args -- string or list of strings of commands. A single string may + not contain spaces. + """ + if isinstance(args, str): + if ' ' in args: + raise ValueError('No spaces in single command allowed.') + args = [args] + try: + subprocess.check_call(args, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError: + print("Required program '{}' not found! exiting.".format(args[0])) + sys.exit(1) + + +def modifiedfiles(): + """Find files that have been modified in the last commit. + + Returns: + A list of filenames. + """ + fnl = [] + try: + args = ['git', 'diff-tree', 'HEAD~1', 'HEAD', '--name-only', '-r', + '--diff-filter=ACMRT'] + fnl = subprocess.check_output(args, stderr=subprocess.DEVNULL) + fnl = fnl.decode('utf8').splitlines() + # Deal with unmodified repositories + if len(fnl) == 1 and fnl[0] is 'clean': + return [] + except subprocess.CalledProcessError as e: + if e.returncode == 128: # new repository + args = ['git', 'ls-files'] + fnl = subprocess.check_output(args, stderr=subprocess.DEVNULL) + fnl = fnl.decode('utf8').splitlines() + # Only return regular files. + fnl = [i for i in fnl if os.path.isfile(i)] + return fnl + + +def keywordfiles(fns): + """Filter those files that have keywords in them + + Arguments: + fns: A list of filenames. + + Returns: + A list for filenames for files that contain keywords. + """ + # These lines are encoded otherwise they would be mangled if this file + # is checked in my git repo! + datekw = b64decode('JERhdGU=') + revkw = b64decode('JFJldmlzaW9u') + rv = [] + for fn in fns: + with open(fn, 'rb') as f: + try: + mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) + if mm.find(datekw) > -1 or mm.find(revkw) > -1: + rv.append(fn) + mm.close() + except ValueError: + pass + return rv + + +if __name__ == '__main__': + main(sys.argv) diff --git a/.git_filters/gcs-rcs-keywords-README b/.git_filters/gcs-rcs-keywords-README deleted file mode 100644 index 8895c44623a..00000000000 --- a/.git_filters/gcs-rcs-keywords-README +++ /dev/null @@ -1,28 +0,0 @@ -This module provides a means to add keyword expansion of the following -standard RCS tags to your git projects: - - Id$ - $Date$ - $File$ - $Author$ - $Revision$ - $Source$ - -The mechanism used are filters. The smudge filter is run on checkout, and the -clean filter is run on commit. The tags are only expanded on the local disk, -not in the repository itself. - -To start, you need to add the following to ~/.gitconfig: - -[filter "rcs-keywords"] - clean = .git_filters/rcs-keywords.clean - smudge = .git_filters/rcs-keywords.smudge %f - -Then, to add keyword expansion, simply add these files to your project: - /.gitattributes - *.c filter=rcs-keywords - /.git_filters/rcs-keywords.smudge - copy this file to project - /.git_filters/rcs-keywords.clean - copy companion to project - -Note: This feature is known not to work in git 1.6.2.2 and 1.7.3.*, and - verified to work in git 1.7.4.4 and 1.7.4.msysgit.0 - diff --git a/.git_filters/rcs-keywords.clean b/.git_filters/rcs-keywords.clean deleted file mode 100644 index 44396a1df6f..00000000000 --- a/.git_filters/rcs-keywords.clean +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/perl -p -# -# @brief Git filter to implement rcs keyword expansion as seen in cvs and svn. -# @author Martin Turon -# -# Copyright (c) 2009-2011 Turon Technologies, Inc. All rights reserved. - -s/\$Id[^\$]*\$/\$Id\$/; -s/\$Date[^\$]*\$/\$Date\$/; -s/\$Author[^\$]*\$/\$Author\$/; -s/\$Source[^\$]*\$/\$Source\$/; -s/\$File[^\$]*\$/\$File\$/; -s/\$Revision[^\$]*\$/\$Revision\$/; - diff --git a/.git_filters/rcs-keywords.smudge b/.git_filters/rcs-keywords.smudge deleted file mode 100644 index 426341686a6..00000000000 --- a/.git_filters/rcs-keywords.smudge +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/perl -# -# @brief Git filter to implement rcs keyword expansion as seen in cvs and svn. -# @author Martin Turon -# -# Usage: -# .git_filter/rcs-keywords.smudge file_path < file_contents -# -# To add keyword expansion: -# /.gitattributes - *.c filter=rcs-keywords -# /.git_filters/rcs-keywords.smudge - copy this file to project -# /.git_filters/rcs-keywords.clean - copy companion to project -# ~/.gitconfig - add [filter] lines below -# -# [filter "rcs-keywords"] -# clean = .git_filters/rcs-keywords.clean -# smudge = .git_filters/rcs-keywords.smudge %f -# -# Copyright (c) 2009-2011 Turon Technologies, Inc. All rights reserved. - -$path = shift; -$path =~ /.*\/(.*)/; -$filename = $1; - -if (0 == length($filename)) { - $filename = $path; -} - -# Need to grab filename and to use git log for this to be accurate. -$rev = `git log -- $path | head -n 3`; -$rev =~ /^Author:\s*(.*)\s*$/m; -$author = $1; -$author =~ /\s*(.*)\s*<.*/; -$name = $1; -$rev =~ /^Date:\s*(.*)\s*$/m; -$date = $1; -$rev =~ /^commit (.*)$/m; -$ident = $1; - -while () { - s/\$Date[^\$]*\$/\$Date: $date \$/; - s/\$Author[^\$]*\$/\$Author: $author \$/; - s/\$Id[^\$]*\$/\$Id: $filename | $date | $name \$/; - s/\$File[^\$]*\$/\$File: $filename \$/; - s/\$Source[^\$]*\$/\$Source: $path \$/; - s/\$Revision[^\$]*\$/\$Revision: $ident \$/; -} continue { - print or die "-p destination: $!\n"; -} - diff --git a/.gitattributes b/.gitattributes index 3d25403836c..5b1ef5f047a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,15 +1,13 @@ # see man gitattributes -# ident does the following: When the attribute ident is set for a path, -# Git replaces Id in the blob object with Id:, followed by the 40-character -# hexadecimal blob object name, followed by a sign upon checkout. -# Any byte sequence that begins with Id: and ends with in the worktree -# file is replaced with Id upon check-in. +# Map file extensions to git filters +# See .git-keywords directory and .gitconfig + +*.h filter=kw +*.hpp filter=kw +*.c filter=kw +*.cpp filter=kw +*.py filter=kw +*.am filter=kw +*.m filter=kw -# expand Id in any of these files, to update git commit info: -*.c ident -*.cpp ident -*.h ident -*.hpp ident -*.py ident -*.am ident diff --git a/.gitconfig b/.gitconfig index d4c7ac8afd8..691dd89c604 100644 --- a/.gitconfig +++ b/.gitconfig @@ -1,8 +1,4 @@ -# See .git_filters/git-rcs-keywords-README -# To add keyword expansion: -# git init -# cp ~/.gitconfig, /.gitattributes, and /.git_filters - -[filter "rcs-keywords"] - clean = .git_filters/rcs-keywords.clean - smudge = .git_filters/rcs-keywords.smudge %f +# See .git-keywords directory for README +[filter "kw"] + clean = .git-keywords/kwclean + smudge = .git-keywords/kwset diff --git a/utils/version.c b/utils/version.c index 8c0544cb51c..da9c2697462 100644 --- a/utils/version.c +++ b/utils/version.c @@ -12,7 +12,7 @@ * Revision Info: * $Author: nicks $ * $Date: 2011/03/02 00:04:55 $ - * $Id$ + * $Revision: 8c0544cb51c408c315be1f6cae68f38fc534b7da $ * * Copyright © 2011 The General Hospital Corporation (Boston, MA) "MGH" * @@ -374,7 +374,7 @@ int handle_version_option(int argc, char **argv, const char *id_string, const ch fprintf(stdout, "ProgramName: %s ProgramArguments: %s " "ProgramVersion: %s TimeStamp: %s " - "BuildTimeStamp: %s CVS: %s User: %s " + "BuildTimeStamp: %s Id: %s User: %s " "Machine: %s Platform: %s PlatformVersion: %s " "CompilerName: %s CompilerVersion: %d \n", program_name,