Script to automatically update copyright
authorTeemu Murtola <teemu.murtola@gmail.com>
Sat, 27 Jul 2013 19:16:58 +0000 (22:16 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Wed, 20 Nov 2013 17:24:39 +0000 (19:24 +0200)
Adapt the automatic uncrustification script to also update the
copyright header for changed files.  Add a Python script to do the
actual copyright header processing.  The Python script can also do other
copyright header tasks.  Make the pre-commit hook more flexible to be
able to do either only uncrustify or only copyright checking, as well as
temporarily skipping the hook for a commit.

Related to #818.

Change-Id: Ie21365acbe07e1f097e6d72c6a5e0d0826631ff0

.gitattributes
admin/copyright.py [new file with mode: 0755]
admin/git-pre-commit
admin/uncrustify.sh
src/external/.gitattributes

index 411fee685360588f03b99937968f64a892f83001..df037542fb0d3a709daab5a4d517c427ea3b0572 100644 (file)
@@ -1,11 +1,32 @@
+# Generic rules
 *.c     filter=uncrustify
 *.cpp   filter=uncrustify
 *.h     filter=uncrustify
+CMakeLists.txt  filter=copyright
+*.cmake         filter=copyright
+*.cmakein       filter=copyright
+*.py            filter=copyright
+*.l             filter=copyright
+*.y             filter=copyright
+*.pre           filter=copyright
+# Exceptions: extra files to incldue
+admin/uncrustify.sh                     filter=copyright
+admin/git-pre-commit                    filter=copyright
+# Exceptions: files to exclude
+*.pc.cmakein                            !filter
+cmake/CheckC*CompilerFlag.cmake         !filter
+cmake/FindBLAS.cmake                    !filter
+cmake/FindLAPACK.cmake                  !filter
+cmake/FindGit.cmake                     !filter
+cmake/*.c                               !filter
+cmake/*.c.cmakein                       !filter
+doxygen/*.cmakein                       !filter
+share/man/man7/gromacs.7.cmakein        !filter
 src/contrib/*                           !filter
-nb_kernel_Elec*                         !filter
 src/gromacs/linearalgebra/gmx_blas/*    !filter
 src/gromacs/linearalgebra/gmx_lapack/*  !filter
 src/gromacs/selection/parser.cpp        !filter
 src/gromacs/selection/parser.h          !filter
 src/gromacs/selection/scanner.cpp       !filter
 src/gromacs/selection/scanner_flex.h    !filter
+nb_kernel_Elec*                         filter=copyright
diff --git a/admin/copyright.py b/admin/copyright.py
new file mode 100755 (executable)
index 0000000..085542e
--- /dev/null
@@ -0,0 +1,407 @@
+#!/usr/bin/python
+#
+# This file is part of the GROMACS molecular simulation package.
+#
+# Copyright (c) 2013, by the GROMACS development team, led by
+# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+# and including many others, as listed in the AUTHORS file in the
+# top-level source directory and at http://www.gromacs.org.
+#
+# GROMACS is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+#
+# GROMACS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with GROMACS; if not, see
+# http://www.gnu.org/licenses, or write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+#
+# If you want to redistribute modifications to GROMACS, please
+# consider that scientific software is very special. Version
+# control is crucial - bugs must be traceable. We will be happy to
+# consider code for inclusion in the official distribution, but
+# derived work must not be called official GROMACS. Details are found
+# in the README & COPYING files - if they are missing, get the
+# official version at http://www.gromacs.org.
+#
+# To help us fund GROMACS development, we humbly ask that you cite
+# the research papers on the package. Check out http://www.gromacs.org.
+
+import datetime
+import os.path
+import re
+import sys
+
+from optparse import OptionParser
+
+class CopyrightState(object):
+
+    """Information about an existing (or non-existing) copyright header."""
+
+    def __init__(self, has_copyright, is_correct, is_newstyle, years, other_copyrights):
+        self.has_copyright = has_copyright
+        self.is_correct = is_correct
+        self.is_newstyle = is_newstyle
+        self.years = years
+        self.other_copyrights = other_copyrights
+
+class CopyrightChecker(object):
+
+    """Logic for analyzing existing copyright headers and generating new ones."""
+
+    _header = ["", "This file is part of the GROMACS molecular simulation package.", ""]
+    _copyright = "Copyright (c) {0}, by the GROMACS development team, led by"
+    _footer = """
+Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+and including many others, as listed in the AUTHORS file in the
+top-level source directory and at http://www.gromacs.org.
+
+GROMACS is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public License
+as published by the Free Software Foundation; either version 2.1
+of the License, or (at your option) any later version.
+
+GROMACS is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with GROMACS; if not, see
+http://www.gnu.org/licenses, or write to the Free Software Foundation,
+Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+
+If you want to redistribute modifications to GROMACS, please
+consider that scientific software is very special. Version
+control is crucial - bugs must be traceable. We will be happy to
+consider code for inclusion in the official distribution, but
+derived work must not be called official GROMACS. Details are found
+in the README & COPYING files - if they are missing, get the
+official version at http://www.gromacs.org.
+
+To help us fund GROMACS development, we humbly ask that you cite
+the research papers on the package. Check out http://www.gromacs.org.
+""".strip().splitlines()
+
+    def check_copyright(self, comment_block):
+        """Analyze existing copyright header for correctness and extract information."""
+        copyright_re = r'Copyright \(c\) (([0-9]{4}[,-])*[0-9]{4}),? by the GROMACS development team'
+        has_copyright = False
+        is_newstyle = True
+        is_correct = True
+        next_header_line = 0
+        next_footer_line = 0
+        existing_years = ''
+        other_copyrights = []
+        for line in comment_block:
+            if 'Copyright' in line:
+                has_copyright = True
+                match = re.match(copyright_re, line)
+                if match:
+                    existing_years = match.group(1)
+                    new_line = self._copyright.format(existing_years)
+                    if line != new_line:
+                        is_correct = False
+                else:
+                    other_copyrights.append(line)
+                if next_header_line != -1 or next_footer_line != 0:
+                    is_correct = False
+                continue
+            if line.startswith('Written by the Gromacs development team'):
+                has_copyright = True
+            if next_header_line >= 0:
+                if line == self._header[next_header_line]:
+                    next_header_line += 1
+                    if next_header_line >= len(self._header):
+                        next_header_line = -1
+                else:
+                    is_correct = False
+                    is_newstyle = False
+            elif next_footer_line >= 0:
+                if line == self._footer[next_footer_line]:
+                    next_footer_line += 1
+                    if next_footer_line >= len(self._footer):
+                        next_footer_line = -1
+                else:
+                    is_correct = False
+            else:
+                is_correct = False
+        if next_header_line != -1 or next_footer_line != -1:
+            is_correct = False
+
+        return CopyrightState(has_copyright, is_correct, is_newstyle, existing_years, other_copyrights)
+
+    def process_copyright(self, state, options, current_years, reporter):
+        """Determine whether a copyrigth header needs to be updated and report issues."""
+        need_update = False
+
+        if state.years:
+            if options.replace_years:
+                if state.years != current_years:
+                    need_update = True
+                    reporter.report('copyright years replaced')
+                new_years = current_years
+            else:
+                new_years = state.years
+                if not new_years.endswith(current_years):
+                    if options.update_year:
+                        need_update = True
+                        new_years += ',' + current_years
+                    if options.check or not need_update:
+                        reporter.report('copyright year outdated')
+                    else:
+                        reporter.report('copyright year added')
+        else:
+            new_years = current_years
+
+        if not state.has_copyright:
+            if options.add_missing:
+                need_update = True
+            if options.check or not need_update:
+                reporter.report('copyright header missing')
+            elif options.add_missing:
+                reporter.report('copyright header added')
+        else:
+            if not state.is_newstyle:
+                if options.replace_header:
+                    need_update = True
+                if options.check or not need_update:
+                    reporter.report('copyright header incorrect')
+                else:
+                    reporter.report('copyright header replaced')
+            elif not state.is_correct:
+                if options.update_header:
+                    need_update = True
+                if options.check or not need_update:
+                    reporter.report('copyright header outdated')
+                else:
+                    reporter.report('copyright header updated')
+
+        return need_update, new_years
+
+    def get_copyright_text(self, years, other_copyrights):
+        """Construct a new copyright header."""
+        output = []
+        output.extend(self._header)
+        if other_copyrights:
+            for line in other_copyrights:
+                outline = line.rstrip()
+                if outline.endswith(','):
+                    outline = outline[:-1]
+                if not outline.endswith('.'):
+                    outline += '.'
+                output.append(outline)
+        output.append(self._copyright.format(years))
+        output.extend(self._footer)
+        return output
+
+class Reporter(object):
+
+    """Wrapper for reporting issues in a file."""
+
+    def __init__(self, reportfile, filename):
+        self._reportfile = reportfile
+        self._filename = filename
+
+    def report(self, text):
+        self._reportfile.write(self._filename + ': ' + text + '\n');
+
+class CommentHandlerC(object):
+
+    """Handler for extracting and creating C-style comments."""
+
+    def extract_first_comment_block(self, content_lines):
+        if not content_lines or not content_lines[0].startswith('/*'):
+            return ([], 0)
+        comment_block = [content_lines[0][2:].strip()]
+        line_index = 1
+        while line_index < len(content_lines):
+            line = content_lines[line_index]
+            if '*/' in content_lines[line_index]:
+                break
+            comment_block.append(line.lstrip('* ').rstrip())
+            line_index += 1
+        return (comment_block, line_index + 1)
+
+    def create_comment_block(self, lines):
+        output = []
+        output.append(('/* ' + lines[0]).rstrip())
+        output.extend([(' * ' + x).rstrip() for x in lines[1:]])
+        output.append(' */')
+        return output
+
+class CommentHandlerSh(object):
+
+    """Handler for extracting and creating sh-style comments."""
+
+    def extract_first_comment_block(self, content_lines):
+        if not content_lines or not content_lines[0].startswith('#'):
+            return ([], 0)
+        comment_block = []
+        line_index = 0
+        while line_index < len(content_lines):
+            line = content_lines[line_index]
+            if not line.startswith('#'):
+                break
+            comment_block.append(line.lstrip('# ').rstrip())
+            line_index += 1
+            if line == '# the research papers on the package. Check out http://www.gromacs.org.':
+                break
+        while line_index < len(content_lines):
+            line = content_lines[line_index].rstrip()
+            if len(line) > 0 and line != '#':
+                break
+            line_index += 1
+        return (comment_block, line_index)
+
+    def create_comment_block(self, lines):
+        output = []
+        output.extend([('# ' + x).rstrip() for x in lines])
+        output.append('')
+        return output
+
+comment_handlers = {'c': CommentHandlerC(), 'sh': CommentHandlerSh()}
+
+def select_comment_handler(override, filename):
+    """Select comment handler for a file based on file name and input options."""
+    filetype = override
+    if not filetype and filename != '-':
+        basename = os.path.basename(filename)
+        root, ext = os.path.splitext(basename)
+        if ext == '.cmakein':
+            dummy, ext2 = os.path.splitext(root)
+            if ext2:
+                ext = ext2
+        if ext in ('.c', '.cpp', '.h', '.y', '.l', '.pre'):
+            filetype = 'c'
+        elif basename in ('CMakeLists.txt', 'GMXRC', 'git-pre-commit') or \
+                ext in ('.cmake', '.cmakein', '.py', '.sh', '.bash', '.csh', '.zsh'):
+            filetype = 'sh'
+    if filetype in comment_handlers:
+        return comment_handlers[filetype]
+    if filetype:
+        sys.stderr.write("Unsupported input format: {0}\n".format(filetype))
+    elif filename != '-':
+        sys.stderr.write("Unsupported input format: {0}\n".format(filename))
+    else:
+        sys.stderr.write("No file name or file type provided.\n")
+    sys.exit(1)
+
+def create_copyright_header(years, other_copyrights=None, language='c'):
+    if language not in comment_handlers:
+        sys.strerr.write("Unsupported language: {0}\n".format(language))
+        sys.exit(1)
+    copyright_checker = CopyrightChecker()
+    comment_handler = comment_handlers[language]
+    copyright_lines = copyright_checker.get_copyright_text(years, other_copyrights)
+    comment_lines = comment_handler.create_comment_block(copyright_lines)
+    return '\n'.join(comment_lines) + '\n'
+
+def process_options():
+    """Process input options."""
+    parser = OptionParser()
+    parser.add_option('-l', '--lang',
+                      help='Comment type to use (c or sh)')
+    parser.add_option('-y', '--years',
+                      help='Comma-separated list of years')
+    parser.add_option('-F', '--files',
+                      help='File to read list of files from')
+    parser.add_option('--check', action='store_true',
+                      help='Do not modify the files, only check the copyright (default action). ' +
+                           'If specified together with --update, do the modifications ' +
+                           'but produce output as if only --check was provided.')
+    parser.add_option('--update-year', action='store_true',
+                      help='Update the copyright year if outdated')
+    parser.add_option('--replace-years', action='store_true',
+                      help='Replace the copyright years with those given with --years')
+    parser.add_option('--update-header', action='store_true',
+                      help='Update the copyright header if outdated')
+    parser.add_option('--replace-header', action='store_true',
+                      help='Replace any copyright header with the current one')
+    parser.add_option('--add-missing', action='store_true',
+                      help='Add missing copyright headers')
+    options, args = parser.parse_args()
+
+    filenames = args
+    if options.files:
+        with open(options.files, 'r') as filelist:
+            filenames = [x.strip() for x in filelist.read().splitlines()]
+    elif not filenames:
+        filenames = ['-']
+
+    # Default is --check if nothing provided.
+    if not options.check and not options.update_year and \
+            not options.update_header and not options.replace_header and \
+            not options.add_missing:
+        options.check = True
+
+    return options, filenames
+
+def main():
+    """Do processing as a stand-alone script."""
+    options, filenames = process_options()
+    years = options.years
+    if not years:
+        years = str(datetime.date.today().year)
+    if years.endswith(','):
+        years = years[:-1]
+
+    checker = CopyrightChecker()
+
+    # Process each input file in turn.
+    for filename in filenames:
+        comment_handler = select_comment_handler(options.lang, filename)
+
+        # Read the input file.  We are doing an in-place operation, so can't
+        # operate in pass-through mode.
+        if filename == '-':
+            contents = sys.stdin.read().splitlines()
+            reporter = Reporter(sys.stderr, '<stdin>')
+        else:
+            with open(filename, 'r') as inputfile:
+                contents = inputfile.read().splitlines()
+            reporter = Reporter(sys.stdout, filename)
+
+        output = []
+        # Keep lines that must be at the beginning of the file and skip them in
+        # the check.
+        if contents and (contents[0].startswith('#!/') or \
+                contents[0].startswith('%code requires') or \
+                contents[0].startswith('/* #if')):
+            output.append(contents[0])
+            contents = contents[1:]
+        # Remove and skip empty lines at the beginning.
+        while contents and len(contents[0]) == 0:
+            contents = contents[1:]
+
+        # Analyze the first comment block in the file.
+        comment_block, line_count = comment_handler.extract_first_comment_block(contents)
+        state = checker.check_copyright(comment_block)
+        need_update, file_years = checker.process_copyright(state, options, years, reporter)
+
+        if need_update:
+            # Remove the original comment if it was a copyright comment.
+            if state.has_copyright:
+                contents = contents[line_count:]
+            new_block = checker.get_copyright_text(file_years, state.other_copyrights)
+            output.extend(comment_handler.create_comment_block(new_block))
+
+        # Write the output file if required.
+        if need_update or filename == '-':
+            # Append the rest of the input file as it was.
+            output.extend(contents)
+            output = '\n'.join(output) + '\n'
+            if filename == '-':
+                sys.stdout.write(output)
+            else:
+                with open(filename, 'w') as outputfile:
+                    outputfile.write(output)
+
+if __name__ == "__main__":
+    main()
index 4fbbf0b89b574cd900e053a7894acf4737a109d6..ea17bc9019fcd4c925c8c03da0a472180a1c62b0 100755 (executable)
 # the research papers on the package. Check out http://www.gromacs.org.
 
 # This script is intended as a pre-commit hook that optionally runs all
-# changes through uncrustify.  By default, it does nothing.  To enable the
-# script after copying it to .git/hooks/pre-commit, you need to set
+# changes through some formatting check.  Currently, it runs uncrustify and
+# checks copyright headers.
+#
+# By default, it does nothing.  To enable the script after copying it to
+# .git/hooks/pre-commit, you need to set
 #   git config hooks.uncrustifymode check
 #   git config hooks.uncrustifypath /path/to/uncrustify
 # With this configuration, all source files modified in the commit are run
-# through uncrustify (see admin/uncrustify.sh for how this set of files is
-# determined).  If any file is changed by uncrustify, the names of those files
-# are reported and the commit is prevented.
+# through uncrustify and checked for correct copyright headers
+# (see admin/uncrustify.sh for how this set of files is determined).
+# If any file is changed by uncrustify or has other problems, the names of
+# those files are reported and the commit is prevented.  The issues can be
+# fixed by running admin/uncrustify.sh manually.
+#
 # To disable the hook, you can set
 #   git config hooks.uncrustifymode off
+# To disable it temporarily for a commit, set NO_FORMAT_CHECK environment
+# variable.  For example,
+#   NO_FORMAT_CHECK=1 git commit -a
+# You can also run git commit --no-verify, but that also disables other hooks,
+# such as the Change-Id hook used by gerrit.
 #
 # The actual work is done by the admin/uncrustify.sh script, which gets
 # run with the 'check-index' action.
 # See the comments in that script for more information.
 
+if [ ! -z "$NO_FORMAT_CHECK" ]
+then
+    exit 0
+fi
+
 if git rev-parse --verify HEAD >/dev/null 2>&1
 then
-       against=HEAD
+    against=HEAD
 else
-       # Initial commit: diff against an empty tree object
-       against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+    # Initial commit: diff against an empty tree object
+    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
 fi
 
 # Redirect output to stderr.
 exec 1>&2
 
 uncrustify_mode=`git config hooks.uncrustifymode`
+copyright_mode=`git config hooks.copyrightmode`
+if [ -z "$uncrustify_mode" ]
+then
+    uncrustify_mode=off
+fi
+if [ -z "$copyright_mode" ]
+then
+    copyright_mode=off
+fi
 
-if [ -f admin/uncrustify.sh ] && \
-   [ ! -z "$uncrustify_mode" ] && [ "$uncrustify_mode" != "off" ]
+if [[ -f admin/uncrustify.sh && \
+      ( "$uncrustify_mode" != "off" || "$copyright_mode" != "off" ) ]]
 then
-    uncrustify_path=`git config hooks.uncrustifypath`
-    if [ -z "$uncrustify_path" ]
+    if [ "$uncrustify_mode" != "off" ]
     then
-        echo "Please set the path to uncrustify using 'git config hooks.uncrustifypath'."
-        echo "Note that you need a custom version of uncrustify."
+        uncrustify_path=`git config hooks.uncrustifypath`
+        if [ -z "$uncrustify_path" ]
+        then
+            echo "Please set the path to uncrustify using 'git config hooks.uncrustifypath'."
+            echo "Note that you need a custom version of uncrustify."
+            exit 1
+        fi
+        export UNCRUSTIFY="$uncrustify_path"
+    fi
+    admin/uncrustify.sh check-index --rev=$against \
+        --uncrustify="$uncrustify_mode" --copyright="$copyright_mode"
+    stat=$?
+    if [ $stat -eq 1 ] ; then
+        exit 1
+    elif [ $stat -ne 0 ] ; then
+        echo "Source code formatting check failed"
         exit 1
     fi
-    export UNCRUSTIFY="$uncrustify_path"
-    case "$uncrustify_mode" in
-        check)
-            admin/uncrustify.sh check-index --rev=$against
-            stat=$?
-            if [ $stat -eq 1 ] ; then
-                exit 1
-            elif [ $stat -ne 0 ] ; then
-                echo "Source code formatting check failed"
-                exit 1
-            fi
-            ;;
-        *)
-            echo "Unknown uncrustify mode: $uncrustify_mode"
-            exit 1
-            ;;
-    esac
 fi
