Skip to content

Commit

Permalink
Add option to rbt patch for automatically creating a Git commit
Browse files Browse the repository at this point in the history
The option -c, --commit automatically creates a commit in the current local
branch with the summary, description, bugs, testing, and author information
fetched from the review request.

Testing done:
Tested with a new review request using the commands 'rbt patch -c
<review_request_id>' and 'rbt patch --commit <review_request_id>'.

Unit tests passed.

Reviewed at https://reviews.reviewboard.org/r/4865/
  • Loading branch information
edwlee authored and davidt committed Nov 16, 2013
1 parent e431336 commit 999a2e0
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 8 deletions.
4 changes: 4 additions & 0 deletions docs/rbtools/rbt/commands/patch.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Options

Numerical pX argument for patch.

.. cmdoption:: -c, --commit

Commit using information fetched from the review request (Git only).

.. cmdoption:: --server

Specify a different Review Board server to use.
Expand Down
13 changes: 13 additions & 0 deletions rbtools/api/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,19 @@ class DraftScreenshotListResource(ScreenshotListResource):
class ReviewRequestResource(ItemResource):
"""The Review Request resource specific base class."""

@property
def absolute_url(self):
"""Returns the absolute URL for the Review Request.
The value of absolute_url is returned if it's defined.
Otherwise the absolute URL is generated and returned.
"""
if 'absolute_url' in self.fields:
return self.fields['absolute_url']
else:
base_url = self._url.split('/api/')[0]
return urlparse.urljoin(base_url, self.url)

@request_method_decorator
def submit(self, description=None, changenum=None):
"""Submit a review request"""
Expand Down
16 changes: 16 additions & 0 deletions rbtools/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ def _execute(self, cmd):
print 'Results:\n' + res
return res

def has_pending_changes(self):
"""Checks if there are changes waiting to be committed.
Derived classes should override this method if they wish to support
checking for pending changes.
"""
raise NotImplementedError

def apply_patch(self, patch_file, base_path, base_dir, p=None):
"""
Apply the patch patch_file and return True if the patch was
Expand All @@ -139,6 +147,14 @@ def apply_patch(self, patch_file, base_path, base_dir, p=None):
cmd = ['patch', '-i', str(patch_file)]
self._execute(cmd)

def create_commmit(self, message, author):
"""Creates a commit based on the provided message and author.
Derived classes should override this method if they wish to support
committing changes to their repositories.
"""
raise NotImplementedError

def sanitize_changenum(self, changenum):
"""Return a "sanitized" change number.
Expand Down
16 changes: 16 additions & 0 deletions rbtools/clients/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from rbtools.clients.perforce import PerforceClient
from rbtools.clients.svn import SVNClient, SVNRepositoryInfo
from rbtools.utils.checks import check_install
from rbtools.utils.console import edit_text
from rbtools.utils.process import die, execute


Expand Down Expand Up @@ -503,6 +504,15 @@ def diff_between_revisions(self, revision_range, args, repository_info):
'base_commit_id': self.merge_base,
}

def has_pending_changes(self):
"""Checks if there are changes waiting to be committed.
Returns True if the working directory has been modified or if changes
have been staged in the index, otherwise returns False.
"""
status = execute(['git', 'status', '--porcelain'])
return status != ''

def apply_patch(self, patch_file, base_path=None, base_dir=None, p=None):
"""
Apply the patch patch_file and return True if the patch was
Expand All @@ -514,3 +524,9 @@ def apply_patch(self, patch_file, base_path=None, base_dir=None, p=None):
cmd = ['git', 'apply', patch_file]

self._execute(cmd)

def create_commmit(self, message, author):
modified_message = edit_text(message)
execute(['git', 'add', '--all', ':/'])
execute(['git', 'commit', '-m', modified_message,
'--author="%s <%s>"' % (author.fullname, author.email)])
62 changes: 62 additions & 0 deletions rbtools/commands/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ class Patch(Command):
author = "The Review Board Project"
args = "<review-request-id>"
option_list = [
Option("-c", "--commit",
dest="commit",
action="store_true",
default=False,
help="Commit using information fetched "
"from the review request (Git only)."),
Option("--diff-revision",
dest="diff_revision",
default=None,
Expand Down Expand Up @@ -91,6 +97,33 @@ def apply_patch(self, repository_info, tool, request_id, diff_revision,
tool.apply_patch(diff_file_path, repository_info.base_path,
base_dir, self.options.px)

def _extract_commit_message(self, review_request):
"""Returns a commit message based on the review request.
The commit message returned contains the Summary, Description, Bugs,
and Testing Done fields from the review request, if available.
"""
info = []

summary = review_request.summary
description = review_request.description

if not description.startswith(summary):
info.append(summary)

info.append(description)

if review_request.testing_done:
info.append('Testing Done:\n%s' % review_request.testing_done)

if review_request.bugs_closed:
info.append('Bugs closed: %s'
% ', '.join(review_request.bugs_closed))

info.append('Reviewed at %s' % review_request.absolute_url)

return '\n\n'.join(info)

def main(self, request_id):
"""Run the command."""
repository_info, tool = self.initialize_scm_tool(
Expand All @@ -108,5 +141,34 @@ def main(self, request_id):
if self.options.patch_stdout:
print diff_body
else:
try:
if tool.has_pending_changes():
message = 'Working directory is not clean.'

if not self.options.commit:
print 'Warning: %s' % message
else:
raise CommandError(message)
except NotImplementedError:
pass

self.apply_patch(repository_info, tool, request_id, diff_revision,
tmp_patch_file, base_dir)

if self.options.commit:
try:
review_request = api_root.get_review_request(
review_request_id=request_id)
except APIError, e:
raise CommandError('Error getting review request %s: %s'
% (request_id, e))

message = self._extract_commit_message(review_request)
author = review_request.get_submitter()

try:
tool.create_commmit(message, author)
print('Changes committed to current branch.')
except NotImplementedError:
raise CommandError('--commit is not supported with %s'
% tool.name)
9 changes: 1 addition & 8 deletions rbtools/commands/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import os
import re
import sys
from urlparse import urljoin

from rbtools.api.errors import APIError
from rbtools.commands import Command, CommandError, Option
Expand Down Expand Up @@ -438,13 +437,7 @@ def post_request(self, tool, repository_info, server_url, api_root,
raise CommandError(
"Error updating review request draft: %s" % e)

request_url = 'r/%s/' % review_request.id
review_url = urljoin(server_url, request_url)

if not review_url.startswith('http'):
review_url = 'http://%s' % review_url

return review_request.id, review_url
return review_request.id, review_request.absolute_url

def main(self, *args):
"""Create and update review requests."""
Expand Down
20 changes: 20 additions & 0 deletions rbtools/utils/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os
import subprocess

from rbtools.utils.filesystem import make_tempfile


def edit_text(content):
"""Allows a user to edit a block of text and returns the saved result.
The environment's default text editor is used if available, otherwise
vim is used.
"""
tempfile = make_tempfile(content)
editor = os.environ.get('EDITOR', 'vim')
subprocess.call([editor, tempfile])
f = open(tempfile)
result = f.read()
f.close()

return result

0 comments on commit 999a2e0

Please sign in to comment.