summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2012-12-05 21:05:33 -0800
committerPaul Phillips <paulp@improving.org>2012-12-05 21:05:33 -0800
commit78bcc844f17c99a103a7c2f05de96ae32747cf8f (patch)
tree29f3dcc1bbc28b6c57221e911ca5116e9a78fc0c
parent9547753a80fab450c842cb4a9a8296aa41875ff3 (diff)
parent924633e17a88e9f9d3d6be65036261125d5da04d (diff)
downloadscala-78bcc844f17c99a103a7c2f05de96ae32747cf8f.tar.gz
scala-78bcc844f17c99a103a7c2f05de96ae32747cf8f.tar.bz2
scala-78bcc844f17c99a103a7c2f05de96ae32747cf8f.zip
Merge pull request #1714 from JamesIry/remove_review_script
SI-6770 Removes unused and unusable review scripts
-rw-r--r--test/postreview.py2540
-rwxr-xr-xtest/review44
2 files changed, 0 insertions, 2584 deletions
diff --git a/test/postreview.py b/test/postreview.py
deleted file mode 100644
index 2e2518f7ee..0000000000
--- a/test/postreview.py
+++ /dev/null
@@ -1,2540 +0,0 @@
-#!/usr/bin/env python
-import cookielib
-import difflib
-import getpass
-import marshal
-import mimetools
-import ntpath
-import os
-import re
-import socket
-import stat
-import subprocess
-import sys
-import tempfile
-import urllib
-import urllib2
-from optparse import OptionParser
-from tempfile import mkstemp
-from urlparse import urljoin, urlparse
-
-try:
- from hashlib import md5
-except ImportError:
- # Support Python versions before 2.5.
- from md5 import md5
-
-try:
- import json
-except ImportError:
- import simplejson as json
-
-# This specific import is necessary to handle the paths for
-# cygwin enabled machines.
-if (sys.platform.startswith('win')
- or sys.platform.startswith('cygwin')):
- import ntpath as cpath
-else:
- import posixpath as cpath
-
-###
-# Default configuration -- user-settable variables follow.
-###
-
-# The following settings usually aren't needed, but if your Review
-# Board crew has specific preferences and doesn't want to express
-# them with command line switches, set them here and you're done.
-# In particular, setting the REVIEWBOARD_URL variable will allow
-# you to make it easy for people to submit reviews regardless of
-# their SCM setup.
-#
-# Note that in order for this script to work with a reviewboard site
-# that uses local paths to access a repository, the 'Mirror path'
-# in the repository setup page must be set to the remote URL of the
-# repository.
-
-#
-# Reviewboard URL.
-#
-# Set this if you wish to hard-code a default server to always use.
-# It's generally recommended to set this using your SCM repository
-# (for those that support it -- currently only SVN, Git, and Perforce).
-#
-# For example, on SVN:
-# $ svn propset reviewboard:url http://reviewboard.example.com .
-#
-# Or with Git:
-# $ git config reviewboard.url http://reviewboard.example.com
-#
-# On Perforce servers version 2008.1 and above:
-# $ p4 counter reviewboard.url http://reviewboard.example.com
-#
-# Older Perforce servers only allow numerical counters, so embedding
-# the url in the counter name is also supported:
-# $ p4 counter reviewboard.url.http:\|\|reviewboard.example.com 1
-#
-# Note that slashes are not allowed in Perforce counter names, so replace them
-# with pipe characters (they are a safe substitute as they are not used
-# unencoded in URLs). You may need to escape them when issuing the p4 counter
-# command as above.
-#
-# If this is not possible or desired, setting the value here will let
-# you get started quickly.
-#
-# For all other repositories, a .reviewboardrc file present at the top of
-# the checkout will also work. For example:
-#
-# $ cat .reviewboardrc
-# REVIEWBOARD_URL = "http://reviewboard.example.com"
-#
-REVIEWBOARD_URL = None
-
-# Default submission arguments. These are all optional; run this
-# script with --help for descriptions of each argument.
-TARGET_GROUPS = None
-TARGET_PEOPLE = None
-SUBMIT_AS = None
-PUBLISH = False
-OPEN_BROWSER = False
-
-# Debugging. For development...
-DEBUG = False
-
-###
-# End user-settable variables.
-###
-
-
-VERSION = "0.8"
-
-user_config = None
-tempfiles = []
-options = None
-
-
-class APIError(Exception):
- pass
-
-
-class RepositoryInfo:
- """
- A representation of a source code repository.
- """
- def __init__(self, path=None, base_path=None, supports_changesets=False,
- supports_parent_diffs=False):
- self.path = path
- self.base_path = base_path
- self.supports_changesets = supports_changesets
- self.supports_parent_diffs = supports_parent_diffs
- debug("repository info: %s" % self)
-
- def __str__(self):
- return "Path: %s, Base path: %s, Supports changesets: %s" % \
- (self.path, self.base_path, self.supports_changesets)
-
- def set_base_path(self, base_path):
- if not base_path.startswith('/'):
- base_path = '/' + base_path
- debug("changing repository info base_path from %s to %s" % \
- (self.base_path, base_path))
- self.base_path = base_path
-
- def find_server_repository_info(self, server):
- """
- Try to find the repository from the list of repositories on the server.
- For Subversion, this could be a repository with a different URL. For
- all other clients, this is a noop.
- """
- return self
-
-
-class SvnRepositoryInfo(RepositoryInfo):
- """
- A representation of a SVN source code repository. This version knows how to
- find a matching repository on the server even if the URLs differ.
- """
- def __init__(self, path, base_path, uuid, supports_parent_diffs=False):
- RepositoryInfo.__init__(self, path, base_path,
- supports_parent_diffs=supports_parent_diffs)
- self.uuid = uuid
-
- def find_server_repository_info(self, server):
- """
- The point of this function is to find a repository on the server that
- matches self, even if the paths aren't the same. (For example, if self
- uses an 'http' path, but the server uses a 'file' path for the same
- repository.) It does this by comparing repository UUIDs. If the
- repositories use the same path, you'll get back self, otherwise you'll
- get a different SvnRepositoryInfo object (with a different path).
- """
- repositories = server.get_repositories()
-
- for repository in repositories:
- if repository['tool'] != 'Subversion':
- continue
-
- info = self._get_repository_info(server, repository)
-
- if not info or self.uuid != info['uuid']:
- continue
-
- repos_base_path = info['url'][len(info['root_url']):]
- relpath = self._get_relative_path(self.base_path, repos_base_path)
- if relpath:
- return SvnRepositoryInfo(info['url'], relpath, self.uuid)
-
- # We didn't find a matching repository on the server. We'll just return
- # self and hope for the best.
- return self
-
- def _get_repository_info(self, server, repository):
- try:
- return server.get_repository_info(repository['id'])
- except APIError, e:
- # If the server couldn't fetch the repository info, it will return
- # code 210. Ignore those.
- # Other more serious errors should still be raised, though.
- rsp = e.args[0]
- if rsp['err']['code'] == 210:
- return None
-
- raise e
-
- def _get_relative_path(self, path, root):
- pathdirs = self._split_on_slash(path)
- rootdirs = self._split_on_slash(root)
-
- # root is empty, so anything relative to that is itself
- if len(rootdirs) == 0:
- return path
-
- # If one of the directories doesn't match, then path is not relative
- # to root.
- if rootdirs != pathdirs:
- return None
-
- # All the directories matched, so the relative path is whatever
- # directories are left over. The base_path can't be empty, though, so
- # if the paths are the same, return '/'
- if len(pathdirs) == len(rootdirs):
- return '/'
- else:
- return '/'.join(pathdirs[len(rootdirs):])
-
- def _split_on_slash(self, path):
- # Split on slashes, but ignore multiple slashes and throw away any
- # trailing slashes.
- split = re.split('/*', path)
- if split[-1] == '':
- split = split[0:-1]
- return split
-
-
-class ReviewBoardHTTPPasswordMgr(urllib2.HTTPPasswordMgr):
- """
- Adds HTTP authentication support for URLs.
-
- Python 2.4's password manager has a bug in http authentication when the
- target server uses a non-standard port. This works around that bug on
- Python 2.4 installs. This also allows post-review to prompt for passwords
- in a consistent way.
-
- See: http://bugs.python.org/issue974757
- """
- def __init__(self, reviewboard_url):
- self.passwd = {}
- self.rb_url = reviewboard_url
- self.rb_user = None
- self.rb_pass = None
-
- def find_user_password(self, realm, uri):
- if uri.startswith(self.rb_url):
- if self.rb_user is None or self.rb_pass is None:
- print "==> HTTP Authentication Required"
- print 'Enter username and password for "%s" at %s' % \
- (realm, urlparse(uri)[1])
- self.rb_user = raw_input('Username: ')
- self.rb_pass = getpass.getpass('Password: ')
-
- return self.rb_user, self.rb_pass
- else:
- # If this is an auth request for some other domain (since HTTP
- # handlers are global), fall back to standard password management.
- return urllib2.HTTPPasswordMgr.find_user_password(self, realm, uri)
-
-
-class ReviewBoardServer(object):
- """
- An instance of a Review Board server.
- """
- def __init__(self, url, info, cookie_file):
- self.url = url
- if self.url[-1] != '/':
- self.url += '/'
- self._info = info
- self._server_info = None
- self.cookie_file = cookie_file
- self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
-
- # Set up the HTTP libraries to support all of the features we need.
- cookie_handler = urllib2.HTTPCookieProcessor(self.cookie_jar)
- password_mgr = ReviewBoardHTTPPasswordMgr(self.url)
- auth_handler = urllib2.HTTPBasicAuthHandler(password_mgr)
-
- opener = urllib2.build_opener(cookie_handler, auth_handler)
- opener.addheaders = [('User-agent', 'post-review/' + VERSION)]
- urllib2.install_opener(opener)
-
- def login(self, force=False):
- """
- Logs in to a Review Board server, prompting the user for login
- information if needed.
- """
- if not force and self.has_valid_cookie():
- return
-
- print "==> Review Board Login Required"
- print "Enter username and password for Review Board at %s" % self.url
- if options.username:
- username = options.username
- elif options.submit_as:
- username = options.submit_as
- else:
- username = raw_input('Username: ')
-
- if not options.password:
- password = getpass.getpass('Password: ')
- else:
- password = options.password
-
- debug('Logging in with username "%s"' % username)
- try:
- self.api_post('api/json/accounts/login/', {
- 'username': username,
- 'password': password,
- })
- except APIError, e:
- rsp, = e.args
-
- die("Unable to log in: %s (%s)" % (rsp["err"]["msg"],
- rsp["err"]["code"]))
-
- debug("Logged in.")
-
- def has_valid_cookie(self):
- """
- Load the user's cookie file and see if they have a valid
- 'rbsessionid' cookie for the current Review Board server. Returns
- true if so and false otherwise.
- """
- try:
- parsed_url = urlparse(self.url)
- host = parsed_url[1]
- path = parsed_url[2] or '/'
-
- # Cookie files don't store port numbers, unfortunately, so
- # get rid of the port number if it's present.
- host = host.split(":")[0]
-
- debug("Looking for '%s %s' cookie in %s" % \
- (host, path, self.cookie_file))
- self.cookie_jar.load(self.cookie_file, ignore_expires=True)
-
- try:
- cookie = self.cookie_jar._cookies[host][path]['rbsessionid']
-
- if not cookie.is_expired():
- debug("Loaded valid cookie -- no login required")
- return True
-
- debug("Cookie file loaded, but cookie has expired")
- except KeyError:
- debug("Cookie file loaded, but no cookie for this server")
- except IOError, error:
- debug("Couldn't load cookie file: %s" % error)
-
- return False
-
- def new_review_request(self, changenum, submit_as=None):
- """
- Creates a review request on a Review Board server, updating an
- existing one if the changeset number already exists.
-
- If submit_as is provided, the specified user name will be recorded as
- the submitter of the review request (given that the logged in user has
- the appropriate permissions).
- """
- try:
- debug("Attempting to create review request for %s" % changenum)
- data = { 'repository_path': self.info.path }
-
- if changenum:
- data['changenum'] = changenum
-
- if submit_as:
- debug("Submitting the review request as %s" % submit_as)
- data['submit_as'] = submit_as
-
- rsp = self.api_post('api/json/reviewrequests/new/', data)
- except APIError, e:
- rsp, = e.args
-
- if not options.diff_only:
- if rsp['err']['code'] == 204: # Change number in use
- debug("Review request already exists. Updating it...")
- rsp = self.api_post(
- 'api/json/reviewrequests/%s/update_from_changenum/' %
- rsp['review_request']['id'])
- else:
- raise e
-
- debug("Review request created")
- return rsp['review_request']
-
- def set_review_request_field(self, review_request, field, value):
- """
- Sets a field in a review request to the specified value.
- """
- rid = review_request['id']
-
- debug("Attempting to set field '%s' to '%s' for review request '%s'" %
- (field, value, rid))
-
- self.api_post('api/json/reviewrequests/%s/draft/set/' % rid, {
- field: value,
- })
-
- def get_review_request(self, rid):
- """
- Returns the review request with the specified ID.
- """
- rsp = self.api_get('api/json/reviewrequests/%s/' % rid)
- return rsp['review_request']
-
- def get_repositories(self):
- """
- Returns the list of repositories on this server.
- """
- rsp = self.api_get('/api/json/repositories/')
- return rsp['repositories']
-
- def get_repository_info(self, rid):
- """
- Returns detailed information about a specific repository.
- """
- rsp = self.api_get('/api/json/repositories/%s/info/' % rid)
- return rsp['info']
-
- def save_draft(self, review_request):
- """
- Saves a draft of a review request.
- """
- self.api_post("api/json/reviewrequests/%s/draft/save/" %
- review_request['id'])
- debug("Review request draft saved")
-
- def upload_diff(self, review_request, diff_content, parent_diff_content):
- """
- Uploads a diff to a Review Board server.
- """
- debug("Uploading diff, size: %d" % len(diff_content))
-
- if parent_diff_content:
- debug("Uploading parent diff, size: %d" % len(parent_diff_content))
-
- fields = {}
- files = {}
-
- if self.info.base_path:
- fields['basedir'] = self.info.base_path
-
- files['path'] = {
- 'filename': 'diff',
- 'content': diff_content
- }
-
- if parent_diff_content:
- files['parent_diff_path'] = {
- 'filename': 'parent_diff',
- 'content': parent_diff_content
- }
-
- self.api_post('api/json/reviewrequests/%s/diff/new/' %
- review_request['id'], fields, files)
-
- def publish(self, review_request):
- """
- Publishes a review request.
- """
- debug("Publishing")
- self.api_post('api/json/reviewrequests/%s/publish/' %
- review_request['id'])
-
- def _get_server_info(self):
- if not self._server_info:
- self._server_info = self._info.find_server_repository_info(self)
-
- return self._server_info
-
- info = property(_get_server_info)
-
- def process_json(self, data):
- """
- Loads in a JSON file and returns the data if successful. On failure,
- APIError is raised.
- """
- rsp = json.loads(data)
-
- if rsp['stat'] == 'fail':
- raise APIError, rsp
-
- return rsp
-
- def http_get(self, path):
- """
- Performs an HTTP GET on the specified path, storing any cookies that
- were set.
- """
- debug('HTTP GETting %s' % path)
-
- url = self._make_url(path)
-
- try:
- rsp = urllib2.urlopen(url).read()
- self.cookie_jar.save(self.cookie_file)
- return rsp
- except urllib2.HTTPError, e:
- print "Unable to access %s (%s). The host path may be invalid" % \
- (url, e.code)
- try:
- debug(e.read())
- except AttributeError:
- pass
- die()
-
- def _make_url(self, path):
- """Given a path on the server returns a full http:// style url"""
- app = urlparse(self.url)[2]
- if path[0] == '/':
- url = urljoin(self.url, app[:-1] + path)
- else:
- url = urljoin(self.url, app + path)
-
- if not url.startswith('http'):
- url = 'http://%s' % url
- return url
-
- def api_get(self, path):
- """
- Performs an API call using HTTP GET at the specified path.
- """
- return self.process_json(self.http_get(path))
-
- def http_post(self, path, fields, files=None):
- """
- Performs an HTTP POST on the specified path, storing any cookies that
- were set.
- """
- if fields:
- debug_fields = fields.copy()
- else:
- debug_fields = {}
-
- if 'password' in debug_fields:
- debug_fields["password"] = "**************"
- url = self._make_url(path)
- debug('HTTP POSTing to %s: %s' % (url, debug_fields))
-
- content_type, body = self._encode_multipart_formdata(fields, files)
- headers = {
- 'Content-Type': content_type,
- 'Content-Length': str(len(body))
- }
-
- try:
- r = urllib2.Request(url, body, headers)
- data = urllib2.urlopen(r).read()
- self.cookie_jar.save(self.cookie_file)
- return data
- except urllib2.URLError, e:
- try:
- debug(e.read())
- except AttributeError:
- pass
-
- die("Unable to access %s. The host path may be invalid\n%s" % \
- (url, e))
- except urllib2.HTTPError, e:
- die("Unable to access %s (%s). The host path may be invalid\n%s" % \
- (url, e.code, e.read()))
-
- def api_post(self, path, fields=None, files=None):
- """
- Performs an API call using HTTP POST at the specified path.
- """
- return self.process_json(self.http_post(path, fields, files))
-
- def _encode_multipart_formdata(self, fields, files):
- """
- Encodes data for use in an HTTP POST.
- """
- BOUNDARY = mimetools.choose_boundary()
- content = ""
-
- fields = fields or {}
- files = files or {}
-
- for key in fields:
- content += "--" + BOUNDARY + "\r\n"
- content += "Content-Disposition: form-data; name=\"%s\"\r\n" % key
- content += "\r\n"
- content += fields[key] + "\r\n"
-
- for key in files:
- filename = files[key]['filename']
- value = files[key]['content']
- content += "--" + BOUNDARY + "\r\n"
- content += "Content-Disposition: form-data; name=\"%s\"; " % key
- content += "filename=\"%s\"\r\n" % filename
- content += "\r\n"
- content += value + "\r\n"
-
- content += "--" + BOUNDARY + "--\r\n"
- content += "\r\n"
-
- content_type = "multipart/form-data; boundary=%s" % BOUNDARY
-
- return content_type, content
-
-
-class SCMClient(object):
- """
- A base representation of an SCM tool for fetching repository information
- and generating diffs.
- """
- def get_repository_info(self):
- return None
-
- def scan_for_server(self, repository_info):
- """
- Scans the current directory on up to find a .reviewboard file
- containing the server path.
- """
- server_url = self._get_server_from_config(user_config, repository_info)
- if server_url:
- return server_url
-
- for path in walk_parents(os.getcwd()):
- filename = os.path.join(path, ".reviewboardrc")
- if os.path.exists(filename):
- config = load_config_file(filename)
- server_url = self._get_server_from_config(config,
- repository_info)
- if server_url:
- return server_url
-
- return None
-
- def diff(self, args):
- """
- Returns the generated diff and optional parent diff for this
- repository.
-
- The returned tuple is (diff_string, parent_diff_string)
- """
- return (None, None)
-
- def diff_between_revisions(self, revision_range, args, repository_info):
- """
- Returns the generated diff between revisions in the repository.
- """
- return None
-
- def _get_server_from_config(self, config, repository_info):
- if 'REVIEWBOARD_URL' in config:
- return config['REVIEWBOARD_URL']
- elif 'TREES' in config:
- trees = config['TREES']
- if not isinstance(trees, dict):
- die("Warning: 'TREES' in config file is not a dict!")
-
- if repository_info.path in trees and \
- 'REVIEWBOARD_URL' in trees[repository_info.path]:
- return trees[repository_info.path]['REVIEWBOARD_URL']
-
- return None
-
-
-class CVSClient(SCMClient):
- """
- A wrapper around the cvs tool that fetches repository
- information and generates compatible diffs.
- """
- def get_repository_info(self):
- if not check_install("cvs"):
- return None
-
- cvsroot_path = os.path.join("CVS", "Root")
-
- if not os.path.exists(cvsroot_path):
- return None
-
- fp = open(cvsroot_path, "r")
- repository_path = fp.read().strip()
- fp.close()
-
- i = repository_path.find("@")
- if i != -1:
- repository_path = repository_path[i + 1:]
-
- i = repository_path.find(":")
- if i != -1:
- host = repository_path[:i]
- try:
- canon = socket.getfqdn(host)
- repository_path = repository_path.replace('%s:' % host,
- '%s:' % canon)
- except socket.error, msg:
- debug("failed to get fqdn for %s, msg=%s" % (host, msg))
-
- return RepositoryInfo(path=repository_path)
-
- def diff(self, files):
- """
- Performs a diff across all modified files in a CVS repository.
-
- CVS repositories do not support branches of branches in a way that
- makes parent diffs possible, so we never return a parent diff
- (the second value in the tuple).
- """
- return (self.do_diff(files), None)
-
- def diff_between_revisions(self, revision_range, args, repository_info):
- """
- Performs a diff between 2 revisions of a CVS repository.
- """
- revs = []
-
- for rev in revision_range.split(":"):
- revs += ["-r", rev]
-
- return self.do_diff(revs)
-
- def do_diff(self, params):
- """
- Performs the actual diff operation through cvs diff, handling
- fake errors generated by CVS.
- """
- # Diff returns "1" if differences were found.
- return execute(["cvs", "diff", "-uN"] + params,
- extra_ignore_errors=(1,))
-
-
-class ClearCaseClient(SCMClient):
- """
- A wrapper around the clearcase tool that fetches repository
- information and generates compatible diffs.
- This client assumes that cygwin is installed on windows.
- """
- ccroot_path = "/view/reviewboard.diffview/vobs/"
- viewinfo = ""
- viewtype = "snapshot"
-
- def get_filename_hash(self, fname):
- # Hash the filename string so its easy to find the file later on.
- return md5(fname).hexdigest()
-
- def get_repository_info(self):
- if not check_install('cleartool help'):
- return None
-
- # We must be running this from inside a view.
- # Otherwise it doesn't make sense.
- self.viewinfo = execute(["cleartool", "pwv", "-short"])
- if self.viewinfo.startswith('\*\* NONE'):
- return None
-
- # Returning the hardcoded clearcase root path to match the server
- # respository path.
- # There is no reason to have a dynamic path unless you have
- # multiple clearcase repositories. This should be implemented.
- return RepositoryInfo(path=self.ccroot_path,
- base_path=self.ccroot_path,
- supports_parent_diffs=False)
-
- def get_previous_version(self, files):
- file = []
- curdir = os.getcwd()
-
- # Cygwin case must transform a linux-like path to windows like path
- # including drive letter.
- if 'cygdrive' in curdir:
- where = curdir.index('cygdrive') + 9
- drive_letter = curdir[where:where+1]
- curdir = drive_letter + ":\\" + curdir[where+2:len(curdir)]
-
- for key in files:
- # Sometimes there is a quote in the filename. It must be removed.
- key = key.replace('\'', '')
- elem_path = cpath.normpath(os.path.join(curdir, key))
-
- # Removing anything before the last /vobs
- # because it may be repeated.
- elem_path_idx = elem_path.rfind("/vobs")
- if elem_path_idx != -1:
- elem_path = elem_path[elem_path_idx:len(elem_path)].strip("\"")
-
- # Call cleartool to get this version and the previous version
- # of the element.
- curr_version, pre_version = execute(
- ["cleartool", "desc", "-pre", elem_path])
- curr_version = cpath.normpath(curr_version)
- pre_version = pre_version.split(':')[1].strip()
-
- # If a specific version was given, remove it from the path
- # to avoid version duplication
- if "@@" in elem_path:
- elem_path = elem_path[:elem_path.rfind("@@")]
- file.append(elem_path + "@@" + pre_version)
- file.append(curr_version)
-
- # Determnine if the view type is snapshot or dynamic.
- if os.path.exists(file[0]):
- self.viewtype = "dynamic"
-
- return file
-
- def get_extended_namespace(self, files):
- """
- Parses the file path to get the extended namespace
- """
- versions = self.get_previous_version(files)
-
- evfiles = []
- hlist = []
-
- for vkey in versions:
- # Verify if it is a checkedout file.
- if "CHECKEDOUT" in vkey:
- # For checkedout files just add it to the file list
- # since it cannot be accessed outside the view.
- splversions = vkey[:vkey.rfind("@@")]
- evfiles.append(splversions)
- else:
- # For checkedin files.
- ext_path = []
- ver = []
- fname = "" # fname holds the file name without the version.
- (bpath, fpath) = cpath.splitdrive(vkey)
- if bpath :
- # Windows.
- # The version (if specified like file.c@@/main/1)
- # should be kept as a single string
- # so split the path and concat the file name
- # and version in the last position of the list.
- ver = fpath.split("@@")
- splversions = fpath[:vkey.rfind("@@")].split("\\")
- fname = splversions.pop()
- splversions.append(fname + ver[1])
- else :
- # Linux.
- bpath = vkey[:vkey.rfind("vobs")+4]
- fpath = vkey[vkey.rfind("vobs")+5:]
- ver = fpath.split("@@")
- splversions = ver[0][:vkey.rfind("@@")].split("/")
- fname = splversions.pop()
- splversions.append(fname + ver[1])
-
- filename = splversions.pop()
- bpath = cpath.normpath(bpath + "/")
- elem_path = bpath
-
- for key in splversions:
- # For each element (directory) in the path,
- # get its version from clearcase.
- elem_path = cpath.join(elem_path, key)
-
- # This is the version to be appended to the extended
- # path list.
- this_version = execute(
- ["cleartool", "desc", "-fmt", "%Vn",
- cpath.normpath(elem_path)])
- if this_version:
- ext_path.append(key + "/@@" + this_version + "/")
- else:
- ext_path.append(key + "/")
-
- # This must be done in case we haven't specified
- # the version on the command line.
- ext_path.append(cpath.normpath(fname + "/@@" +
- vkey[vkey.rfind("@@")+2:len(vkey)]))
- epstr = cpath.join(bpath, cpath.normpath(''.join(ext_path)))
- evfiles.append(epstr)
-
- """
- In windows, there is a problem with long names(> 254).
- In this case, we hash the string and copy the unextended
- filename to a temp file whose name is the hash.
- This way we can get the file later on for diff.
- The same problem applies to snapshot views where the
- extended name isn't available.
- The previous file must be copied from the CC server
- to a local dir.
- """
- if cpath.exists(epstr) :
- pass
- else:
- if len(epstr) > 254 or self.viewtype == "snapshot":
- name = self.get_filename_hash(epstr)
- # Check if this hash is already in the list
- try:
- i = hlist.index(name)
- die("ERROR: duplicate value %s : %s" %
- (name, epstr))
- except ValueError:
- hlist.append(name)
-
- normkey = cpath.normpath(vkey)
- td = tempfile.gettempdir()
- # Cygwin case must transform a linux-like path to
- # windows like path including drive letter
- if 'cygdrive' in td:
- where = td.index('cygdrive') + 9
- drive_letter = td[where:where+1] + ":"
- td = cpath.join(drive_letter, td[where+1:])
- tf = cpath.normpath(cpath.join(td, name))
- if cpath.exists(tf):
- debug("WARNING: FILE EXISTS")
- os.unlink(tf)
- execute(["cleartool", "get", "-to", tf, normkey])
- else:
- die("ERROR: FILE NOT FOUND : %s" % epstr)
-
- return evfiles
-
- def get_files_from_label(self, label):
- voblist=[]
- # Get the list of vobs for the current view
- allvoblist = execute(["cleartool", "lsvob", "-short"]).split()
- # For each vob, find if the label is present
- for vob in allvoblist:
- try:
- execute(["cleartool", "describe", "-local",
- "lbtype:%s@%s" % (label, vob)]).split()
- voblist.append(vob)
- except:
- pass
-
- filelist=[]
- # For each vob containing the label, get the file list
- for vob in voblist:
- try:
- res = execute(["cleartool", "find", vob, "-all", "-version",
- "lbtype(%s)" % label, "-print"])
- filelist.extend(res.split())
- except :
- pass
-
- # Return only the unique itens
- return set(filelist)
-
- def diff(self, files):
- """
- Performs a diff of the specified file and its previous version.
- """
- # We must be running this from inside a view.
- # Otherwise it doesn't make sense.
- return self.do_diff(self.get_extended_namespace(files))
-
- def diff_label(self, label):
- """
- Get the files that are attached to a label and diff them
- TODO
- """
- return self.diff(self.get_files_from_label(label))
-
- def diff_between_revisions(self, revision_range, args, repository_info):
- """
- Performs a diff between 2 revisions of a CC repository.
- """
- rev_str = ''
-
- for rev in revision_range.split(":"):
- rev_str += "-r %s " % rev
-
- return self.do_diff(rev_str)
-
- def do_diff(self, params):
- # Diff returns "1" if differences were found.
- # Add the view name and view type to the description
- if options.description:
- options.description = ("VIEW: " + self.viewinfo +
- "VIEWTYPE: " + self.viewtype + "\n" + options.description)
- else:
- options.description = (self.viewinfo +
- "VIEWTYPE: " + self.viewtype + "\n")
-
- o = []
- Feol = False
- while len(params) > 0:
- # Read both original and modified files.
- onam = params.pop(0)
- mnam = params.pop(0)
- file_data = []
- do_rem = False
- # If the filename length is greater than 254 char for windows,
- # we copied the file to a temp file
- # because the open will not work for path greater than 254.
- # This is valid for the original and
- # modified files if the name size is > 254.
- for filenam in (onam, mnam) :
- if cpath.exists(filenam) and self.viewtype == "dynamic":
- do_rem = False
- fn = filenam
- elif len(filenam) > 254 or self.viewtype == "snapshot":
- fn = self.get_filename_hash(filenam)
- fn = cpath.join(tempfile.gettempdir(), fn)
- do_rem = True
- fd = open(cpath.normpath(fn))
- fdata = fd.readlines()
- fd.close()
- file_data.append(fdata)
- # If the file was temp, it should be removed.
- if do_rem:
- os.remove(filenam)
-
- modi = file_data.pop()
- orig = file_data.pop()
-
- # For snapshot views, the local directories must be removed because
- # they will break the diff on the server. Just replacing
- # everything before the view name (including the view name) for
- # vobs do the work.
- if (self.viewtype == "snapshot"
- and (sys.platform.startswith('win')
- or sys.platform.startswith('cygwin'))):
- vinfo = self.viewinfo.rstrip("\r\n")
- mnam = "c:\\\\vobs" + mnam[mnam.rfind(vinfo) + len(vinfo):]
- onam = "c:\\\\vobs" + onam[onam.rfind(vinfo) + len(vinfo):]
- # Call the diff lib to generate a diff.
- # The dates are bogus, since they don't natter anyway.
- # The only thing is that two spaces are needed to the server
- # so it can identify the heades correctly.
- diff = difflib.unified_diff(orig, modi, onam, mnam,
- ' 2002-02-21 23:30:39.942229878 -0800',
- ' 2002-02-21 23:30:50.442260588 -0800', lineterm=' \n')
- # Transform the generator output into a string output
- # Use a comprehension instead of a generator,
- # so 2.3.x doesn't fail to interpret.
- diffstr = ''.join([str(l) for l in diff])
- # Workaround for the difflib no new line at end of file
- # problem.
- if not diffstr.endswith('\n'):
- diffstr = diffstr + ("\n\\ No newline at end of file\n")
- o.append(diffstr)
-
- ostr = ''.join(o)
- return (ostr, None) # diff, parent_diff (not supported)
-
-
-class SVNClient(SCMClient):
- """
- A wrapper around the svn Subversion tool that fetches repository
- information and generates compatible diffs.
- """
- def get_repository_info(self):
- if not check_install('svn help'):
- return None
-
- # Get the SVN repository path (either via a working copy or
- # a supplied URI)
- svn_info_params = ["svn", "info"]
- if options.repository_url:
- svn_info_params.append(options.repository_url)
- data = execute(svn_info_params,
- ignore_errors=True)
- m = re.search(r'^Repository Root: (.+)$', data, re.M)
- if not m:
- return None
-
- path = m.group(1)
-
- m = re.search(r'^URL: (.+)$', data, re.M)
- if not m:
- return None
-
- base_path = m.group(1)[len(path):] or "/"
-
- m = re.search(r'^Repository UUID: (.+)$', data, re.M)
- if not m:
- return None
-
- return SvnRepositoryInfo(path, base_path, m.group(1))
-
- def scan_for_server(self, repository_info):
- # Scan first for dot files, since it's faster and will cover the
- # user's $HOME/.reviewboardrc
- server_url = super(SVNClient, self).scan_for_server(repository_info)
- if server_url:
- return server_url
-
- return self.scan_for_server_property(repository_info)
-
- def scan_for_server_property(self, repository_info):
- def get_url_prop(path):
- url = execute(["svn", "propget", "reviewboard:url", path]).strip()
- return url or None
-
- for path in walk_parents(os.getcwd()):
- if not os.path.exists(os.path.join(path, ".svn")):
- break
-
- prop = get_url_prop(path)
- if prop:
- return prop
-
- return get_url_prop(repository_info.path)
-
- def diff(self, files):
- """
- Performs a diff across all modified files in a Subversion repository.
-
- SVN repositories do not support branches of branches in a way that
- makes parent diffs possible, so we never return a parent diff
- (the second value in the tuple).
- """
- return (self.do_diff(["svn", "diff", "--diff-cmd=diff"] + files),
- None)
-
- def diff_between_revisions(self, revision_range, args, repository_info):
- """
- Performs a diff between 2 revisions of a Subversion repository.
- """
- if options.repository_url:
- revisions = revision_range.split(':')
- if len(revisions) < 1:
- return None
- elif len(revisions) == 1:
- revisions.append('HEAD')
-
- # if a new path was supplied at the command line, set it
- if len(args):
- repository_info.set_base_path(args[0])
-
- url = repository_info.path + repository_info.base_path
-
- old_url = url + '@' + revisions[0]
- new_url = url + '@' + revisions[1]
-
- return self.do_diff(["svn", "diff", "--diff-cmd=diff", old_url,
- new_url],
- repository_info)
- # Otherwise, perform the revision range diff using a working copy
- else:
- return self.do_diff(["svn", "diff", "--diff-cmd=diff", "-r",
- revision_range],
- repository_info)
-
- def do_diff(self, cmd, repository_info=None):
- """
- Performs the actual diff operation, handling renames and converting
- paths to absolute.
- """
- diff = execute(cmd, split_lines=True)
- diff = self.handle_renames(diff)
- diff = self.convert_to_absolute_paths(diff, repository_info)
-
- return ''.join(diff)
-
- def handle_renames(self, diff_content):
- """
- The output of svn diff is incorrect when the file in question came
- into being via svn mv/cp. Although the patch for these files are
- relative to its parent, the diff header doesn't reflect this.
- This function fixes the relevant section headers of the patch to
- portray this relationship.
- """
-
- # svn diff against a repository URL on two revisions appears to
- # handle moved files properly, so only adjust the diff file names
- # if they were created using a working copy.
- if options.repository_url:
- return diff_content
-
- result = []
-
- from_line = ""
- for line in diff_content:
- if line.startswith('--- '):
- from_line = line
- continue
-
- # This is where we decide how mangle the previous '--- '
- if line.startswith('+++ '):
- to_file, _ = self.parse_filename_header(line[4:])
- info = self.svn_info(to_file)
- if info.has_key("Copied From URL"):
- url = info["Copied From URL"]
- root = info["Repository Root"]
- from_file = urllib.unquote(url[len(root):])
- result.append(from_line.replace(to_file, from_file))
- else:
- result.append(from_line) #as is, no copy performed
-
- # We only mangle '---' lines. All others get added straight to
- # the output.
- result.append(line)
-
- return result
-
-
- def convert_to_absolute_paths(self, diff_content, repository_info):
- """
- Converts relative paths in a diff output to absolute paths.
- This handles paths that have been svn switched to other parts of the
- repository.
- """
-
- result = []
-
- for line in diff_content:
- front = None
- if line.startswith('+++ ') or line.startswith('--- ') or line.startswith('Index: '):
- front, line = line.split(" ", 1)
-
- if front:
- if line.startswith('/'): #already absolute
- line = front + " " + line
- else:
- # filename and rest of line (usually the revision
- # component)
- file, rest = self.parse_filename_header(line)
-
- # If working with a diff generated outside of a working
- # copy, then file paths are already absolute, so just
- # add initial slash.
- if options.repository_url:
- path = urllib.unquote(
- "%s/%s" % (repository_info.base_path, file))
- else:
- info = self.svn_info(file)
- url = info["URL"]
- root = info["Repository Root"]
- path = urllib.unquote(url[len(root):])
-
- line = front + " " + path + rest
-
- result.append(line)
-
- return result
-
- def svn_info(self, path):
- """Return a dict which is the result of 'svn info' at a given path."""
- svninfo = {}
- for info in execute(["svn", "info", path],
- split_lines=True):
- parts = info.strip().split(": ", 1)
- if len(parts) == 2:
- key, value = parts
- svninfo[key] = value
-
- return svninfo
-
- # Adapted from server code parser.py
- def parse_filename_header(self, s):
- parts = None
- if "\t" in s:
- # There's a \t separating the filename and info. This is the
- # best case scenario, since it allows for filenames with spaces
- # without much work.
- parts = s.split("\t")
-
- # There's spaces being used to separate the filename and info.
- # This is technically wrong, so all we can do is assume that
- # 1) the filename won't have multiple consecutive spaces, and
- # 2) there's at least 2 spaces separating the filename and info.
- if " " in s:
- parts = re.split(r" +", s)
-
- if parts:
- parts[1] = '\t' + parts[1]
- return parts
-
- # strip off ending newline, and return it as the second component
- return [s.split('\n')[0], '\n']
-
-
-class PerforceClient(SCMClient):
- """
- A wrapper around the p4 Perforce tool that fetches repository information
- and generates compatible diffs.
- """
- def get_repository_info(self):
- if not check_install('p4 help'):
- return None
-
- data = execute(["p4", "info"], ignore_errors=True)
-
- m = re.search(r'^Server address: (.+)$', data, re.M)
- if not m:
- return None
-
- repository_path = m.group(1).strip()
-
- try:
- hostname, port = repository_path.split(":")
- info = socket.gethostbyaddr(hostname)
- repository_path = "%s:%s" % (info[0], port)
- except (socket.gaierror, socket.herror):
- pass
-
- return RepositoryInfo(path=repository_path, supports_changesets=True)
-
- def scan_for_server(self, repository_info):
- # Scan first for dot files, since it's faster and will cover the
- # user's $HOME/.reviewboardrc
- server_url = \
- super(PerforceClient, self).scan_for_server(repository_info)
-
- if server_url:
- return server_url
-
- return self.scan_for_server_counter(repository_info)
-
- def scan_for_server_counter(self, repository_info):
- """
- Checks the Perforce counters to see if the Review Board server's url
- is specified. Since Perforce only started supporting non-numeric
- counter values in server version 2008.1, we support both a normal
- counter 'reviewboard.url' with a string value and embedding the url in
- a counter name like 'reviewboard.url.http:||reviewboard.example.com'.
- Note that forward slashes aren't allowed in counter names, so
- pipe ('|') characters should be used. These should be safe because they
- should not be used unencoded in urls.
- """
-
- counters_text = execute(["p4", "counters"])
-
- # Try for a "reviewboard.url" counter first.
- m = re.search(r'^reviewboard.url = (\S+)', counters_text, re.M)
-
- if m:
- return m.group(1)
-
- # Next try for a counter of the form:
- # reviewboard_url.http:||reviewboard.example.com
- m2 = re.search(r'^reviewboard.url\.(\S+)', counters_text, re.M)
-
- if m2:
- return m2.group(1).replace('|', '/')
-
- return None
-
- def get_changenum(self, args):
- if len(args) == 1:
- try:
- return str(int(args[0]))
- except ValueError:
- pass
- return None
-
- def diff(self, args):
- """
- Goes through the hard work of generating a diff on Perforce in order
- to take into account adds/deletes and to provide the necessary
- revision information.
- """
- # set the P4 enviroment:
- if options.p4_client:
- os.environ['P4CLIENT'] = options.p4_client
-
- if options.p4_port:
- os.environ['P4PORT'] = options.p4_port
-
- changenum = self.get_changenum(args)
- if changenum is None:
- return self._path_diff(args)
- else:
- return self._changenum_diff(changenum)
-
-
- def _path_diff(self, args):
- """
- Process a path-style diff. See _changenum_diff for the alternate
- version that handles specific change numbers.
-
- Multiple paths may be specified in `args`. The path styles supported
- are:
-
- //path/to/file
- Upload file as a "new" file.
-
- //path/to/dir/...
- Upload all files as "new" files.
-
- //path/to/file[@#]rev
- Upload file from that rev as a "new" file.
-
- //path/to/file[@#]rev,[@#]rev
- Upload a diff between revs.
-
- //path/to/dir/...[@#]rev,[@#]rev
- Upload a diff of all files between revs in that directory.
- """
- r_revision_range = re.compile(r'^(?P<path>//[^@#]+)' +
- r'(?P<revision1>[#@][^,]+)?' +
- r'(?P<revision2>,[#@][^,]+)?$')
-
- empty_filename = make_tempfile()
- tmp_diff_from_filename = make_tempfile()
- tmp_diff_to_filename = make_tempfile()
-
- diff_lines = []
-
- for path in args:
- m = r_revision_range.match(path)
-
- if not m:
- die('Path %r does not match a valid Perforce path.' % (path,))
- revision1 = m.group('revision1')
- revision2 = m.group('revision2')
- first_rev_path = m.group('path')
-
- if revision1:
- first_rev_path += revision1
- records = self._run_p4(['files', first_rev_path])
-
- # Make a map for convenience.
- files = {}
-
- # Records are:
- # 'rev': '1'
- # 'func': '...'
- # 'time': '1214418871'
- # 'action': 'edit'
- # 'type': 'ktext'
- # 'depotFile': '...'
- # 'change': '123456'
- for record in records:
- if record['action'] != 'delete':
- if revision2:
- files[record['depotFile']] = [record, None]
- else:
- files[record['depotFile']] = [None, record]
-
- if revision2:
- # [1:] to skip the comma.
- second_rev_path = m.group('path') + revision2[1:]
- records = self._run_p4(['files', second_rev_path])
- for record in records:
- if record['action'] != 'delete':
- try:
- m = files[record['depotFile']]
- m[1] = record
- except KeyError:
- files[record['depotFile']] = [None, record]
-
- old_file = new_file = empty_filename
- changetype_short = None
-
- for depot_path, (first_record, second_record) in files.items():
- old_file = new_file = empty_filename
- if first_record is None:
- self._write_file(depot_path + '#' + second_record['rev'],
- tmp_diff_to_filename)
- new_file = tmp_diff_to_filename
- changetype_short = 'A'
- base_revision = 0
- elif second_record is None:
- self._write_file(depot_path + '#' + first_record['rev'],
- tmp_diff_from_filename)
- old_file = tmp_diff_from_filename
- changetype_short = 'D'
- base_revision = int(first_record['rev'])
- else:
- self._write_file(depot_path + '#' + first_record['rev'],
- tmp_diff_from_filename)
- self._write_file(depot_path + '#' + second_record['rev'],
- tmp_diff_to_filename)
- new_file = tmp_diff_to_filename
- old_file = tmp_diff_from_filename
- changetype_short = 'M'
- base_revision = int(first_record['rev'])
-
- dl = self._do_diff(old_file, new_file, depot_path,
- base_revision, changetype_short,
- ignore_unmodified=True)
- diff_lines += dl
-
- os.unlink(empty_filename)
- os.unlink(tmp_diff_from_filename)
- os.unlink(tmp_diff_to_filename)
- return (''.join(diff_lines), None)
-
- def _run_p4(self, command):
- """Execute a perforce command using the python marshal API.
-
- - command: A list of strings of the command to execute.
-
- The return type depends on the command being run.
- """
- command = ['p4', '-G'] + command
- p = subprocess.Popen(command, stdout=subprocess.PIPE)
- result = []
- has_error = False
-
- while 1:
- try:
- data = marshal.load(p.stdout)
- except EOFError:
- break
- else:
- result.append(data)
- if data.get('code', None) == 'error':
- has_error = True
-
- rc = p.wait()
-
- if rc or has_error:
- for record in result:
- if 'data' in record:
- print record['data']
- die('Failed to execute command: %s\n' % (command,))
-
- return result
-
- def _changenum_diff(self, changenum):
- """
- Process a diff for a particular change number. This handles both
- pending and submitted changelists.
-
- See _path_diff for the alternate version that does diffs of depot
- paths.
- """
- # TODO: It might be a good idea to enhance PerforceDiffParser to
- # understand that newFile could include a revision tag for post-submit
- # reviewing.
- cl_is_pending = False
-
- debug("Generating diff for changenum %s" % changenum)
-
- description = execute(["p4", "describe", "-s", changenum],
- split_lines=True)
-
- if '*pending*' in description[0]:
- cl_is_pending = True
-
- # Get the file list
- for line_num, line in enumerate(description):
- if 'Affected files ...' in line:
- break
- else:
- # Got to the end of all the description lines and didn't find
- # what we were looking for.
- die("Couldn't find any affected files for this change.")
-
- description = description[line_num+2:]
-
- diff_lines = []
-
- empty_filename = make_tempfile()
- tmp_diff_from_filename = make_tempfile()
- tmp_diff_to_filename = make_tempfile()
-
- for line in description:
- line = line.strip()
- if not line:
- continue
-
- m = re.search(r'\.\.\. ([^#]+)#(\d+) (add|edit|delete|integrate|branch)', line)
- if not m:
- die("Unsupported line from p4 opened: %s" % line)
-
- depot_path = m.group(1)
- base_revision = int(m.group(2))
- if not cl_is_pending:
- # If the changelist is pending our base revision is the one that's
- # currently in the depot. If we're not pending the base revision is
- # actually the revision prior to this one
- base_revision -= 1
-
- changetype = m.group(3)
-
- debug('Processing %s of %s' % (changetype, depot_path))
-
- old_file = new_file = empty_filename
- old_depot_path = new_depot_path = None
- changetype_short = None
-
- if changetype == 'edit' or changetype == 'integrate':
- # A big assumption
- new_revision = base_revision + 1
-
- # We have an old file, get p4 to take this old version from the
- # depot and put it into a plain old temp file for us
- old_depot_path = "%s#%s" % (depot_path, base_revision)
- self._write_file(old_depot_path, tmp_diff_from_filename)
- old_file = tmp_diff_from_filename
-
- # Also print out the new file into a tmpfile
- if cl_is_pending:
- new_file = self._depot_to_local(depot_path)
- else:
- new_depot_path = "%s#%s" %(depot_path, new_revision)
- self._write_file(new_depot_path, tmp_diff_to_filename)
- new_file = tmp_diff_to_filename
-
- changetype_short = "M"
-
- elif changetype == 'add' or changetype == 'branch':
- # We have a new file, get p4 to put this new file into a pretty
- # temp file for us. No old file to worry about here.
- if cl_is_pending:
- new_file = self._depot_to_local(depot_path)
- else:
- self._write_file(depot_path, tmp_diff_to_filename)
- new_file = tmp_diff_to_filename
- changetype_short = "A"
-
- elif changetype == 'delete':
- # We've deleted a file, get p4 to put the deleted file into a temp
- # file for us. The new file remains the empty file.
- old_depot_path = "%s#%s" % (depot_path, base_revision)
- self._write_file(old_depot_path, tmp_diff_from_filename)
- old_file = tmp_diff_from_filename
- changetype_short = "D"
- else:
- die("Unknown change type '%s' for %s" % (changetype, depot_path))
-
- dl = self._do_diff(old_file, new_file, depot_path, base_revision, changetype_short)
- diff_lines += dl
-
- os.unlink(empty_filename)
- os.unlink(tmp_diff_from_filename)
- os.unlink(tmp_diff_to_filename)
- return (''.join(diff_lines), None)
-
- def _do_diff(self, old_file, new_file, depot_path, base_revision,
- changetype_short, ignore_unmodified=False):
- """
- Do the work of producing a diff for Perforce.
-
- old_file - The absolute path to the "old" file.
- new_file - The absolute path to the "new" file.
- depot_path - The depot path in Perforce for this file.
- base_revision - The base perforce revision number of the old file as
- an integer.
- changetype_short - The change type as a single character string.
- ignore_unmodified - If True, will return an empty list if the file
- is not changed.
-
- Returns a list of strings of diff lines.
- """
- if hasattr(os, 'uname') and os.uname()[0] == 'SunOS':
- diff_cmd = ["gdiff", "-urNp", old_file, new_file]
- else:
- diff_cmd = ["diff", "-urNp", old_file, new_file]
- # Diff returns "1" if differences were found.
- dl = execute(diff_cmd, extra_ignore_errors=(1,2),
- translate_newlines=False)
-
- # If the input file has ^M characters at end of line, lets ignore them.
- dl = dl.replace('\r\r\n', '\r\n')
- dl = dl.splitlines(True)
-
- cwd = os.getcwd()
- if depot_path.startswith(cwd):
- local_path = depot_path[len(cwd) + 1:]
- else:
- local_path = depot_path
-
- # Special handling for the output of the diff tool on binary files:
- # diff outputs "Files a and b differ"
- # and the code below expects the output to start with
- # "Binary files "
- if len(dl) == 1 and \
- dl[0] == ('Files %s and %s differ'% (old_file, new_file)):
- dl = ['Binary files %s and %s differ'% (old_file, new_file)]
-
- if dl == [] or dl[0].startswith("Binary files "):
- if dl == []:
- if ignore_unmodified:
- return []
- else:
- print "Warning: %s in your changeset is unmodified" % \
- local_path
-
- dl.insert(0, "==== %s#%s ==%s== %s ====\n" % \
- (depot_path, base_revision, changetype_short, local_path))
- dl.append('\n')
- else:
- m = re.search(r'(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)', dl[1])
- if m:
- timestamp = m.group(1)
- else:
- # Thu Sep 3 11:24:48 2007
- m = re.search(r'(\w+)\s+(\w+)\s+(\d+)\s+(\d\d:\d\d:\d\d)\s+(\d\d\d\d)', dl[1])
- if not m:
- die("Unable to parse diff header: %s" % dl[1])
-
- month_map = {
- "Jan": "01",
- "Feb": "02",
- "Mar": "03",
- "Apr": "04",
- "May": "05",
- "Jun": "06",
- "Jul": "07",
- "Aug": "08",
- "Sep": "09",
- "Oct": "10",
- "Nov": "11",
- "Dec": "12",
- }
- month = month_map[m.group(2)]
- day = m.group(3)
- timestamp = m.group(4)
- year = m.group(5)
-
- timestamp = "%s-%s-%s %s" % (year, month, day, timestamp)
-
- dl[0] = "--- %s\t%s#%s\n" % (local_path, depot_path, base_revision)
- dl[1] = "+++ %s\t%s\n" % (local_path, timestamp)
-
- return dl
-
- def _write_file(self, depot_path, tmpfile):
- """
- Grabs a file from Perforce and writes it to a temp file. p4 print sets
- the file readonly and that causes a later call to unlink fail. So we
- make the file read/write.
- """
- debug('Writing "%s" to "%s"' % (depot_path, tmpfile))
- execute(["p4", "print", "-o", tmpfile, "-q", depot_path])
- os.chmod(tmpfile, stat.S_IREAD | stat.S_IWRITE)
-
- def _depot_to_local(self, depot_path):
- """
- Given a path in the depot return the path on the local filesystem to
- the same file. If there are multiple results, take only the last
- result from the where command.
- """
- where_output = self._run_p4(['where', depot_path])
- return where_output[-1]['path']
-
-
-class MercurialClient(SCMClient):
- """
- A wrapper around the hg Mercurial tool that fetches repository
- information and generates compatible diffs.
- """
- def get_repository_info(self):
- if not check_install('hg --help'):
- return None
-
- data = execute(["hg", "root"], ignore_errors=True)
- if data.startswith('abort:'):
- # hg aborted => no mercurial repository here.
- return None
-
- # Elsewhere, hg root output give us the repository path.
-
- # We save data here to use it as a fallback. See below
- local_data = data.strip()
-
- svn = execute(["hg", "svn", "info", ], ignore_errors=True)
-
- if (not svn.startswith('abort:') and
- not svn.startswith("hg: unknown command")):
- self.type = 'svn'
- m = re.search(r'^Repository Root: (.+)$', svn, re.M)
-
- if not m:
- return None
-
- path = m.group(1)
- m2 = re.match(r'^(svn\+ssh|http|https)://([-a-zA-Z0-9.]*@)(.*)$',
- path)
- if m2:
- path = '%s://%s' % (m2.group(1), m2.group(3))
-
- m = re.search(r'^URL: (.+)$', svn, re.M)
-
- if not m:
- return None
-
- base_path = m.group(1)[len(path):] or "/"
- return RepositoryInfo(path=path,
- base_path=base_path,
- supports_parent_diffs=True)
-
- self.type = 'hg'
-
- # We are going to search .hg/hgrc for the default path.
- file_name = os.path.join(local_data,'.hg', 'hgrc')
-
- if not os.path.exists(file_name):
- return RepositoryInfo(path=local_data, base_path='/',
- supports_parent_diffs=True)
-
- f = open(file_name)
- data = f.read()
- f.close()
-
- m = re.search(r'^default\s+=\s+(.+)$', data, re.M)
-
- if not m:
- # Return the local path, if no default value is found.
- return RepositoryInfo(path=local_data, base_path='/',
- supports_parent_diffs=True)
-
- path = m.group(1).strip()
-
- return RepositoryInfo(path=path, base_path='',
- supports_parent_diffs=True)
-
- def diff(self, files):
- """
- Performs a diff across all modified files in a Mercurial repository.
- """
- # We don't support parent diffs with Mercurial yet, so we always
- # return None for the parent diff.
- if self.type == 'svn':
- parent = execute(['hg', 'parent', '--svn', '--template',
- '{node}\n']).strip()
-
- if options.parent_branch:
- parent = options.parent_branch
-
- if options.guess_summary and not options.summary:
- options.summary = execute(['hg', 'log', '-r.', '--template',
- r'{desc|firstline}\n'])
-
- if options.guess_description and not options.description:
- numrevs = len(execute(['hg', 'log', '-r.:%s' % parent,
- '--follow', '--template',
- r'{rev}\n']).strip().split('\n'))
- options.description = execute(['hg', 'log', '-r.:%s' % parent,
- '--follow', '--template',
- r'{desc}\n\n', '--limit',
- str(numrevs-1)]).strip()
-
- return (execute(["hg", "diff", "--svn", '-r%s:.' % parent]), None)
-
- return (execute(["hg", "diff"] + files), None)
-
- def diff_between_revisions(self, revision_range, args, repository_info):
- """
- Performs a diff between 2 revisions of a Mercurial repository.
- """
- if self.type != 'hg':
- raise NotImplementedError
-
- r1, r2 = revision_range.split(':')
- return execute(["hg", "diff", "-r", r1, "-r", r2])
-
-
-class GitClient(SCMClient):
- """
- A wrapper around git that fetches repository information and generates
- compatible diffs. This will attempt to generate a diff suitable for the
- remote repository, whether git, SVN or Perforce.
- """
- def get_repository_info(self):
- if not check_install('git --help'):
- return None
-
- git_dir = execute(["git", "rev-parse", "--git-dir"],
- ignore_errors=True).strip()
-
- if git_dir.startswith("fatal:") or not os.path.isdir(git_dir):
- return None
-
- # post-review in directories other than the top level of
- # of a work-tree would result in broken diffs on the server
- os.chdir(os.path.dirname(os.path.abspath(git_dir)))
-
- # We know we have something we can work with. Let's find out
- # what it is. We'll try SVN first.
- data = execute(["git", "svn", "info"], ignore_errors=True)
-
- m = re.search(r'^Repository Root: (.+)$', data, re.M)
- if m:
- path = m.group(1)
- m = re.search(r'^URL: (.+)$', data, re.M)
-
- if m:
- base_path = m.group(1)[len(path):] or "/"
- m = re.search(r'^Repository UUID: (.+)$', data, re.M)
-
- if m:
- uuid = m.group(1)
- self.type = "svn"
-
- return SvnRepositoryInfo(path=path, base_path=base_path,
- uuid=uuid,
- supports_parent_diffs=True)
- else:
- # Versions of git-svn before 1.5.4 don't (appear to) support
- # 'git svn info'. If we fail because of an older git install,
- # here, figure out what version of git is installed and give
- # the user a hint about what to do next.
- version = execute(["git", "svn", "--version"], ignore_errors=True)
- version_parts = re.search('version (\d+)\.(\d+)\.(\d+)',
- version)
- svn_remote = execute(["git", "config", "--get",
- "svn-remote.svn.url"], ignore_errors=True)
-
- if (version_parts and
- not self.is_valid_version((int(version_parts.group(1)),
- int(version_parts.group(2)),
- int(version_parts.group(3))),
- (1, 5, 4)) and
- svn_remote):
- die("Your installation of git-svn must be upgraded to " + \
- "version 1.5.4 or later")
-
- # Okay, maybe Perforce.
- # TODO
-
- # Nope, it's git then.
- origin = execute(["git", "remote", "show", "origin"])
- m = re.search(r'URL: (.+)', origin)
- if m:
- url = m.group(1).rstrip('/')
- if url:
- self.type = "git"
- return RepositoryInfo(path=url, base_path='',
- supports_parent_diffs=True)
-
- return None
-
- def is_valid_version(self, actual, expected):
- """
- Takes two tuples, both in the form:
- (major_version, minor_version, micro_version)
- Returns true if the actual version is greater than or equal to
- the expected version, and false otherwise.
- """
- return (actual[0] > expected[0]) or \
- (actual[0] == expected[0] and actual[1] > expected[1]) or \
- (actual[0] == expected[0] and actual[1] == expected[1] and \
- actual[2] >= expected[2])
-
- def scan_for_server(self, repository_info):
- # Scan first for dot files, since it's faster and will cover the
- # user's $HOME/.reviewboardrc
- server_url = super(GitClient, self).scan_for_server(repository_info)
-
- if server_url:
- return server_url
-
- # TODO: Maybe support a server per remote later? Is that useful?
- url = execute(["git", "config", "--get", "reviewboard.url"],
- ignore_errors=True).strip()
- if url:
- return url
-
- if self.type == "svn":
- # Try using the reviewboard:url property on the SVN repo, if it
- # exists.
- prop = SVNClient().scan_for_server_property(repository_info)
-
- if prop:
- return prop
-
- return None
-
- def diff(self, args):
- """
- Performs a diff across all modified files in the branch, taking into
- account a parent branch.
- """
- parent_branch = options.parent_branch or "master"
-
- diff_lines = self.make_diff(parent_branch)
-
- if parent_branch != "master":
- parent_diff_lines = self.make_diff("master", parent_branch)
- else:
- parent_diff_lines = None
-
- if options.guess_summary and not options.summary:
- options.summary = execute(["git", "log", "--pretty=format:%s",
- "HEAD^.."], ignore_errors=True).strip()
-
- if options.guess_description and not options.description:
- options.description = execute(
- ["git", "log", "--pretty=format:%s%n%n%b", parent_branch + ".."],
- ignore_errors=True).strip()
-
- return (diff_lines, parent_diff_lines)
-
- def make_diff(self, parent_branch, source_branch=""):
- """
- Performs a diff on a particular branch range.
- """
- if self.type == "svn":
- diff_lines = execute(["git", "diff", "--no-color", "--no-prefix",
- "-r", "-u", "%s..%s" % (parent_branch,
- source_branch)],
- split_lines=True)
- return self.make_svn_diff(parent_branch, diff_lines)
- elif self.type == "git":
- return execute(["git", "diff", "--no-color", "--full-index",
- parent_branch])
-
- return None
-
- def make_svn_diff(self, parent_branch, diff_lines):
- """
- Formats the output of git diff such that it's in a form that
- svn diff would generate. This is needed so the SVNTool in Review
- Board can properly parse this diff.
- """
- rev = execute(["git", "svn", "find-rev", "master"]).strip()
-
- if not rev:
- return None
-
- diff_data = ""
- filename = ""
- revision = ""
- newfile = False
-
- for line in diff_lines:
- if line.startswith("diff "):
- # Grab the filename and then filter this out.
- # This will be in the format of:
- #
- # diff --git a/path/to/file b/path/to/file
- info = line.split(" ")
- diff_data += "Index: %s\n" % info[2]
- diff_data += "=" * 67
- diff_data += "\n"
- elif line.startswith("index "):
- # Filter this out.
- pass
- elif line.strip() == "--- /dev/null":
- # New file
- newfile = True
- elif line.startswith("--- "):
- newfile = False
- diff_data += "--- %s\t(revision %s)\n" % \
- (line[4:].strip(), rev)
- elif line.startswith("+++ "):
- filename = line[4:].strip()
- if newfile:
- diff_data += "--- %s\t(revision 0)\n" % filename
- diff_data += "+++ %s\t(revision 0)\n" % filename
- else:
- # We already printed the "--- " line.
- diff_data += "+++ %s\t(working copy)\n" % filename
- else:
- diff_data += line
-
- return diff_data
-
- def diff_between_revisions(self, revision_range, args, repository_info):
- pass
-
-
-SCMCLIENTS = (
- SVNClient(),
- CVSClient(),
- GitClient(),
- MercurialClient(),
- PerforceClient(),
- ClearCaseClient(),
-)
-
-def debug(s):
- """
- Prints debugging information if post-review was run with --debug
- """
- if DEBUG or options and options.debug:
- print ">>> %s" % s
-
-
-def make_tempfile():
- """
- Creates a temporary file and returns the path. The path is stored
- in an array for later cleanup.
- """
- fd, tmpfile = mkstemp()
- os.close(fd)
- tempfiles.append(tmpfile)
- return tmpfile
-
-
-def check_install(command):
- """
- Try executing an external command and return a boolean indicating whether
- that command is installed or not. The 'command' argument should be
- something that executes quickly, without hitting the network (for
- instance, 'svn help' or 'git --version').
- """
- try:
- p = subprocess.Popen(command.split(' '),
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- return True
- except OSError:
- return False
-
-
-def execute(command, env=None, split_lines=False, ignore_errors=False,
- extra_ignore_errors=(), translate_newlines=True):
- """
- Utility function to execute a command and return the output.
- """
- if isinstance(command, list):
- debug(subprocess.list2cmdline(command))
- else:
- debug(command)
-
- if env:
- env.update(os.environ)
- else:
- env = os.environ.copy()
-
- env['LC_ALL'] = 'en_US.UTF-8'
- env['LANGUAGE'] = 'en_US.UTF-8'
-
- if sys.platform.startswith('win'):
- p = subprocess.Popen(command,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- shell=False,
- universal_newlines=translate_newlines,
- env=env)
- else:
- p = subprocess.Popen(command,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- shell=False,
- close_fds=True,
- universal_newlines=translate_newlines,
- env=env)
- if split_lines:
- data = p.stdout.readlines()
- else:
- data = p.stdout.read()
- rc = p.wait()
- if rc and not ignore_errors and rc not in extra_ignore_errors:
- die('Failed to execute command: %s\n%s' % (command, data))
-
- return data
-
-
-def die(msg=None):
- """
- Cleanly exits the program with an error message. Erases all remaining
- temporary files.
- """
- for tmpfile in tempfiles:
- try:
- os.unlink(tmpfile)
- except:
- pass
-
- if msg:
- print msg
-
- sys.exit(1)
-
-
-def walk_parents(path):
- """
- Walks up the tree to the root directory.
- """
- while os.path.splitdrive(path)[1] != os.sep:
- yield path
- path = os.path.dirname(path)
-
-
-def load_config_file(filename):
- """
- Loads data from a config file.
- """
- config = {
- 'TREES': {},
- }
-
- if os.path.exists(filename):
- try:
- execfile(filename, config)
- except:
- pass
-
- return config
-
-
-def tempt_fate(server, tool, changenum, diff_content=None,
- parent_diff_content=None, submit_as=None, retries=3):
- """
- Attempts to create a review request on a Review Board server and upload
- a diff. On success, the review request path is displayed.
- """
- try:
- save_draft = False
-
- if options.rid:
- review_request = server.get_review_request(options.rid)
- else:
- review_request = server.new_review_request(changenum, submit_as)
-
- if options.target_groups:
- server.set_review_request_field(review_request, 'target_groups',
- options.target_groups)
- save_draft = True
-
- if options.target_people:
- server.set_review_request_field(review_request, 'target_people',
- options.target_people)
- save_draft = True
-
- if options.summary:
- server.set_review_request_field(review_request, 'summary',
- options.summary)
- save_draft = True
-
- if options.branch:
- server.set_review_request_field(review_request, 'branch',
- options.branch)
- save_draft = True
-
- if options.bugs_closed:
- server.set_review_request_field(review_request, 'bugs_closed',
- options.bugs_closed)
- save_draft = True
-
- if options.description:
- server.set_review_request_field(review_request, 'description',
- options.description)
- save_draft = True
-
- if options.testing_done:
- server.set_review_request_field(review_request, 'testing_done',
- options.testing_done)
- save_draft = True
-
- if save_draft:
- server.save_draft(review_request)
- except APIError, e:
- rsp, = e.args
- if rsp['err']['code'] == 103: # Not logged in
- retries = retries - 1
-
- # We had an odd issue where the server ended up a couple of
- # years in the future. Login succeeds but the cookie date was
- # "odd" so use of the cookie appeared to fail and eventually
- # ended up at max recursion depth :-(. Check for a maximum
- # number of retries.
- if retries >= 0:
- server.login(force=True)
- tempt_fate(server, tool, changenum, diff_content,
- parent_diff_content, submit_as, retries=retries)
- return
-
- if options.rid:
- die("Error getting review request %s: %s (code %s)" % \
- (options.rid, rsp['err']['msg'], rsp['err']['code']))
- else:
- die("Error creating review request: %s (code %s)" % \
- (rsp['err']['msg'], rsp['err']['code']))
-
-
- if not server.info.supports_changesets or not options.change_only:
- try:
- server.upload_diff(review_request, diff_content,
- parent_diff_content)
- except APIError, e:
- rsp, = e.args
- print "Error uploading diff: %s (%s)" % (rsp['err']['msg'],
- rsp['err']['code'])
- debug(rsp)
- die("Your review request still exists, but the diff is not " +
- "attached.")
-
- if options.publish:
- server.publish(review_request)
-
- request_url = 'r/' + str(review_request['id'])
- review_url = urljoin(server.url, request_url)
-
- if not review_url.startswith('http'):
- review_url = 'http://%s' % review_url
-
- print "Review request #%s posted." % (review_request['id'],)
- print
- print review_url
-
- return review_url
-
-
-def parse_options(args):
- parser = OptionParser(usage="%prog [-pond] [-r review_id] [changenum]",
- version="%prog " + VERSION)
-
- parser.add_option("-p", "--publish",
- dest="publish", action="store_true", default=PUBLISH,
- help="publish the review request immediately after "
- "submitting")
- parser.add_option("-r", "--review-request-id",
- dest="rid", metavar="ID", default=None,
- help="existing review request ID to update")
- parser.add_option("-o", "--open",
- dest="open_browser", action="store_true",
- default=OPEN_BROWSER,
- help="open a web browser to the review request page")
- parser.add_option("-n", "--output-diff",
- dest="output_diff_only", action="store_true",
- default=False,
- help="outputs a diff to the console and exits. "
- "Does not post")
- parser.add_option("--server",
- dest="server", default=REVIEWBOARD_URL,
- metavar="SERVER",
- help="specify a different Review Board server "
- "to use")
- parser.add_option("--diff-only",
- dest="diff_only", action="store_true", default=False,
- help="uploads a new diff, but does not update "
- "info from changelist")
- parser.add_option("--target-groups",
- dest="target_groups", default=TARGET_GROUPS,
- help="names of the groups who will perform "
- "the review")
- parser.add_option("--target-people",
- dest="target_people", default=TARGET_PEOPLE,
- help="names of the people who will perform "
- "the review")
- parser.add_option("--summary",
- dest="summary", default=None,
- help="summary of the review ")
- parser.add_option("--description",
- dest="description", default=None,
- help="description of the review ")
- parser.add_option("--description-file",
- dest="description_file", default=None,
- help="text file containing a description of the review")
- parser.add_option("--guess-summary",
- dest="guess_summary", action="store_true",
- default=False,
- help="guess summary from the latest commit (git/"
- "hgsubversion only)")
- parser.add_option("--guess-description",
- dest="guess_description", action="store_true",
- default=False,
- help="guess description based on commits on this branch "
- "(git/hgsubversion only)")
- parser.add_option("--testing-done",
- dest="testing_done", default=None,
- help="details of testing done ")
- parser.add_option("--testing-done-file",
- dest="testing_file", default=None,
- help="text file containing details of testing done ")
- parser.add_option("--branch",
- dest="branch", default=None,
- help="affected branch ")
- parser.add_option("--bugs-closed",
- dest="bugs_closed", default=None,
- help="list of bugs closed ")
- parser.add_option("--revision-range",
- dest="revision_range", default=None,
- help="generate the diff for review based on given "
- "revision range")
- parser.add_option("--label",
- dest="label", default=None,
- help="label (ClearCase Only) ")
- parser.add_option("--submit-as",
- dest="submit_as", default=SUBMIT_AS, metavar="USERNAME",
- help="user name to be recorded as the author of the "
- "review request, instead of the logged in user")
- parser.add_option("--username",
- dest="username", default=None, metavar="USERNAME",
- help="user name to be supplied to the reviewboard server")
- parser.add_option("--password",
- dest="password", default=None, metavar="PASSWORD",
- help="password to be supplied to the reviewboard server")
- parser.add_option("--change-only",
- dest="change_only", action="store_true",
- default=False,
- help="updates info from changelist, but does "
- "not upload a new diff (only available if your "
- "repository supports changesets)")
- parser.add_option("--parent",
- dest="parent_branch", default=None,
- metavar="PARENT_BRANCH",
- help="the parent branch this diff should be against "
- "(only available if your repository supports "
- "parent diffs)")
- parser.add_option("--p4-client",
- dest="p4_client", default=None,
- help="the Perforce client name that the review is in")
- parser.add_option("--p4-port",
- dest="p4_port", default=None,
- help="the Perforce servers IP address that the review is on")
- parser.add_option("--repository-url",
- dest="repository_url", default=None,
- help="the url for a repository for creating a diff "
- "outside of a working copy (currently only supported "
- "by Subversion). Requires --revision-range")
- parser.add_option("-d", "--debug",
- action="store_true", dest="debug", default=DEBUG,
- help="display debug output")
-
- (globals()["options"], args) = parser.parse_args(args)
-
- if options.description and options.description_file:
- sys.stderr.write("The --description and --description-file options "
- "are mutually exclusive.\n")
- sys.exit(1)
-
- if options.description_file:
- if os.path.exists(options.description_file):
- fp = open(options.description_file, "r")
- options.description = fp.read()
- fp.close()
- else:
- sys.stderr.write("The description file %s does not exist.\n" %
- options.description_file)
- sys.exit(1)
-
- if options.testing_done and options.testing_file:
- sys.stderr.write("The --testing-done and --testing-done-file options "
- "are mutually exclusive.\n")
- sys.exit(1)
-
- if options.testing_file:
- if os.path.exists(options.testing_file):
- fp = open(options.testing_file, "r")
- options.testing_done = fp.read()
- fp.close()
- else:
- sys.stderr.write("The testing file %s does not exist.\n" %
- options.testing_file)
- sys.exit(1)
-
- if options.repository_url and not options.revision_range:
- sys.stderr.write("The --repository-url option requires the "
- "--revision-range option.\n")
- sys.exit(1)
-
- return args
-
-def determine_client():
-
- repository_info = None
- tool = None
-
- # Try to find the SCM Client we're going to be working with.
- for tool in SCMCLIENTS:
- repository_info = tool.get_repository_info()
-
- if repository_info:
- break
-
- if not repository_info:
- if options.repository_url:
- print "No supported repository could be access at the supplied url."
- else:
- print "The current directory does not contain a checkout from a"
- print "supported source code repository."
- sys.exit(1)
-
- # Verify that options specific to an SCM Client have not been mis-used.
- if options.change_only and not repository_info.supports_changesets:
- sys.stderr.write("The --change-only option is not valid for the "
- "current SCM client.\n")
- sys.exit(1)
-
- if options.parent_branch and not repository_info.supports_parent_diffs:
- sys.stderr.write("The --parent option is not valid for the "
- "current SCM client.\n")
- sys.exit(1)
-
- if ((options.p4_client or options.p4_port) and \
- not isinstance(tool, PerforceClient)):
- sys.stderr.write("The --p4-client and --p4-port options are not valid "
- "for the current SCM client.\n")
- sys.exit(1)
-
- return (repository_info, tool)
-
-def main():
- if 'USERPROFILE' in os.environ:
- homepath = os.path.join(os.environ["USERPROFILE"], "Local Settings",
- "Application Data")
- elif 'HOME' in os.environ:
- homepath = os.environ["HOME"]
- else:
- homepath = ''
-
- # Load the config and cookie files
- globals()['user_config'] = \
- load_config_file(os.path.join(homepath, ".reviewboardrc"))
- cookie_file = os.path.join(homepath, ".post-review-cookies.txt")
-
- args = parse_options(sys.argv[1:])
-
- repository_info, tool = determine_client()
-
- # Try to find a valid Review Board server to use.
- if options.server:
- server_url = options.server
- else:
- server_url = tool.scan_for_server(repository_info)
-
- if not server_url:
- print "Unable to find a Review Board server for this source code tree."
- sys.exit(1)
-
- server = ReviewBoardServer(server_url, repository_info, cookie_file)
-
- if repository_info.supports_changesets:
- changenum = tool.get_changenum(args)
- else:
- changenum = None
-
- if options.revision_range:
- diff = tool.diff_between_revisions(options.revision_range, args,
- repository_info)
- parent_diff = None
- elif options.label and isinstance(tool, ClearCaseClient):
- diff, parent_diff = tool.diff_label(options.label)
- else:
- diff, parent_diff = tool.diff(args)
-
- if options.output_diff_only:
- print diff
- sys.exit(0)
-
- # Let's begin.
- server.login()
-
- review_url = tempt_fate(server, tool, changenum, diff_content=diff,
- parent_diff_content=parent_diff,
- submit_as=options.submit_as)
-
- # Load the review up in the browser if requested to:
- if options.open_browser:
- try:
- import webbrowser
- if 'open_new_tab' in dir(webbrowser):
- # open_new_tab is only in python 2.5+
- webbrowser.open_new_tab(review_url)
- elif 'open_new' in dir(webbrowser):
- webbrowser.open_new(review_url)
- else:
- os.system( 'start %s' % review_url )
- except:
- print 'Error opening review URL: %s' % review_url
-
-
-if __name__ == "__main__":
- main()
diff --git a/test/review b/test/review
deleted file mode 100755
index e1ccb9c0af..0000000000
--- a/test/review
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-
-if [ -z $1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ] || [ "$1" = "-help" ] || [ "$1" = "-?" ]; then
- echo "Usage: `basename $0` [rev] [args]\n"
- echo " [rev] : either the revision number without leading 'r' (post-commit),"
- echo " or '-loc' to create a review from current local changes (pre-commit)\n"
- echo " [args] : optional arguments:"
- echo " -r ID existing review request ID to update\n"
- exit 1
-fi
-
-POSTREVIEW=`dirname $0`/postreview.py
-
-if [ "$1" = "-loc" ]; then
- echo "creating review request from local changes..."
- REVARG=""
- LOG=""
- SUMMARY="local changes"
- REPO=""
-else
- REV=$1
- PREV=`expr $REV - 1`
- if [ $? -ne 0 ]; then
- echo "argument revision not a number: $REV"
- exit 1
- fi
-
- echo "creating review request for changeset $REV..."
-
- LOG="`svn log http://lampsvn.epfl.ch/svn-repos/scala -c $REV`"
- if [ $? -ne 0 ]; then
- echo "could not get svn log for revision $REV"
- exit 1
- fi
-
- REVARG="--revision-range=$PREV:$REV"
- SUMMARY="r$REV"
- REPO="--repository-url=http://lampsvn.epfl.ch/svn-repos/scala"
-fi
-
-
-shift # remove parameter $1 (revision)
-
-python $POSTREVIEW --server="https://chara2.epfl.ch" $REVARG --summary="$SUMMARY" --description="$LOG" $REPO -o $@