index aa6f1df3e60f7b7a9824c7f23c9a752d534d7ff3..cbabff6cfeb451500b977631de0533d7225922f4 100755 (executable)
@@ -3,9 +3,9 @@
 # This file is part of the GROMACS molecular simulation package.
 #
 # Copyright (c) 2013, by the GROMACS development team, led by
-# David van der Spoel, Berk Hess, Erik Lindahl, and including many
-# others, as listed in the AUTHORS file in the top-level source
-# directory and at http://www.gromacs.org.
+# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+# and including many others, as listed in the AUTHORS file in the
+# top-level source directory and at http://www.gromacs.org.
 #
 # GROMACS is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
 # the research papers on the package. Check out http://www.gromacs.org.
 
 # This script runs uncrustify on modified files and reports/applies the
-# results.  By default, the current HEAD commit is compared to the work tree,
+# results.  It also checks for outdated copyright years or headers.
+# By default, the current HEAD commit is compared to the work tree,
 # and files that
 #  1. are different between these two trees and
-#  2. change under uncrustify
+#  2. change under uncrustify and/or have outdated copyright header
 # are reported.  This behavior can be changed by
 #  1. Specifying an --rev=REV argument, which uses REV instead of HEAD as
 #     the base of the comparison.
 #  2. Specifying an action:
