| Index: third_party/upload.py
|
| diff --git a/third_party/upload.py b/third_party/upload.py
|
| index 8b7268a7c4db44f091f19ecbe60ecf4917c030b4..e8fee3b0a6ca480e0e38d5109d612f67f551dee3 100755
|
| --- a/third_party/upload.py
|
| +++ b/third_party/upload.py
|
| @@ -461,8 +461,39 @@ class HttpRpcServer(AbstractRpcServer):
|
| return opener
|
|
|
|
|
| +class CondensedHelpFormatter(optparse.IndentedHelpFormatter):
|
| + """Frees more horizontal space by removing indentation from group
|
| + options and collapsing arguments between short and long, e.g.
|
| + '-o ARG, --opt=ARG' to -o --opt ARG"""
|
| +
|
| + def format_heading(self, heading):
|
| + return "%s:\n" % heading
|
| +
|
| + def format_option(self, option):
|
| + self.dedent()
|
| + res = optparse.HelpFormatter.format_option(self, option)
|
| + self.indent()
|
| + return res
|
| +
|
| + def format_option_strings(self, option):
|
| + self.set_long_opt_delimiter(" ")
|
| + optstr = optparse.HelpFormatter.format_option_strings(self, option)
|
| + optlist = optstr.split(", ")
|
| + if len(optlist) > 1:
|
| + if option.takes_value():
|
| + # strip METAVAR from all but the last option
|
| + optlist = [x.split()[0] for x in optlist[:-1]] + optlist[-1:]
|
| + optstr = " ".join(optlist)
|
| + return optstr
|
| +
|
| +
|
| parser = optparse.OptionParser(
|
| - usage="%prog [options] [-- diff_options] [path...]")
|
| + usage="%prog [options] [-- diff_options] [path...]",
|
| + add_help_option=False,
|
| + formatter=CondensedHelpFormatter()
|
| +)
|
| +parser.add_option("-h", "--help", action="store_true",
|
| + help="Show this help message and exit.")
|
| parser.add_option("-y", "--assume_yes", action="store_true",
|
| dest="assume_yes", default=False,
|
| help="Assume that the answer to yes/no questions is 'yes'.")
|
| @@ -528,7 +559,7 @@ group.add_option("-i", "--issue", type="int", action="store",
|
| metavar="ISSUE", default=None,
|
| help="Issue number to which to add. Defaults to new issue.")
|
| group.add_option("--base_url", action="store", dest="base_url", default=None,
|
| - help="Base repository URL (listed as \"Base URL\" when "
|
| + help="Base URL path for files (listed as \"Base URL\" when "
|
| "viewing issue). If omitted, will be guessed automatically "
|
| "for SVN repos and left blank for others.")
|
| group.add_option("--download_base", action="store_true",
|
| @@ -544,10 +575,8 @@ group.add_option("--send_mail", action="store_true",
|
| help="Send notification email to reviewers.")
|
| group.add_option("-p", "--send_patch", action="store_true",
|
| dest="send_patch", default=False,
|
| - help="Send notification email to reviewers, with a diff of "
|
| - "the changes included as an attachment instead of "
|
| - "inline. Also prepends 'PATCH:' to the email subject. "
|
| - "(implies --send_mail)")
|
| + help="Same as --send_mail, but include diff as an "
|
| + "attachment, and prepend email subject with 'PATCH:'.")
|
| group.add_option("--vcs", action="store", dest="vcs",
|
| metavar="VCS", default=None,
|
| help=("Version control system (optional, usually upload.py "
|
| @@ -756,6 +785,12 @@ 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"""
|
| + raise NotImplementedError(
|
| + "abstract method -- subclass %s must override" % self.__class__)
|
|
|
| def PostProcessDiff(self, diff):
|
| """Return the diff with any special post processing this VCS needs, e.g.
|
| @@ -910,6 +945,9 @@ 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")
|
|
|
| def GuessBase(self, required):
|
| """Wrapper for _GuessBase."""
|
| @@ -922,12 +960,11 @@ class SubversionVCS(VersionControlSystem):
|
| required: If true, exits if the url can't be guessed, otherwise None is
|
| returned.
|
| """
|
| - info = RunShell(["svn", "info"])
|
| - for line in info.splitlines():
|
| - if line.startswith("URL: "):
|
| - url = line.split()[1]
|
| + url = self._GetInfo("URL")
|
| + if url:
|
| scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
|
| guess = ""
|
| + # TODO(anatoli) - repository specific hacks should be handled by server
|
| if netloc == "svn.python.org" and scheme == "svn+ssh":
|
| path = "projects" + path
|
| scheme = "http"
|
| @@ -943,6 +980,12 @@ 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():
|
| + if line.startswith(key + ": "):
|
| + return line.split(":", 1)[1].strip()
|
|
|
| def _EscapeFilename(self, filename):
|
| """Escapes filename for SVN commands."""
|
| @@ -1180,6 +1223,15 @@ 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
|
| + # subtree is merged. In that case, more analysis would need to
|
| + # be done to figure out which HEAD is the 'most representative'.
|
| + for r in revlist:
|
| + if ' ' not in r:
|
| + return r
|
|
|
| def PostProcessDiff(self, gitdiff):
|
| """Converts the diff output to include an svn-style "Index:" line as well
|
| @@ -1291,7 +1343,8 @@ class GitVCS(VersionControlSystem):
|
| status = "A +" # Match svn attribute name for renames.
|
| if filename not in self.hashes:
|
| # If a rename doesn't change the content, we never get a hash.
|
| - base_content = RunShell(["git", "show", "HEAD:" + filename])
|
| + base_content = RunShell(
|
| + ["git", "show", "HEAD:" + filename], silent_ok=True)
|
| elif not hash_before:
|
| status = "A"
|
| base_content = ""
|
| @@ -1323,6 +1376,10 @@ class CVSVCS(VersionControlSystem):
|
| def __init__(self, options):
|
| super(CVSVCS, self).__init__(options)
|
|
|
| + def GetGUID(self):
|
| + """For now we don't know how to get repository ID for CVS"""
|
| + return
|
| +
|
| def GetOriginalContent_(self, filename):
|
| RunShell(["cvs", "up", filename], silent_ok=True)
|
| # TODO need detect file content encoding
|
| @@ -1398,6 +1455,12 @@ class MercurialVCS(VersionControlSystem):
|
| else:
|
| self.base_rev = RunShell(["hg", "parent", "-q"]).split(':')[1].strip()
|
|
|
| + def GetGUID(self):
|
| + # See chapter "Uniquely identifying a repository"
|
| + # http://hgbook.red-bean.com/read/customizing-the-output-of-mercurial.html
|
| + info = RunShell("hg log -r0 --template {node}".split())
|
| + return info.strip()
|
| +
|
| def _GetRelPath(self, filename):
|
| """Get relative path of a file according to the current directory,
|
| given its logical path in the repo."""
|
| @@ -1522,6 +1585,10 @@ class PerforceVCS(VersionControlSystem):
|
| if len(lines):
|
| options.message = lines[0]
|
|
|
| + def GetGUID(self):
|
| + """For now we don't know how to get repository ID for Perforce"""
|
| + return
|
| +
|
| def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False,
|
| universal_newlines=True):
|
| args = ["p4"]
|
| @@ -2108,6 +2175,14 @@ def RealMain(argv, data=None):
|
| script (applies only to SVN checkouts).
|
| """
|
| options, args = parser.parse_args(argv[1:])
|
| + if options.help:
|
| + if options.verbose < 2:
|
| + # hide Perforce options
|
| + parser.epilog = "Use '--help -v' to show additional Perforce options."
|
| + parser.option_groups.remove(parser.get_option_group('--p4_port'))
|
| + parser.print_help()
|
| + sys.exit(0)
|
| +
|
| global verbosity
|
| verbosity = options.verbose
|
| if verbosity >= 3:
|
| @@ -2157,6 +2232,10 @@ def RealMain(argv, data=None):
|
| options.save_cookies,
|
| options.account_type)
|
| form_fields = [("subject", message)]
|
| +
|
| + repo_guid = vcs.GetGUID()
|
| + if repo_guid:
|
| + form_fields.append(("repo_guid", repo_guid))
|
| if base:
|
| b = urlparse.urlparse(base)
|
| username, netloc = urllib.splituser(b.netloc)
|
|
|