From 72428350c218728bad948afc828b2ebfda080ab3 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 1 Mar 2018 09:47:58 -0500 Subject: [PATCH 1/2] Audit for missing reverse integration upon committing to master This is meant as a nag. I'm not sure there'd be a good way to catch-us-in-the-act unless we migrate to some merge tools a la git-flow. // FREEBIE --- Scripts/git_hooks/README.md | 1 + Scripts/git_hooks/post-commit | 3 + Scripts/git_hooks/pre-commit | 141 +++++++++++++++++++++++++++ Scripts/reverse_integration_check.py | 45 +++++++++ 4 files changed, 190 insertions(+) create mode 100644 Scripts/git_hooks/README.md create mode 100755 Scripts/git_hooks/post-commit create mode 100755 Scripts/git_hooks/pre-commit create mode 100755 Scripts/reverse_integration_check.py diff --git a/Scripts/git_hooks/README.md b/Scripts/git_hooks/README.md new file mode 100644 index 000000000..146c2df48 --- /dev/null +++ b/Scripts/git_hooks/README.md @@ -0,0 +1 @@ +Copy these git hooks into .git/hooks diff --git a/Scripts/git_hooks/post-commit b/Scripts/git_hooks/post-commit new file mode 100755 index 000000000..ed392f169 --- /dev/null +++ b/Scripts/git_hooks/post-commit @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +Scripts/reverse_integration_check.py diff --git a/Scripts/git_hooks/pre-commit b/Scripts/git_hooks/pre-commit new file mode 100755 index 000000000..7c72b84e5 --- /dev/null +++ b/Scripts/git_hooks/pre-commit @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import subprocess +import datetime +import argparse +import commands + + +git_repo_path = os.path.abspath(subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).strip()) + + +def splitall(path): + allparts = [] + while 1: + parts = os.path.split(path) + if parts[0] == path: # sentinel for absolute paths + allparts.insert(0, parts[0]) + break + elif parts[1] == path: # sentinel for relative paths + allparts.insert(0, parts[1]) + break + else: + path = parts[0] + allparts.insert(0, parts[1]) + return allparts + + +def process(filepath): + + short_filepath = filepath[len(git_repo_path):] + if short_filepath.startswith(os.sep): + short_filepath = short_filepath[len(os.sep):] + + filename = os.path.basename(filepath) + if filename.startswith('.'): + return + file_ext = os.path.splitext(filename)[1] + if file_ext in ('.swift'): + env_copy = os.environ.copy() + env_copy["SCRIPT_INPUT_FILE_COUNT"] = "1" + env_copy["SCRIPT_INPUT_FILE_0"] = '%s' % ( short_filepath, ) + lint_output = subprocess.check_output(['swiftlint', 'autocorrect', '--use-script-input-files'], env=env_copy) + print lint_output + try: + lint_output = subprocess.check_output(['swiftlint', 'lint', '--use-script-input-files'], env=env_copy) + except subprocess.CalledProcessError, e: + lint_output = e.output + print lint_output + + with open(filepath, 'rt') as f: + text = f.read() + original_text = text + + lines = text.split('\n') + while lines and lines[0].startswith('//'): + lines = lines[1:] + text = '\n'.join(lines) + text = text.strip() + + header = '''// +// Copyright (c) %s Open Whisper Systems. All rights reserved. +// + +''' % ( + datetime.datetime.now().year, + ) + text = header + text + '\n' + + if original_text == text: + return + + print 'Updating:', short_filepath + + with open(filepath, 'wt') as f: + f.write(text) + + +def should_ignore_path(path): + ignore_paths = [ + os.path.join(git_repo_path, '.git') + ] + for ignore_path in ignore_paths: + if path.startswith(ignore_path): + return True + for component in splitall(path): + if component.startswith('.'): + return True + if component.endswith('.framework'): + return True + if component in ('Pods', 'ThirdParty', 'Carthage',): + return True + + return False + + +def process_if_appropriate(filepath): + filename = os.path.basename(filepath) + if filename.startswith('.'): + return + file_ext = os.path.splitext(filename)[1] + if file_ext not in ('.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift'): + return + if should_ignore_path(filepath): + return + process(filepath) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Precommit script.') + parser.add_argument('--all', action='store_true', help='process all files in or below current dir') + args = parser.parse_args() + + if args.all: + for rootdir, dirnames, filenames in os.walk(git_repo_path): + for filename in filenames: + file_path = os.path.abspath(os.path.join(rootdir, filename)) + process_if_appropriate(file_path) + else: + filepaths = [] + + # Staging + output = commands.getoutput('git diff --cached --name-only --diff-filter=ACMR') + filepaths.extend([line.strip() for line in output.split('\n')]) + + # Working + output = commands.getoutput('git diff --name-only --diff-filter=ACMR') + filepaths.extend([line.strip() for line in output.split('\n')]) + + # Only process each path once. + filepaths = sorted(set(filepaths)) + + for filepath in filepaths: + filepath = os.path.abspath(os.path.join(git_repo_path, filepath)) + process_if_appropriate(filepath) + + print 'git clang-format...' + print commands.getoutput('git clang-format') diff --git a/Scripts/reverse_integration_check.py b/Scripts/reverse_integration_check.py new file mode 100755 index 000000000..c366354ba --- /dev/null +++ b/Scripts/reverse_integration_check.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +# When we make a hotfix, we need to reverse integrate our hotfix back into +# master. After commiting to master, this script audits that all tags have been +# reverse integrated. +import subprocess +from distutils.version import LooseVersion +import logging + +#logging.basicConfig(level=logging.DEBUG) + +def is_on_master(): + output = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).strip() + logging.debug("branch output: %s" % output) + return output == "master" + +def main(): + if not is_on_master(): + # Don't interfere while on a feature or hotfix branch + logging.debug("not on master branch") + return + + logging.debug("on master branch") + + unmerged_tags_output = subprocess.check_output(["git", "tag", "--no-merged", "master"]) + unmerged_tags = [line.strip() for line in unmerged_tags_output.split("\n") if len(line) > 0] + + logging.debug("All unmerged tags: %s" % unmerged_tags) + + # Before this point we weren't always reverse integrating our tags. As we + # audit old tags, we can ratchet this version number back. + epoch_tag="2.21.0" + + logging.debug("ignoring tags before epoch_tag: %s" % epoch_tag) + + tags_of_concern = [unmerged_tag for unmerged_tag in unmerged_tags if LooseVersion(unmerged_tag) > LooseVersion(epoch_tag)] + + if len(tags_of_concern) > 0: + logging.debug("Found unmerged tags newer than epoch: %s" % tags_of_concern) + raise RuntimeError("Found unmerged tags: %s" % tags_of_concern) + else: + logging.debug("No unmerged tags newer than epoch. All good!") + +if __name__ == "__main__": + main() From 17ed0f610047964e48dc95f4d3bfaeaaf2ca5f18 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 2 Mar 2018 15:05:16 -0500 Subject: [PATCH 2/2] dedupe git hooks // FREEBIE --- Scripts/git_hooks/pre-commit | 142 +----------------- .../Utilities => Scripts}/precommit.py | 0 SignalServiceKit/Utilities/post-commit | 12 -- 3 files changed, 2 insertions(+), 152 deletions(-) rename {SignalServiceKit/Utilities => Scripts}/precommit.py (100%) delete mode 100755 SignalServiceKit/Utilities/post-commit diff --git a/Scripts/git_hooks/pre-commit b/Scripts/git_hooks/pre-commit index 7c72b84e5..89a316307 100755 --- a/Scripts/git_hooks/pre-commit +++ b/Scripts/git_hooks/pre-commit @@ -1,141 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +#!/usr/bin/env bash -import os -import sys -import subprocess -import datetime -import argparse -import commands - - -git_repo_path = os.path.abspath(subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).strip()) - - -def splitall(path): - allparts = [] - while 1: - parts = os.path.split(path) - if parts[0] == path: # sentinel for absolute paths - allparts.insert(0, parts[0]) - break - elif parts[1] == path: # sentinel for relative paths - allparts.insert(0, parts[1]) - break - else: - path = parts[0] - allparts.insert(0, parts[1]) - return allparts - - -def process(filepath): - - short_filepath = filepath[len(git_repo_path):] - if short_filepath.startswith(os.sep): - short_filepath = short_filepath[len(os.sep):] - - filename = os.path.basename(filepath) - if filename.startswith('.'): - return - file_ext = os.path.splitext(filename)[1] - if file_ext in ('.swift'): - env_copy = os.environ.copy() - env_copy["SCRIPT_INPUT_FILE_COUNT"] = "1" - env_copy["SCRIPT_INPUT_FILE_0"] = '%s' % ( short_filepath, ) - lint_output = subprocess.check_output(['swiftlint', 'autocorrect', '--use-script-input-files'], env=env_copy) - print lint_output - try: - lint_output = subprocess.check_output(['swiftlint', 'lint', '--use-script-input-files'], env=env_copy) - except subprocess.CalledProcessError, e: - lint_output = e.output - print lint_output - - with open(filepath, 'rt') as f: - text = f.read() - original_text = text - - lines = text.split('\n') - while lines and lines[0].startswith('//'): - lines = lines[1:] - text = '\n'.join(lines) - text = text.strip() - - header = '''// -// Copyright (c) %s Open Whisper Systems. All rights reserved. -// - -''' % ( - datetime.datetime.now().year, - ) - text = header + text + '\n' - - if original_text == text: - return - - print 'Updating:', short_filepath - - with open(filepath, 'wt') as f: - f.write(text) - - -def should_ignore_path(path): - ignore_paths = [ - os.path.join(git_repo_path, '.git') - ] - for ignore_path in ignore_paths: - if path.startswith(ignore_path): - return True - for component in splitall(path): - if component.startswith('.'): - return True - if component.endswith('.framework'): - return True - if component in ('Pods', 'ThirdParty', 'Carthage',): - return True - - return False - - -def process_if_appropriate(filepath): - filename = os.path.basename(filepath) - if filename.startswith('.'): - return - file_ext = os.path.splitext(filename)[1] - if file_ext not in ('.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift'): - return - if should_ignore_path(filepath): - return - process(filepath) - - -if __name__ == "__main__": - - parser = argparse.ArgumentParser(description='Precommit script.') - parser.add_argument('--all', action='store_true', help='process all files in or below current dir') - args = parser.parse_args() - - if args.all: - for rootdir, dirnames, filenames in os.walk(git_repo_path): - for filename in filenames: - file_path = os.path.abspath(os.path.join(rootdir, filename)) - process_if_appropriate(file_path) - else: - filepaths = [] - - # Staging - output = commands.getoutput('git diff --cached --name-only --diff-filter=ACMR') - filepaths.extend([line.strip() for line in output.split('\n')]) - - # Working - output = commands.getoutput('git diff --name-only --diff-filter=ACMR') - filepaths.extend([line.strip() for line in output.split('\n')]) - - # Only process each path once. - filepaths = sorted(set(filepaths)) - - for filepath in filepaths: - filepath = os.path.abspath(os.path.join(git_repo_path, filepath)) - process_if_appropriate(filepath) - - print 'git clang-format...' - print commands.getoutput('git clang-format') +Scripts/precommit.py diff --git a/SignalServiceKit/Utilities/precommit.py b/Scripts/precommit.py similarity index 100% rename from SignalServiceKit/Utilities/precommit.py rename to Scripts/precommit.py diff --git a/SignalServiceKit/Utilities/post-commit b/SignalServiceKit/Utilities/post-commit deleted file mode 100755 index 8ce81df92..000000000 --- a/SignalServiceKit/Utilities/post-commit +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -git log -1 HEAD | grep -i FREEBIE > /dev/null - -if [[ $? -ne 0 ]] -then - cat <