-#       check-* reports the files that uncrustify changes
-#       diff-* prints the actual diff of what would change
-#       update-* applies the changes to the repository
-#       *-workdir operates on the working directory (files on disk)
-#       *-index operates on the index of the repository
+#       check-*:   reports the files that uncrustify changes
+#       diff-*:    prints the actual diff of what would change
+#       update-* applies the changes to the repository
+#       *-workdir: operates on the working directory (files on disk)
+#       *-index:   operates on the index of the repository
 #     For convenience, if you omit the workdir/index suffix, workdir is assumed
 #     (i.e., diff equals diff-workdir).
+#  3. Specifying --uncrustify=off, which does not run uncrustify.
+#  4. Specifying --copyright=<mode>, which alters the level of copyright
+#     checking is done:
+#       off:     does not check copyright headers at all
+#       year:    only update copyright year in new-format copyright headers
+#       add:     in addition to 'year', add copyright headers to files that
+#                don't have any
+#       update:  in addition to 'year' and 'add', also update new-format
+#                copyright headers if they are broken or outdated.
+#       replace: replace any copyright header with a new-format copyright
+#                header
+#       full:    do all of the above
 # By default, update-* refuses to update "dirty" files (i.e., that differ
 # between the disk and the index) to make it easy to revert the changes.
 # This can be overridden by adding a -f/--force option.
