| 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
|
|
|
|
|
|
|