aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristen Kozak <sebright@google.com>2017-01-28 16:54:37 -0800
committerKristen Kozak <sebright@google.com>2017-03-24 16:26:37 -0700
commitc39e0694720db9d7674a61d51b47792776cdfda9 (patch)
treeda8f3881d7c5bc79e393a054eca62b6a94bbaaab
parenta57877ba0446c0da27573a0245c16205c9f79d19 (diff)
downloadopencensus-java-c39e0694720db9d7674a61d51b47792776cdfda9.tar.gz
Checks the commit history of pull requests in CI.
The script runs on Travis and only checks the commit history when the commit is from a pull request. It fails the build if the pull request contains merge commits or does not start from the branch that it is being merged into. If there is an unexpected exception, the script does not fail the build.
-rw-r--r--.travis.yml1
-rw-r--r--check-git-history.py88
2 files changed, 89 insertions, 0 deletions
diff --git a/.travis.yml b/.travis.yml
index 393fdbf1..9809c63d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,6 +24,7 @@ install:
fi
script:
+ - python check-git-history.py
- case "$TASK" in
"BUILD")
case "$TRAVIS_JDK_VERSION" in
diff --git a/check-git-history.py b/check-git-history.py
new file mode 100644
index 00000000..28656fa0
--- /dev/null
+++ b/check-git-history.py
@@ -0,0 +1,88 @@
+import os
+import sys
+import traceback
+
+def main(argv):
+ try:
+ # Only check the history if the build is running on a pull request. Travis
+ # always merges pull requests into the base branch before running the build.
+ if is_merged_pull_request():
+ # These functions assume that HEAD^1 is the base branch and HEAD^2 is the
+ # pull request.
+ exit_if_pull_request_has_merge_commits()
+ exit_if_pull_request_does_not_start_from_base_branch()
+ print 'Checked pull request history.'
+ else:
+ print 'Skipped history check.'
+ except Exception as e:
+ # Don't stop the build if this script has a bug.
+ traceback.print_exc(e)
+
+def is_merged_pull_request():
+ '''Returns true if the current commit is a merge between a pull request and
+ an existing branch.'''
+ # When Travis merges a pull request, the commit has no branches pointing to
+ # it, and the only decoration is HEAD.
+ return read_process('git show --no-patch --format="%D"').strip() == 'HEAD'
+
+def exit_if_pull_request_has_merge_commits():
+ '''Exits with an error if any of the commits added by the pull request are
+ merge commits.'''
+ # Print the parents of each commit added by the pull request.
+ git_command = 'git log --format="%P" HEAD^1..HEAD^2'
+ for line in os.popen(git_command):
+ parents = line.split()
+ assert len(parents) >= 1, line
+ if len(parents) > 1:
+ print 'Pull request contains a merge commit:'
+ print_history()
+ sys.exit(1)
+
+def exit_if_pull_request_does_not_start_from_base_branch():
+ '''Exits with an error if the pull request is not branched from a commit on
+ the base branch.'''
+ start = get_commit(list_pull_request_commits()[-1] + '^')
+ if not start in list_commits('HEAD^1'):
+ print 'Pull request does not start from the base branch:'
+ print_history()
+ sys.exit(1)
+
+def list_pull_request_commits():
+ '''Returns a list of all commit hashes that are contained in the pull
+ request but not the base branch.'''
+ return get_log_commits('git log --format="%H" HEAD^1..HEAD^2')
+
+def list_commits(branch):
+ '''Returns a list of all commit hashes on the given branch, following only
+ the first parent.'''
+ return get_log_commits('git log --first-parent --format="%H" ' + branch)
+
+def get_log_commits(git_log_command):
+ commits = []
+ for line in os.popen(git_log_command):
+ commit = line.strip()
+ assert_commit(commit)
+ commits.append(commit)
+ return commits
+
+def get_commit(revision):
+ git_command = 'git show --no-patch --format="%H" ' + revision
+ commit = read_process(git_command).strip()
+ assert_commit(commit)
+ return commit
+
+def assert_commit(commit):
+ '''Assert that the string has the format of a git commit hash.'''
+ assert set(commit) <= set('abcdef0123456789'), commit
+ assert len(commit) == 40, commit
+
+def print_history():
+ os.system('git log HEAD^1 HEAD^2 -30 --graph --oneline --decorate')
+
+def read_process(command):
+ '''Runs a command and returns everything printed to stdout.'''
+ with os.popen(command, 'r') as fd:
+ return fd.read()
+
+if __name__ == '__main__':
+ main(sys.argv)