Index: third_party/upload.py |
diff --git a/third_party/upload.py b/third_party/upload.py |
index e8fee3b0a6ca480e0e38d5109d612f67f551dee3..d5e1fb2eee5d22f4c4f00fd13f75cef8db685b5d 100755 |
--- a/third_party/upload.py |
+++ b/third_party/upload.py |
@@ -1,4 +1,5 @@ |
#!/usr/bin/env python |
+# coding: utf-8 |
# |
# Copyright 2007 Google Inc. |
# |
@@ -215,7 +216,7 @@ class AbstractRpcServer(object): |
def _CreateRequest(self, url, data=None): |
"""Creates a new urllib request.""" |
logging.debug("Creating request for: '%s' with payload:\n%s", url, data) |
- req = urllib2.Request(url, data=data) |
+ req = urllib2.Request(url, data=data, headers={"Accept": "text/plain"}) |
if self.host_override: |
req.add_header("Host", self.host_override) |
for key, value in self.extra_headers.iteritems(): |
@@ -399,14 +400,13 @@ class AbstractRpcServer(object): |
raise |
elif e.code == 401 or e.code == 302: |
self._Authenticate() |
-## elif e.code >= 500 and e.code < 600: |
-## # Server Error - try again. |
-## continue |
elif e.code == 301: |
# Handle permanent redirect manually. |
url = e.info()["location"] |
url_loc = urlparse.urlparse(url) |
self.host = '%s://%s' % (url_loc[0], url_loc[1]) |
+ elif e.code >= 500: |
+ ErrorExit(e.read()) |
else: |
raise |
finally: |
@@ -532,14 +532,13 @@ group.add_option("--account_type", action="store", dest="account_type", |
"valid choices are 'GOOGLE' and 'HOSTED').")) |
# Issue |
group = parser.add_option_group("Issue options") |
-group.add_option("-d", "--description", action="store", dest="description", |
- metavar="DESCRIPTION", default=None, |
- help="Optional description when creating an issue.") |
-group.add_option("-f", "--description_file", action="store", |
- dest="description_file", metavar="DESCRIPTION_FILE", |
+group.add_option("-t", "--title", action="store", dest="title", |
+ help="New issue subject or new patch set title") |
+group.add_option("-m", "--message", action="store", dest="message", |
default=None, |
- help="Optional path of a file that contains " |
- "the description when creating an issue.") |
+ help="New issue description or new patch set message") |
+group.add_option("-F", "--file", action="store", dest="file", |
+ default=None, help="Read the message above from file.") |
group.add_option("-r", "--reviewers", action="store", dest="reviewers", |
metavar="REVIEWERS", default=None, |
help="Add reviewers (comma separated email addresses).") |
@@ -551,10 +550,6 @@ group.add_option("--private", action="store_true", dest="private", |
help="Make the issue restricted to reviewers and those CCed") |
# Upload options |
group = parser.add_option_group("Patch options") |
-group.add_option("-m", "--message", action="store", dest="message", |
- metavar="MESSAGE", default=None, |
- help="A message to identify the patch. " |
- "Will prompt if omitted.") |
group.add_option("-i", "--issue", type="int", action="store", |
metavar="ISSUE", default=None, |
help="Issue number to which to add. Defaults to new issue.") |
@@ -785,7 +780,7 @@ class VersionControlSystem(object): |
options: Command line options. |
""" |
self.options = options |
- |
+ |
def GetGUID(self): |
"""Return string to distinguish the repository from others, for example to |
query all opened review issues for it""" |
@@ -945,7 +940,7 @@ class SubversionVCS(VersionControlSystem): |
# Result is cached to not guess it over and over again in GetBaseFile(). |
required = self.options.download_base or self.options.revision is not None |
self.svn_base = self._GuessBase(required) |
- |
+ |
def GetGUID(self): |
return self._GetInfo("Repository UUID") |
@@ -980,7 +975,7 @@ class SubversionVCS(VersionControlSystem): |
if required: |
ErrorExit("Can't find URL in output from svn info") |
return None |
- |
+ |
def _GetInfo(self, key): |
"""Parses 'svn info' for current dir. Returns value for key or None""" |
for line in RunShell(["svn", "info"]).splitlines(): |
@@ -1223,7 +1218,7 @@ class GitVCS(VersionControlSystem): |
self.hashes = {} |
# Map of new filename -> old filename for renames. |
self.renames = {} |
- |
+ |
def GetGUID(self): |
revlist = RunShell("git rev-list --parents HEAD".split()).splitlines() |
# M-A: Return the 1st root hash, there could be multiple when a |
@@ -1310,7 +1305,7 @@ class GitVCS(VersionControlSystem): |
# then the diff of everything except deleted files with rename and copy |
# support enabled. |
cmd = [ |
- "git", "diff", "--no-ext-diff", "--full-index", "--ignore-submodules" |
+ "git", "diff", "--no-color", "--no-ext-diff", "--full-index", "--ignore-submodules" |
] |
diff = RunShell(cmd + ["--diff-filter=D"] + extra_args, env=env, |
silent_ok=True) |
@@ -1464,11 +1459,13 @@ class MercurialVCS(VersionControlSystem): |
def _GetRelPath(self, filename): |
"""Get relative path of a file according to the current directory, |
given its logical path in the repo.""" |
- assert filename.startswith(self.subdir), (filename, self.subdir) |
- return filename[len(self.subdir):].lstrip(r"\/") |
+ absname = os.path.join(self.repo_dir, filename) |
+ return os.path.relpath(absname) |
def GenerateDiff(self, extra_args): |
- cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args |
+ cmd = [ |
+ "hg", "diff", "--color", "never", "--git", "-r", self.base_rev |
+ ] + extra_args |
data = RunShell(cmd, silent_ok=True) |
svndiff = [] |
filecount = 0 |
@@ -1494,7 +1491,8 @@ class MercurialVCS(VersionControlSystem): |
def GetUnknownFiles(self): |
"""Return a list of files unknown to the VCS.""" |
args = [] |
- status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."], |
+ status = RunShell( |
+ ["hg", "status", "--color", "never", "--rev", self.base_rev, "-u", "."], |
silent_ok=True) |
unknown_files = [] |
for line in status.splitlines(): |
@@ -1504,15 +1502,16 @@ class MercurialVCS(VersionControlSystem): |
return unknown_files |
def GetBaseFile(self, filename): |
- # "hg status" and "hg cat" both take a path relative to the current subdir |
- # rather than to the repo root, but "hg diff" has given us the full path |
- # to the repo root. |
+ # "hg status" and "hg cat" both take a path relative to the current subdir, |
+ # but "hg diff" has given us the path relative to the repo root. |
base_content = "" |
new_content = None |
is_binary = False |
oldrelpath = relpath = self._GetRelPath(filename) |
# "hg status -C" returns two lines for moved/copied files, one otherwise |
- out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath]) |
+ out = RunShell( |
+ [ "hg", "status", "--color", "never", "-C", "--rev", self.base_rev, |
+ relpath]) |
out = out.splitlines() |
# HACK: strip error message about missing file/directory if it isn't in |
# the working copy |
@@ -1575,15 +1574,15 @@ class PerforceVCS(VersionControlSystem): |
ConfirmLogin() |
- if not options.message: |
+ if not options.title: |
description = self.RunPerforceCommand(["describe", self.p4_changelist], |
marshal_output=True) |
if description and "desc" in description: |
# Rietveld doesn't support multi-line descriptions |
- raw_message = description["desc"].strip() |
- lines = raw_message.splitlines() |
+ raw_title = description["desc"].strip() |
+ lines = raw_title.splitlines() |
if len(lines): |
- options.message = lines[0] |
+ options.title = lines[0] |
def GetGUID(self): |
"""For now we don't know how to get repository ID for Perforce""" |
@@ -1974,10 +1973,12 @@ def GuessVCSName(options): |
if res != None: |
return res |
- # Subversion has a .svn in all working directories. |
- if os.path.isdir('.svn'): |
- logging.info("Guessed VCS = Subversion") |
- return (VCS_SUBVERSION, None) |
+ # Subversion from 1.7 has a single centralized .svn folder |
+ # ( see http://subversion.apache.org/docs/release-notes/1.7.html#wc-ng ) |
+ # That's why we use 'svn info' instead of checking for .svn dir |
+ res = RunDetectCommand(VCS_SUBVERSION, ["svn", "info"]) |
+ if res != None: |
+ return res |
# Git has a command to test if you're in a git tree. |
# Try running it, but don't die if we don't have git installed. |
@@ -2219,20 +2220,13 @@ def RealMain(argv, data=None): |
files = vcs.GetBaseFiles(data) |
if verbosity >= 1: |
print "Upload server:", options.server, "(change with -s/--server)" |
- if options.issue: |
- prompt = "Message describing this patch set: " |
- else: |
- prompt = "New issue subject: " |
- message = options.message or raw_input(prompt).strip() |
- if not message: |
- ErrorExit("A non-empty message is required") |
rpc_server = GetRpcServer(options.server, |
options.email, |
options.host, |
options.save_cookies, |
options.account_type) |
- form_fields = [("subject", message)] |
- |
+ form_fields = [] |
+ |
repo_guid = vcs.GetGUID() |
if repo_guid: |
form_fields.append(("repo_guid", repo_guid)) |
@@ -2256,15 +2250,40 @@ def RealMain(argv, data=None): |
for cc in options.cc.split(','): |
CheckReviewer(cc) |
form_fields.append(("cc", options.cc)) |
- description = options.description |
- if options.description_file: |
- if options.description: |
- ErrorExit("Can't specify description and description_file") |
- file = open(options.description_file, 'r') |
- description = file.read() |
+ |
+ # Process --message, --title and --file. |
+ message = options.message or "" |
+ title = options.title or "" |
+ if options.file: |
+ if options.message: |
+ ErrorExit("Can't specify both message and message file options") |
+ file = open(options.file, 'r') |
+ message = file.read() |
file.close() |
- if description: |
- form_fields.append(("description", description)) |
+ if options.issue: |
+ prompt = "Title describing this patch set: " |
+ else: |
+ prompt = "New issue subject: " |
+ title = ( |
+ title or message.split('\n', 1)[0].strip() or raw_input(prompt).strip()) |
+ if not title and not options.issue: |
+ ErrorExit("A non-empty title is required for a new issue") |
+ # For existing issues, it's fine to give a patchset an empty name. Rietveld |
+ # doesn't accept that so use a whitespace. |
+ title = title or " " |
+ if len(title) > 100: |
+ title = title[:99] + '…' |
+ if title and not options.issue: |
+ message = message or title |
+ |
+ form_fields.append(("subject", title)) |
+ if message: |
+ if not options.issue: |
+ form_fields.append(("description", message)) |
+ else: |
+ # TODO: [ ] Use /<issue>/publish to add a comment. |
+ pass |
+ |
# Send a hash of all the base file so the server can determine if a copy |
# already exists in an earlier patchset. |
base_hashes = "" |
@@ -2282,10 +2301,6 @@ def RealMain(argv, data=None): |
form_fields.append(("private", "1")) |
if options.send_patch: |
options.send_mail = True |
- # If we're uploading base files, don't send the email before the uploads, so |
- # that it contains the file status. |
- if options.send_mail and options.download_base: |
- form_fields.append(("send_mail", "1")) |
if not options.download_base: |
form_fields.append(("content_upload", "1")) |
if len(data) > MAX_UPLOAD_SIZE: |
@@ -2320,11 +2335,15 @@ def RealMain(argv, data=None): |
if not options.download_base: |
vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files) |
- if options.send_mail: |
- payload = "" |
- if options.send_patch: |
- payload=urllib.urlencode({"attach_patch": "yes"}) |
- rpc_server.Send("/" + issue + "/mail", payload=payload) |
+ |
+ payload = {} # payload for final request |
+ if options.send_mail: |
+ payload["send_mail"] = "yes" |
+ if options.send_patch: |
+ payload["attach_patch"] = "yes" |
+ payload = urllib.urlencode(payload) |
+ rpc_server.Send("/" + issue + "/upload_complete/" + (patchset or ""), |
+ payload=payload) |
return issue, patchset |