@@ -68,7 +81,9 @@
 # To identify which files to run through uncrustify, the script uses git
 # filters, specified in .gitattributes files.  Only files that have the filter
 # set as "uncrustify" (or something ending in "uncrustify") are processed: if
-# other files have been changed, they are ignored by the script.
+# other files have been changed, they are ignored by the script.  Files passed
+# to uncrustify, as well as files with filter "copyright", get their copyright
+# header checked.
 #
 # If you want to run uncrustify automatically for changes you make, there are
 # two options:
 # The pre-commit hook + manually running the script gives better/more intuitive
 # control (with the filter, it is possible to have a work tree that is
 # different from HEAD and still have an empty 'git diff') and provides better
-# performance for changes that modify many files.
+# performance for changes that modify many files.  It is the only way that
+# currently also checks the copyright headers.
 # The filter allows one to transparently merge branches that have not been run
 # through uncrustify, and is applied more consistently (the pre-commit hook is
 # not run for every commit, e.g., during a rebase).
 
 # Parse command-line arguments
 function usage() {
-    echo "usage: uncrustify.sh [-f|--force] [--rev=REV] [action]"
-    echo "actions: (check|diff|update)[-(index|workdir)] (default:check-workdir)"
+    echo "usage: uncrustify.sh [-f|--force] [--rev=REV]"
+    echo "           [--uncrustify=(off|check)] [--copyright=<cmode>] [<action>]"
+    echo "<action>: (check*|diff|update)[-(index|workdir*)] (*=default)"
+    echo "<cmode>:  off|add|update*|replace|full"
 }
 
 action="check-workdir"
 declare -a diffargs
 baserev="HEAD"
 force=
