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