You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by jo...@apache.org on 2015/12/12 19:57:58 UTC

[1/3] mesos git commit: Added support for github to apply-reviews.py.

Repository: mesos
Updated Branches:
  refs/heads/master 0a6b820d1 -> 9ea22dc74


Added support for github to apply-reviews.py.

Review: https://reviews.apache.org/r/39410


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/c3e7ee71
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/c3e7ee71
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/c3e7ee71

Branch: refs/heads/master
Commit: c3e7ee7185c41c2177bfd048f745e6d7dc9b89e0
Parents: 0a6b820
Author: Artem Harutyunyan <ar...@mesosphere.io>
Authored: Sat Dec 12 10:50:32 2015 -0800
Committer: Joris Van Remoortere <jo...@gmail.com>
Committed: Sat Dec 12 10:53:39 2015 -0800

----------------------------------------------------------------------
 support/apply-reviews.py | 298 ++++++++++++++++++++++++++++++------------
 1 file changed, 213 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/c3e7ee71/support/apply-reviews.py
----------------------------------------------------------------------
diff --git a/support/apply-reviews.py b/support/apply-reviews.py
index 6ea229f..7ef447b 100755
--- a/support/apply-reviews.py
+++ b/support/apply-reviews.py
@@ -2,39 +2,57 @@
 import argparse
 import atexit
 import json
+import linecache
+import os
 import re
 import subprocess
 import sys
 import urllib2
 
 
-REVIEW_REQUEST_BASE_URL = 'https://reviews.apache.org/r'
-REVIEW_REQUEST_API_URL = 'https://reviews.apache.org/api/review-requests'
-USER_URL = 'https://reviews.apache.org/api/users'
+REVIEWBOARD_REVIEW_URL = 'https://reviews.apache.org/r'
+REVIEWBOARD_API_URL =\
+  'https://reviews.apache.org/api/review-requests'
+REVIEWBOARD_USER_URL = 'https://reviews.apache.org/api/users'
+
+
+GITHUB_URL = 'https://api.github.com/repos/apache/mesos/pulls'
+GITHUB_PATCH_URL =\
+  'https://patch-diff.githubusercontent.com/raw/apache/mesos/pull'
 
 
 def review_api_url(review_id):
   """Returns a Review Board API URL given a review ID."""
   # Reviewboard REST API expects '/' at the end of the URL.
-  return '{api}/{review}/'.format(api=REVIEW_REQUEST_API_URL, review=review_id)
+  return '{base}/{review}/'.format(base=REVIEWBOARD_API_URL, review=review_id)
 
 
 def review_url(review_id):
   """Returns a Review Board UI URL given a review ID."""
-  return '{base}/{review}'.format(base=REVIEW_REQUEST_BASE_URL, review=review_id)
+  return '{base}/{review}/'.format(base=REVIEWBOARD_REVIEW_URL, review=review_id)
+
 
+def pull_request_url(pull_request_number):
+  """Returns a GitHub pull request URL given a PR number."""
+  return '{base}/{pr}'.format(base=GITHUB_URL, pr=pull_request_number)
 
-def user_url(username):
+
+def reviewboard_user_url(username):
   """Returns a Review Board URL for a user given a username."""
   # Reviewboard REST API expects '/' at the end of the URL.
-  return '{base}/{user}/'.format(base=USER_URL, user=username)
+  return '{base}/{user}/'.format(base=REVIEWBOARD_USER_URL, user=username)
 
 
-def patch_url(review_id):
-  """Returns a Review Board URL for a patch given a review ID."""
-  # Reviewboard REST API expects '/' at the end of the URL.
-  return '{base}/{review}/diff/raw/'.format(base=REVIEW_REQUEST_BASE_URL,
-                                            review=review_id)
+def patch_url():
+  """Returns a Review Board or a GitHub URL for a patch."""
+  if options['review_id']:
+    # Reviewboard REST API expects '/' at the end of the URL.
+    return '{base}/{review}/diff/raw/'.format(base=REVIEWBOARD_REVIEW_URL,
+                                              review=options['review_id'])
+  elif options['github']:
+    return '{base}/{patch}.patch'.format(base=GITHUB_PATCH_URL,
+                                         patch=options['github'])
+  return None
 
 
 def url_to_json(url):
@@ -45,7 +63,7 @@ def url_to_json(url):
 
 def extract_review_id(url):
   """Extracts review ID from Review Board URL."""
-  review_id = re.search(REVIEW_REQUEST_API_URL + '/(\d+)/', url)
+  review_id = re.search(REVIEWBOARD_API_URL + '/(\d+)/', url)
   if review_id:
     return review_id.group(1)
 