+uncrustify_mode=check
+copyright_mode=update
 for arg in "$@" ; do
     if [[ "$arg" == "check-index" || "$arg" == "check-workdir" || \
           "$arg" == "diff-index" || "$arg" == "diff-workdir" || \
@@ -109,6 +129,16 @@ for arg in "$@" ; do
         action=$arg-workdir
     elif [[ "$action" == diff-* ]] ; then
         diffargs+=("$arg")
+    elif [[ "$arg" == --uncrustify=* ]] ; then
+        uncrustify_mode=${arg#--uncrustify=}
+        if [[ "$uncrustify_mode" != "off" && "$uncrustify_mode" != "check" ]] ; then
+            echo "Unknown option: $arg"
+            echo
+            usage
+            exit 2
+        fi
+    elif [[ "$arg" == --copyright=* ]] ; then
+        copyright_mode=${arg#--copyright=}
     elif [[ "$arg" == "-f" || "$arg" == "--force" ]] ; then
         force=1
     elif [[ "$arg" == --rev=* ]] ; then
@@ -125,17 +155,20 @@ for arg in "$@" ; do
 done
 
 # Check that uncrustify is present
-if [ -z "$UNCRUSTIFY" ]
+if [[ "$uncrustify_mode" != "off" ]]
 then
-    echo "Please set the path to uncrustify using UNCRUSTIFY."
-    echo "Note that you need a custom version of uncrustify."
-    echo "See comments in the script file for how to get one."
-    exit 2
-fi
-if ! which "$UNCRUSTIFY" 1>/dev/null
-then
-    echo "Uncrustify not found: $UNCRUSTIFY"
-    exit 2
+    if [ -z "$UNCRUSTIFY" ]
+    then
+        echo "Please set the path to uncrustify using UNCRUSTIFY."
+        echo "Note that you need a custom version of uncrustify."
+        echo "See comments in the script file for how to get one."
+        exit 2
+    fi
+    if ! which "$UNCRUSTIFY" 1>/dev/null
+    then
+        echo "Uncrustify not found: $UNCRUSTIFY"
+        exit 2
+    fi
 fi
 
 # Switch to the root of the source tree and check the config file
@@ -153,7 +186,7 @@ fi
 tmpdir=`mktemp -d -t gmxuncrust.XXXXXX`
 
 # Produce a list of changed files
-# Only include files that have uncrustify set as filter in .gitattributes
+# Only include files that have proper filter set in .gitattributes
 internal_diff_args=
 if [[ $action == *-index ]]
 then
@@ -164,29 +197,83 @@ cut -f2 <$tmpdir/difflist | \
     git check-attr --stdin filter | \
     sed -e 's/.*: filter: //' | \
     paste $tmpdir/difflist - | \
-    grep 'uncrustify$' >$tmpdir/filtered
-cut -f2 <$tmpdir/filtered >$tmpdir/filelist
-git diff-files --name-only | grep -Ff $tmpdir/filelist >$tmpdir/localmods
+    grep -E '(uncrustify|copyright)$' >$tmpdir/filtered
+cut -f2 <$tmpdir/filtered >$tmpdir/filelist_all
+grep 'uncrustify$' <$tmpdir/filtered | cut -f2 >$tmpdir/filelist_uncrustify
+git diff-files --name-only | grep -Ff $tmpdir/filelist_all >$tmpdir/localmods
 
 # Extract changed files to a temporary directory
 mkdir $tmpdir/org
-mkdir $tmpdir/new
 if [[ $action == *-index ]] ; then
-    git checkout-index --prefix=$tmpdir/org/ --stdin <$tmpdir/filelist
+    git checkout-index --prefix=$tmpdir/org/ --stdin <$tmpdir/filelist_all
 else
-    rsync --files-from=$tmpdir/filelist $srcdir $tmpdir/org
+    rsync --files-from=$tmpdir/filelist_all $srcdir $tmpdir/org
 fi
+# Duplicate the original files to a separate directory, where all changes will
+# be made.
+cp -r $tmpdir/org $tmpdir/new
+
+# Create output file for what was done (in case no messages get written)
+touch $tmpdir/messages
 
 # Run uncrustify on the temporary directory
-cd $tmpdir/org
+cd $tmpdir/new
+if [[ $uncrustify_mode != "off" ]] ; then
+    if ! $UNCRUSTIFY -c $cfg_file -F $tmpdir/filelist_uncrustify --no-backup >$tmpdir/uncrustify.out 2>&1 ; then
+        echo "Reformatting failed. Check uncrustify output below for errors:"
+        cat $tmpdir/uncrustify.out
+        rm -rf $tmpdir
+        exit 2
+    fi
+    # Find the changed files if necessary
+    if [[ $action != diff-* ]] ; then
+        msg="needs uncrustify"
+        if [[ $action == update-* ]] ; then
+            msg="uncrustified"
+        fi
+        git diff --no-index --name-only ../org/ . | \
+            awk -v msg="$msg" '{sub(/.\//,""); print $0 ": " msg}' >> $tmpdir/messages
+    fi
+    # TODO: Consider checking whether rerunning uncrustify causes additional changes
+fi
 
-if ! $UNCRUSTIFY -c $cfg_file -F $tmpdir/filelist --prefix=../new/ >$tmpdir/uncrustify.out 2>&1 ; then
-    echo "Reformatting failed. Check uncrustify output below for errors:"
-    cat $tmpdir/uncrustify.out
-    rm -rf $tmpdir
-    exit 2
+# Update the copyright headers using the requested mode
+if [[ $copyright_mode != "off" ]] ; then
+    cpscript_args="--update-year"
+    case "$copyright_mode" in
+        year)
+            ;;
+        add)
+            cpscript_args+=" --add-missing"
+            ;;
+        update)
+            cpscript_args+=" --add-missing --update-header"
+            ;;
+        replace)
+            cpscript_args+=" --replace-header"
+            ;;
+        full)
+            cpscript_args+=" --add-missing --update-header --replace-header"
+            ;;
+        *)
+            echo "Unknown copyright mode: $copyright_mode"
+            exit 2
+    if [[ $copyright -eq 2 ]] ; then
+        cpscript_args+=" --update-header"
+    fi
+    esac
+    if [[ $action == check-* ]] ; then
+        cpscript_args+=" --check"
+    fi
+    # TODO: Probably better to invoke python explicitly through a customizable
+    # variable.
+    if ! $admin_dir/copyright.py -F $tmpdir/filelist_all $cpscript_args >>$tmpdir/messages
+    then
+        echo "Copyright checking failed!"
+        rm -rf $tmpdir
+        exit 2
+    fi
 fi
-# TODO: Consider checking whether rerunning uncrustify causes additional changes
 
 cd $tmpdir
 
@@ -198,24 +285,23 @@ if [[ $action == diff-* ]] ; then
 fi
 
 # Find the changed files
-touch $tmpdir/messages
 changes=
 set -o pipefail
 if ! git diff --no-index --name-only --exit-code org/ new/ | \
          sed -e 's#new/##' > $tmpdir/changed
 then
     changes=1
-    awk '{print $0 ": needs uncrustify"}' $tmpdir/changed \
-        >> $tmpdir/messages
 fi
+
 # Check if changed files have changed outside the index
 if grep -Ff $tmpdir/localmods $tmpdir/changed > $tmpdir/conflicts
 then
     awk '{print $0 ": has changes in work tree"}' $tmpdir/conflicts \
         >> $tmpdir/messages
     if [[ ! $force && $action == update-* ]] ; then
+        echo "Modified files found in work tree, skipping update. Use -f to override."
+        echo "The following would have been done:"
         sort $tmpdir/messages
-        echo "Modified files found in work tree, skipping update."
         rm -rf $tmpdir
         exit 2
     fi
@@ -247,11 +333,7 @@ elif [[ $action == update-workdir ]] ; then
 fi
 
 # Report what was done
-if [[ $action == update-* ]] ; then
-    sort $tmpdir/messages | sed -e 's/needs uncrustify/uncrustified/'
-else
-    sort $tmpdir/messages
-fi
+sort $tmpdir/messages
 
 rm -rf $tmpdir
 exit $changes
index 32dc5a7f2876555d201f7f78578c879d838ec36d..d58ee85c928f3f9299a3f235cac7def45bad61f8 100644 (file)
@@ -1 +1,2 @@
 * !filter
+gmock-1.6.0/CMakeLists.txt      filter=copyright