@@ -60,19 +78,19 @@ def review_chain(review_id):
     return []
 
   # Verify that the review has exactly one parent.
-  depends = json_obj.get('review_request').get('depends_on')
-
-  if len(depends) > 1:
-    sys.stderr.write('Error: Review {review} has more than one '
-                     'parent\n'.format(review=review_id))
+  parent = json_obj.get('review_request').get('depends_on')
+  if len(parent) > 1:
+    sys.stderr.write(
+      'Error: Review {review} has more than one parent'\
+      .format(review=review_id))
     sys.exit(1)
-  elif len(depends) == 0:
+  elif len(parent) == 0:
     return [(review_id, json_obj.get('review_request').get('summary'))]
   else:
     # The review has exactly one parent.
-    review = (review_id, json_obj.get('review_request').get('summary'))
-    review_list = review_chain(extract_review_id(depends[0].get('href')))
+    review_list = review_chain(extract_review_id(parent[0].get('href')))
 
+    review = (review_id, json_obj.get('review_request').get('summary'))
     if review not in review_list:
       return review_list + [review]
     else:
@@ -81,10 +99,10 @@ def review_chain(review_id):
       sys.exit(1)
 
 
-def shell(command):
-  """Runs a command in a shell, unless the dry-run option is present (in which
+def shell(command, dry_run):
+  """Runs a command in a shell, unless the dry-run option is set (in which
   case it just prints the command."""
-  if options['dry_run']:
+  if dry_run:
     print command
     return
 
@@ -93,37 +111,63 @@ def shell(command):
     sys.exit(error_code)
 
 
-def remove_patch(review_id):
-  """Removes the patch file."""
-  command = 'rm -f %s.patch' % review_id
-  shell(command);
-
+def remove_patch(file_name=None):
+  """Removes the file. In case the file name is not provided it reads the
+  file name from global options dictionary."""
+  if file_name == None:
+    cmd = 'rm -f {_file}.patch'.format(_file=patch_id())
+  else:
+    cmd = 'rm -f {_file}'.format(_file=file_name)
 
-def apply_review(review_id):
-  """Applies a review with a given ID locally."""
-  atexit.register(lambda: remove_patch(review_id))
-  fetch_patch(review_id)
-  apply_patch(review_id)
+  # In case of github we always need to fetch the patch to extract username and
+  # email, so to ensure that it always gets cleaned up we ignore the dry_run
+  # option by always setting the second parameter to False.
+  if options['github']:
+    shell(cmd, False)
+  else:
+    shell(cmd, options['dry_run'])
 
-  review = review_data(review_id)
 
-  commit_patch(review)
-  if (not options['no_amend']):
-    amend()
-  remove_patch(review_id)
+def apply_review():
+  """Applies a review with a given ID locally."""
+  # Make sure we don't leave the patch behind in case of failure.
+  atexit.register(lambda: remove_patch('{_file}.patch'\
+                                       .format(_file=patch_id())))
+
+  fetch_patch()
+  apply_patch()
+  commit_patch()
+  remove_patch()
+
+
+def fetch_patch():
+  """Fetches a patch from Review Board or GitHub."""
+  cmd = ' '.join(['wget',
+                 '--no-check-certificate',
+                 '--no-verbose',
+                 '-O '
+                 '{review_id}.patch',
+                 '{url}'])\
+                 .format(review_id=patch_id(), url=patch_url())
+
+  # In case of github we always need to fetch the patch to extract username
+  # and email, so we ignore the dry_run option by setting the second parameter
+  # to False.
+  if options['github']:
+    shell(cmd, False)
+  else:
+    shell(cmd, options['dry_run'])
 
 
-def fetch_patch(review_id):
-  """Fetches patch from the Review Board."""
-  command = 'wget --no-check-certificate --no-verbose -O {review_id}.patch '\
-            '{url}'.format(review_id=review_id , url=patch_url(review_id))
-  shell(command)
+def patch_id():
+  return (options['review_id'] or options['github'])
 
 
-def apply_patch(review_id):
+def apply_patch():
   """Applies patch locally."""
-  command = 'git apply --index {review_id}.patch'.format(review_id=review_id)
-  shell(command)
+  cmd = 'git apply --index {review_id}.patch'\
+        .format(review_id=patch_id())
+  shell(cmd, options['dry_run'])
 
 
 def quote(string):
@@ -131,61 +175,109 @@ def quote(string):
   return string.replace("'", "'\\''")
 
 
-def commit_patch(review):
-  """Commit patch locally."""
-  command = 'git commit --author \'{author} <{email}>\' -am \'{message}\''\
-            .format(author=quote(review['author']),
-                    email=quote(review['email']),
-                    message=quote(review['message']))
-  shell(command)
+def commit_patch():
+  """Commits patch locally."""
+  data = patch_data()
+
+  # Check whether we need to amend the commit message.
+  if options['no_amend']:
+    amend = ''
+  else:
+    amend = '-e'
+
+  cmd = 'git commit --author \'{author}\' {_amend} -am \'{message}\''\
+        .format(author=quote(data['author']),
+                _amend=amend,
+                message=quote(data['message']))
+  shell(cmd, options['dry_run'])
+
+
+def patch_data():
+  """Populates and returns a dictionary with data necessary for committing the
+  patch (such as the message, the author, etc.)."""
+  if options['review_id']:
+    return reviewboard_data()
+  elif options['github']:
+    return github_data()
+  else:
+    return None
+
+
+def get_author(patch):
+  """Reads the author name and email from the .patch file"""
+  author = linecache.getline(patch, 2)
+  return author.replace('From: ', '').rstrip()
+
+
+def github_data():
+  pull_request_number = options['github']
+  pull_request = url_to_json(pull_request_url(pull_request_number))
 
+  title = pull_request.get('title')
+  description = pull_request.get('body')
+  url = '{url}/{pr}'.format(url=GITHUB_URL, pr=pull_request_number)
+  author = get_author('{pr}.patch'.format(pr=pull_request_number))
+  message = '\n\n'.join(['{summary}',
+                         '{description}',
+                         'This closes: {pr}'])\
+                         .format(summary=title,
+                                 description=description,
+                                 pr=pull_request_number)
+
+  review_data = {
+    "summary": title,
+    "description": description,
+    "url": url,
+    "author": author,
+    "message": message
+  }
 
-def review_data(review_id):
+  return review_data
+
+
+def reviewboard_data():
   """Fetches review data and populates internal data structure."""
+  review_id = options['review_id']
+
   # Populate review object.
   review = url_to_json(review_api_url(review_id)).get('review_request')
 
   url = review_url(review_id)
 
-  # Populate and escape commit message.
-  message = '{summary}\n\n{description}\n\nReview: {_url}'\
-            .format(summary=review.get('summary'),
-                    description=review.get('description'),
-                    _url=url)
-
   # Populate user object.
-  user = url_to_json(user_url(review.get('links').get('submitter')\
-                              .get('title'))).get('user')
+  user = url_to_json(reviewboard_user_url(
+    review.get('links').get('submitter').get('title'))).get('user')
+
+  author = '{author} <{email}>'.format(author=user.get('fullname'),
+                                       email=user.get('email'))
+  message = '\n\n'.join(['{summary}',
+                          '{description}',
+                          'Review: {review_url}'])\
+                          .format(summary=review.get('summary'),
+                                  description=review.get('description'),
+                                  review_url=url)
 
   review_data = {
-    'summary': review.get('summary'),
-    'description': review.get('description'),
-    'url': url,
-    'author': user.get('fullname'),
-    'email': user.get('email'),
-    'message': message
+    "summary": review.get('summary'),
+    "description": review.get('description'),
+    "url": url,
+    "author": author,
+    "message": message
   }
 
   return review_data
 
 
-def amend():
-  """Amends commit."""
-  command = 'git commit --amend'
-  shell(command)
-
 # A global dictionary for holding execution options.
 options = {}
 
-if __name__ == "__main__":
-  # Parse command line.
-  parser = argparse.ArgumentParser(description='Recursively apply Review '
-                                   'Board reviews.')
-  parser.add_argument('-r',
-                      '--review-id',
-                      metavar='REVIEW_ID',
-                      help='Numeric review ID that needs to be applied.',
-                      required=True)
+
+def parse_options():
+  """Parses command line options and populates the dictionary."""
+  parser = argparse.ArgumentParser(
+    description = 'Recursively apply Review Board reviews'
+                  ' and GitHub pull requests.')
+
   parser.add_argument('-d',
                       '--dry-run',
                       action='store_true',
@@ -194,13 +286,25 @@ if __name__ == "__main__":
                       action='store_true',
                       help='Do not amend commit message.')
 
+  # Add -g and -r and make them mutually exclusive.
+  group = parser.add_mutually_exclusive_group(required=True)
+  group.add_argument('-g', '--github',
+                     metavar='PULL_REQUEST',
+                     help='Pull request number')
+  group.add_argument('-r', '--review-id',
+                      metavar='REVIEW_ID',
+                      help='Numeric Review ID')
+
   args = parser.parse_args()
 
-  # Populate the global options dictionary.
   options['review_id'] = args.review_id
   options['dry_run'] = args.dry_run
   options['no_amend'] = args.no_amend
+  options['github'] = args.github
 
+
+def reviewboard():
+  """Applies a chain of reviewboard patches."""
   # Retrieve the list of reviews to apply.
   reviews = review_chain(options['review_id'])
 
@@ -208,4 +312,28 @@ if __name__ == "__main__":
   for review_id, summary in reviews:
     if review_id not in applied:
       applied.add(review_id)
-      apply_review(review_id)
+      options['review_id'] = review_id
+      apply_review()
+
+
+def github():
+  """Applies a patch from github."""
+  apply_review()
+
+
+# A global dictionary for holding command line options. See parse_options()
+# function for details.
+#
+# TODO(hartem): Consider getting rid of global options variable. Either use
+# explicit arguments or classes.
+options = {}
+
+
+if __name__ == "__main__":
+  # Parse command line options and populate the 'options' dictionary.
+  parse_options()
+
+  if options['review_id']:
+    reviewboard()
+  else:
+    github()


[3/3] mesos git commit: Updated apply-review.sh to use apply-reviews.py.

Posted by jo...@apache.org.
Updated apply-review.sh to use apply-reviews.py.

Review: https://reviews.apache.org/r/40129


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/9ea22dc7
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/9ea22dc7
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/9ea22dc7

Branch: refs/heads/master
Commit: 9ea22dc744c04efa762e5bb58f2f32aabf589b1f
Parents: 68cefcb
Author: Artem Harutyunyan <ar...@mesosphere.io>
Authored: Sat Dec 12 10:51:12 2015 -0800
Committer: Joris Van Remoortere <jo...@gmail.com>
Committed: Sat Dec 12 10:53:52 2015 -0800

----------------------------------------------------------------------
 support/apply-review.sh | 119 ++-----------------------------------------
 1 file changed, 3 insertions(+), 116 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/9ea22dc7/support/apply-review.sh
----------------------------------------------------------------------
diff --git a/support/apply-review.sh b/support/apply-review.sh
index 6391451..3a73492 100755
--- a/support/apply-review.sh
+++ b/support/apply-review.sh
@@ -1,121 +1,8 @@
 #!/usr/bin/env bash
 
-# Provides a tool to "apply" a review from Review Board or a
-# pull request from Github.
-
-# Use 'atexit' for cleanup.
-. $(dirname ${0})/atexit.sh
-
-# Use colors for errors.
-. $(dirname ${0})/colors.sh
-
-JSONURL=$(dirname ${0})/jsonurl.py
-GITHUB_URL="https://github.com/apache/mesos/pull"
-REVIEWBOARD_URL="https://reviews.apache.org/r"
-
-function usage {
-cat <<EOF
-Apache Mesos apply patch tool.
-
-Usage: $0 [-h] [-n] [-r | -g] <ID Number>
-
-  -h   Print this help message and exit
-  -n   Don't amend the commit message
-  -r   Apply a patch from Review Board (default)
-  -g   Apply a patch from Github
-EOF
-}
-
-AMEND=true
-REVIEW_LOCATION='reviewboard'
-while getopts ":nhrg" opt; do
-  case $opt in
-    n)
-      AMEND=false
-      ;;
-    r)
-      REVIEW_LOCATION='reviewboard'
-      ;;
-    g)
-      REVIEW_LOCATION='github'
-      ;;
-    h)
-      usage
-      exit 0
-      ;;
-    *)
-      echo "Unknown option: -$OPTARG"
-      usage
-      exit 1
-      ;;
-  esac
-done
-
-shift $(($OPTIND - 1))
-if test ${#} -ne 1; then
-  usage
+if [ ! -f support/apply-reviews.py ]; then
+  echo 'Please run this script from the root of Mesos source directory.'
   exit 1
 fi
 
-REVIEW=${1}
-
-if [[ "${REVIEW_LOCATION}" == "github" ]]; then
-  DIFF_URL="${GITHUB_URL}/${REVIEW}.patch"
-else
-  DIFF_URL="${REVIEWBOARD_URL}/${REVIEW}/diff/raw/"
-fi
-
-atexit "rm -f ${REVIEW}.patch"
-
-wget --no-check-certificate --no-verbose -O ${REVIEW}.patch ${DIFF_URL} || \
-  { echo "${RED}Failed to download patch${NORMAL}"; exit 1; }
-
-git apply --index ${REVIEW}.patch || \
-  { echo "${RED}Failed to apply patch${NORMAL}"; exit 1; }
-
-if [[ "${REVIEW_LOCATION}" == "reviewboard" ]]; then
-  API_URL="https://reviews.apache.org/api/review-requests/${REVIEW}/"
-
-  SUMMARY=$(${JSONURL} ${API_URL} review_request summary)
-  DESCRIPTION=$(${JSONURL} ${API_URL} review_request description)
-
-  USERNAME=$(${JSONURL} ${API_URL} review_request links submitter title)
-  USER_URL="https://reviews.apache.org/api/users/${USERNAME}/"
-
-  AUTHOR_NAME=$(${JSONURL} ${USER_URL} user fullname)
-  AUTHOR_EMAIL=$(${JSONURL} ${USER_URL} user email)
-  AUTHOR="${AUTHOR_NAME} <${AUTHOR_EMAIL}>"
-  REVIEW_URL="${REVIEWBOARD_URL}/${REVIEW}"
-
-elif [[ "${REVIEW_LOCATION}" == "github" ]]; then
-  API_URL="https://api.github.com/repos/apache/mesos/pulls/${REVIEW}"
-
-  SUMMARY=$(${JSONURL} ${API_URL} title)
-  DESCRIPTION=$(${JSONURL} ${API_URL} body)
-
-  AUTHOR=$(head -2 ${REVIEW}.patch | grep "From: " | cut -d ' ' -f2-)
-  REVIEW_URL="${GITHUB_URL}/${REVIEW}"
-  REVIEW_DETAILS=$(cat <<__EOF__
-This closes: #${REVIEW}
-
-__EOF__
-)
-fi
-
-MESSAGE=$(cat <<__EOF__
-${SUMMARY}
-
-${DESCRIPTION}
-
-${REVIEW_DETAILS}
-Review: ${REVIEW_URL}
-__EOF__
-)
-echo "Successfully applied: ${MESSAGE}"
-
-git commit --author="${AUTHOR}" -am "${MESSAGE}" || \
-  { echo "${RED}Failed to commit patch${NORMAL}"; exit 1; }
-
-if $AMEND; then
-  git commit --amend
-fi
+exec python support/apply-reviews.py "$@"


[2/3] mesos git commit: Added '--chain' option to apply-reviews.py.

Posted by jo...@apache.org.
Added '--chain' option to apply-reviews.py.

Review: https://reviews.apache.org/r/39420


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/68cefcba
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/68cefcba
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/68cefcba

Branch: refs/heads/master
Commit: 68cefcbae365796a65b22450297c4207cf276955
Parents: c3e7ee7
Author: Artem Harutyunyan <ar...@mesosphere.io>
Authored: Sat Dec 12 10:51:01 2015 -0800
Committer: Joris Van Remoortere <jo...@gmail.com>
Committed: Sat Dec 12 10:53:47 2015 -0800

----------------------------------------------------------------------
 support/apply-reviews.py | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/68cefcba/support/apply-reviews.py
----------------------------------------------------------------------
diff --git a/support/apply-reviews.py b/support/apply-reviews.py
index 7ef447b..b8a0b6d 100755
--- a/support/apply-reviews.py
+++ b/support/apply-reviews.py
@@ -285,6 +285,9 @@ def parse_options():
   parser.add_argument('-n', '--no-amend',
                       action='store_true',
                       help='Do not amend commit message.')
+  parser.add_argument('-c', '--chain',
+                      action='store_true',
+                      help='Recursively apply parent review chain.')
 
   # Add -g and -r and make them mutually exclusive.
   group = parser.add_mutually_exclusive_group(required=True)
@@ -301,19 +304,21 @@ def parse_options():
   options['dry_run'] = args.dry_run
   options['no_amend'] = args.no_amend
   options['github'] = args.github
+  options['chain'] = args.chain
 
 
 def reviewboard():
-  """Applies a chain of reviewboard patches."""
-  # Retrieve the list of reviews to apply.
-  reviews = review_chain(options['review_id'])
-
-  applied = set()
-  for review_id, summary in reviews:
-    if review_id not in applied:
-      applied.add(review_id)
-      options['review_id'] = review_id
-      apply_review()
+  """Applies either a chain of reviewboard patches or a single patch."""
+  if options['chain']:
+    # Retrieve the list of reviews to apply.
+    applied = set()
+    for review_id, summary in review_chain(options['review_id']):
+      if review_id not in applied:
+        applied.add(review_id)
+        options['review_id'] = review_id
+        apply_review()
+  else:
+    apply_review()
 
 
 def github():