OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # coding: utf-8 |
2 # | 3 # |
3 # Copyright 2007 Google Inc. | 4 # Copyright 2007 Google Inc. |
4 # | 5 # |
5 # Licensed under the Apache License, Version 2.0 (the "License"); | 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
6 # you may not use this file except in compliance with the License. | 7 # you may not use this file except in compliance with the License. |
7 # You may obtain a copy of the License at | 8 # You may obtain a copy of the License at |
8 # | 9 # |
9 # http://www.apache.org/licenses/LICENSE-2.0 | 10 # http://www.apache.org/licenses/LICENSE-2.0 |
10 # | 11 # |
11 # Unless required by applicable law or agreed to in writing, software | 12 # Unless required by applicable law or agreed to in writing, software |
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
208 """Returns an OpenerDirector for making HTTP requests. | 209 """Returns an OpenerDirector for making HTTP requests. |
209 | 210 |
210 Returns: | 211 Returns: |
211 A urllib2.OpenerDirector object. | 212 A urllib2.OpenerDirector object. |
212 """ | 213 """ |
213 raise NotImplementedError() | 214 raise NotImplementedError() |
214 | 215 |
215 def _CreateRequest(self, url, data=None): | 216 def _CreateRequest(self, url, data=None): |
216 """Creates a new urllib request.""" | 217 """Creates a new urllib request.""" |
217 logging.debug("Creating request for: '%s' with payload:\n%s", url, data) | 218 logging.debug("Creating request for: '%s' with payload:\n%s", url, data) |
218 req = urllib2.Request(url, data=data) | 219 req = urllib2.Request(url, data=data, headers={"Accept": "text/plain"}) |
219 if self.host_override: | 220 if self.host_override: |
220 req.add_header("Host", self.host_override) | 221 req.add_header("Host", self.host_override) |
221 for key, value in self.extra_headers.iteritems(): | 222 for key, value in self.extra_headers.iteritems(): |
222 req.add_header(key, value) | 223 req.add_header(key, value) |
223 return req | 224 return req |
224 | 225 |
225 def _GetAuthToken(self, email, password): | 226 def _GetAuthToken(self, email, password): |
226 """Uses ClientLogin to authenticate the user, returning an auth token. | 227 """Uses ClientLogin to authenticate the user, returning an auth token. |
227 | 228 |
228 Args: | 229 Args: |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
392 try: | 393 try: |
393 f = self.opener.open(req) | 394 f = self.opener.open(req) |
394 response = f.read() | 395 response = f.read() |
395 f.close() | 396 f.close() |
396 return response | 397 return response |
397 except urllib2.HTTPError, e: | 398 except urllib2.HTTPError, e: |
398 if tries > 3: | 399 if tries > 3: |
399 raise | 400 raise |
400 elif e.code == 401 or e.code == 302: | 401 elif e.code == 401 or e.code == 302: |
401 self._Authenticate() | 402 self._Authenticate() |
402 ## elif e.code >= 500 and e.code < 600: | |
403 ## # Server Error - try again. | |
404 ## continue | |
405 elif e.code == 301: | 403 elif e.code == 301: |
406 # Handle permanent redirect manually. | 404 # Handle permanent redirect manually. |
407 url = e.info()["location"] | 405 url = e.info()["location"] |
408 url_loc = urlparse.urlparse(url) | 406 url_loc = urlparse.urlparse(url) |
409 self.host = '%s://%s' % (url_loc[0], url_loc[1]) | 407 self.host = '%s://%s' % (url_loc[0], url_loc[1]) |
| 408 elif e.code >= 500: |
| 409 ErrorExit(e.read()) |
410 else: | 410 else: |
411 raise | 411 raise |
412 finally: | 412 finally: |
413 socket.setdefaulttimeout(old_timeout) | 413 socket.setdefaulttimeout(old_timeout) |
414 | 414 |
415 | 415 |
416 class HttpRpcServer(AbstractRpcServer): | 416 class HttpRpcServer(AbstractRpcServer): |
417 """Provides a simplified RPC-style interface for HTTP requests.""" | 417 """Provides a simplified RPC-style interface for HTTP requests.""" |
418 | 418 |
419 def _Authenticate(self): | 419 def _Authenticate(self): |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
525 dest="save_cookies", default=True, | 525 dest="save_cookies", default=True, |
526 help="Do not save authentication cookies to local disk.") | 526 help="Do not save authentication cookies to local disk.") |
527 group.add_option("--account_type", action="store", dest="account_type", | 527 group.add_option("--account_type", action="store", dest="account_type", |
528 metavar="TYPE", default=AUTH_ACCOUNT_TYPE, | 528 metavar="TYPE", default=AUTH_ACCOUNT_TYPE, |
529 choices=["GOOGLE", "HOSTED"], | 529 choices=["GOOGLE", "HOSTED"], |
530 help=("Override the default account type " | 530 help=("Override the default account type " |
531 "(defaults to '%default', " | 531 "(defaults to '%default', " |
532 "valid choices are 'GOOGLE' and 'HOSTED').")) | 532 "valid choices are 'GOOGLE' and 'HOSTED').")) |
533 # Issue | 533 # Issue |
534 group = parser.add_option_group("Issue options") | 534 group = parser.add_option_group("Issue options") |
535 group.add_option("-d", "--description", action="store", dest="description", | 535 group.add_option("-t", "--title", action="store", dest="title", |
536 metavar="DESCRIPTION", default=None, | 536 help="New issue subject or new patch set title") |
537 help="Optional description when creating an issue.") | 537 group.add_option("-m", "--message", action="store", dest="message", |
538 group.add_option("-f", "--description_file", action="store", | |
539 dest="description_file", metavar="DESCRIPTION_FILE", | |
540 default=None, | 538 default=None, |
541 help="Optional path of a file that contains " | 539 help="New issue description or new patch set message") |
542 "the description when creating an issue.") | 540 group.add_option("-F", "--file", action="store", dest="file", |
| 541 default=None, help="Read the message above from file.") |
543 group.add_option("-r", "--reviewers", action="store", dest="reviewers", | 542 group.add_option("-r", "--reviewers", action="store", dest="reviewers", |
544 metavar="REVIEWERS", default=None, | 543 metavar="REVIEWERS", default=None, |
545 help="Add reviewers (comma separated email addresses).") | 544 help="Add reviewers (comma separated email addresses).") |
546 group.add_option("--cc", action="store", dest="cc", | 545 group.add_option("--cc", action="store", dest="cc", |
547 metavar="CC", default=None, | 546 metavar="CC", default=None, |
548 help="Add CC (comma separated email addresses).") | 547 help="Add CC (comma separated email addresses).") |
549 group.add_option("--private", action="store_true", dest="private", | 548 group.add_option("--private", action="store_true", dest="private", |
550 default=False, | 549 default=False, |
551 help="Make the issue restricted to reviewers and those CCed") | 550 help="Make the issue restricted to reviewers and those CCed") |
552 # Upload options | 551 # Upload options |
553 group = parser.add_option_group("Patch options") | 552 group = parser.add_option_group("Patch options") |
554 group.add_option("-m", "--message", action="store", dest="message", | |
555 metavar="MESSAGE", default=None, | |
556 help="A message to identify the patch. " | |
557 "Will prompt if omitted.") | |
558 group.add_option("-i", "--issue", type="int", action="store", | 553 group.add_option("-i", "--issue", type="int", action="store", |
559 metavar="ISSUE", default=None, | 554 metavar="ISSUE", default=None, |
560 help="Issue number to which to add. Defaults to new issue.") | 555 help="Issue number to which to add. Defaults to new issue.") |
561 group.add_option("--base_url", action="store", dest="base_url", default=None, | 556 group.add_option("--base_url", action="store", dest="base_url", default=None, |
562 help="Base URL path for files (listed as \"Base URL\" when " | 557 help="Base URL path for files (listed as \"Base URL\" when " |
563 "viewing issue). If omitted, will be guessed automatically " | 558 "viewing issue). If omitted, will be guessed automatically " |
564 "for SVN repos and left blank for others.") | 559 "for SVN repos and left blank for others.") |
565 group.add_option("--download_base", action="store_true", | 560 group.add_option("--download_base", action="store_true", |
566 dest="download_base", default=False, | 561 dest="download_base", default=False, |
567 help="Base files will be downloaded by the server " | 562 help="Base files will be downloaded by the server " |
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
778 class VersionControlSystem(object): | 773 class VersionControlSystem(object): |
779 """Abstract base class providing an interface to the VCS.""" | 774 """Abstract base class providing an interface to the VCS.""" |
780 | 775 |
781 def __init__(self, options): | 776 def __init__(self, options): |
782 """Constructor. | 777 """Constructor. |
783 | 778 |
784 Args: | 779 Args: |
785 options: Command line options. | 780 options: Command line options. |
786 """ | 781 """ |
787 self.options = options | 782 self.options = options |
788 | 783 |
789 def GetGUID(self): | 784 def GetGUID(self): |
790 """Return string to distinguish the repository from others, for example to | 785 """Return string to distinguish the repository from others, for example to |
791 query all opened review issues for it""" | 786 query all opened review issues for it""" |
792 raise NotImplementedError( | 787 raise NotImplementedError( |
793 "abstract method -- subclass %s must override" % self.__class__) | 788 "abstract method -- subclass %s must override" % self.__class__) |
794 | 789 |
795 def PostProcessDiff(self, diff): | 790 def PostProcessDiff(self, diff): |
796 """Return the diff with any special post processing this VCS needs, e.g. | 791 """Return the diff with any special post processing this VCS needs, e.g. |
797 to include an svn-style "Index:".""" | 792 to include an svn-style "Index:".""" |
798 return diff | 793 return diff |
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
938 self.rev_end = match.group(3) | 933 self.rev_end = match.group(3) |
939 else: | 934 else: |
940 self.rev_start = self.rev_end = None | 935 self.rev_start = self.rev_end = None |
941 # Cache output from "svn list -r REVNO dirname". | 936 # Cache output from "svn list -r REVNO dirname". |
942 # Keys: dirname, Values: 2-tuple (ouput for start rev and end rev). | 937 # Keys: dirname, Values: 2-tuple (ouput for start rev and end rev). |
943 self.svnls_cache = {} | 938 self.svnls_cache = {} |
944 # Base URL is required to fetch files deleted in an older revision. | 939 # Base URL is required to fetch files deleted in an older revision. |
945 # Result is cached to not guess it over and over again in GetBaseFile(). | 940 # Result is cached to not guess it over and over again in GetBaseFile(). |
946 required = self.options.download_base or self.options.revision is not None | 941 required = self.options.download_base or self.options.revision is not None |
947 self.svn_base = self._GuessBase(required) | 942 self.svn_base = self._GuessBase(required) |
948 | 943 |
949 def GetGUID(self): | 944 def GetGUID(self): |
950 return self._GetInfo("Repository UUID") | 945 return self._GetInfo("Repository UUID") |
951 | 946 |
952 def GuessBase(self, required): | 947 def GuessBase(self, required): |
953 """Wrapper for _GuessBase.""" | 948 """Wrapper for _GuessBase.""" |
954 return self.svn_base | 949 return self.svn_base |
955 | 950 |
956 def _GuessBase(self, required): | 951 def _GuessBase(self, required): |
957 """Returns base URL for current diff. | 952 """Returns base URL for current diff. |
958 | 953 |
(...skipping 14 matching lines...) Expand all Loading... |
973 scheme = "http" | 968 scheme = "http" |
974 guess = "Google Code " | 969 guess = "Google Code " |
975 path = path + "/" | 970 path = path + "/" |
976 base = urlparse.urlunparse((scheme, netloc, path, params, | 971 base = urlparse.urlunparse((scheme, netloc, path, params, |
977 query, fragment)) | 972 query, fragment)) |
978 logging.info("Guessed %sbase = %s", guess, base) | 973 logging.info("Guessed %sbase = %s", guess, base) |
979 return base | 974 return base |
980 if required: | 975 if required: |
981 ErrorExit("Can't find URL in output from svn info") | 976 ErrorExit("Can't find URL in output from svn info") |
982 return None | 977 return None |
983 | 978 |
984 def _GetInfo(self, key): | 979 def _GetInfo(self, key): |
985 """Parses 'svn info' for current dir. Returns value for key or None""" | 980 """Parses 'svn info' for current dir. Returns value for key or None""" |
986 for line in RunShell(["svn", "info"]).splitlines(): | 981 for line in RunShell(["svn", "info"]).splitlines(): |
987 if line.startswith(key + ": "): | 982 if line.startswith(key + ": "): |
988 return line.split(":", 1)[1].strip() | 983 return line.split(":", 1)[1].strip() |
989 | 984 |
990 def _EscapeFilename(self, filename): | 985 def _EscapeFilename(self, filename): |
991 """Escapes filename for SVN commands.""" | 986 """Escapes filename for SVN commands.""" |
992 if "@" in filename and not filename.endswith("@"): | 987 if "@" in filename and not filename.endswith("@"): |
993 filename = "%s@" % filename | 988 filename = "%s@" % filename |
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1216 class GitVCS(VersionControlSystem): | 1211 class GitVCS(VersionControlSystem): |
1217 """Implementation of the VersionControlSystem interface for Git.""" | 1212 """Implementation of the VersionControlSystem interface for Git.""" |
1218 | 1213 |
1219 def __init__(self, options): | 1214 def __init__(self, options): |
1220 super(GitVCS, self).__init__(options) | 1215 super(GitVCS, self).__init__(options) |
1221 # Map of filename -> (hash before, hash after) of base file. | 1216 # Map of filename -> (hash before, hash after) of base file. |
1222 # Hashes for "no such file" are represented as None. | 1217 # Hashes for "no such file" are represented as None. |
1223 self.hashes = {} | 1218 self.hashes = {} |
1224 # Map of new filename -> old filename for renames. | 1219 # Map of new filename -> old filename for renames. |
1225 self.renames = {} | 1220 self.renames = {} |
1226 | 1221 |
1227 def GetGUID(self): | 1222 def GetGUID(self): |
1228 revlist = RunShell("git rev-list --parents HEAD".split()).splitlines() | 1223 revlist = RunShell("git rev-list --parents HEAD".split()).splitlines() |
1229 # M-A: Return the 1st root hash, there could be multiple when a | 1224 # M-A: Return the 1st root hash, there could be multiple when a |
1230 # subtree is merged. In that case, more analysis would need to | 1225 # subtree is merged. In that case, more analysis would need to |
1231 # be done to figure out which HEAD is the 'most representative'. | 1226 # be done to figure out which HEAD is the 'most representative'. |
1232 for r in revlist: | 1227 for r in revlist: |
1233 if ' ' not in r: | 1228 if ' ' not in r: |
1234 return r | 1229 return r |
1235 | 1230 |
1236 def PostProcessDiff(self, gitdiff): | 1231 def PostProcessDiff(self, gitdiff): |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1303 # this by overriding the environment (but there is still a problem if the | 1298 # this by overriding the environment (but there is still a problem if the |
1304 # git config key "diff.external" is used). | 1299 # git config key "diff.external" is used). |
1305 env = os.environ.copy() | 1300 env = os.environ.copy() |
1306 if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF'] | 1301 if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF'] |
1307 # -M/-C will not print the diff for the deleted file when a file is renamed. | 1302 # -M/-C will not print the diff for the deleted file when a file is renamed. |
1308 # This is confusing because the original file will not be shown on the | 1303 # This is confusing because the original file will not be shown on the |
1309 # review when a file is renamed. So first get the diff of all deleted files, | 1304 # review when a file is renamed. So first get the diff of all deleted files, |
1310 # then the diff of everything except deleted files with rename and copy | 1305 # then the diff of everything except deleted files with rename and copy |
1311 # support enabled. | 1306 # support enabled. |
1312 cmd = [ | 1307 cmd = [ |
1313 "git", "diff", "--no-ext-diff", "--full-index", "--ignore-submodules" | 1308 "git", "diff", "--no-color", "--no-ext-diff", "--full-index", "--ignore-
submodules" |
1314 ] | 1309 ] |
1315 diff = RunShell(cmd + ["--diff-filter=D"] + extra_args, env=env, | 1310 diff = RunShell(cmd + ["--diff-filter=D"] + extra_args, env=env, |
1316 silent_ok=True) | 1311 silent_ok=True) |
1317 diff += RunShell(cmd + ["-C", "--diff-filter=ACMRT"] + extra_args, env=env, | 1312 diff += RunShell(cmd + ["-C", "--diff-filter=ACMRT"] + extra_args, env=env, |
1318 silent_ok=True) | 1313 silent_ok=True) |
1319 if not diff: | 1314 if not diff: |
1320 ErrorExit("No output from %s" % (cmd + extra_args)) | 1315 ErrorExit("No output from %s" % (cmd + extra_args)) |
1321 return diff | 1316 return diff |
1322 | 1317 |
1323 def GetUnknownFiles(self): | 1318 def GetUnknownFiles(self): |
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1457 | 1452 |
1458 def GetGUID(self): | 1453 def GetGUID(self): |
1459 # See chapter "Uniquely identifying a repository" | 1454 # See chapter "Uniquely identifying a repository" |
1460 # http://hgbook.red-bean.com/read/customizing-the-output-of-mercurial.html | 1455 # http://hgbook.red-bean.com/read/customizing-the-output-of-mercurial.html |
1461 info = RunShell("hg log -r0 --template {node}".split()) | 1456 info = RunShell("hg log -r0 --template {node}".split()) |
1462 return info.strip() | 1457 return info.strip() |
1463 | 1458 |
1464 def _GetRelPath(self, filename): | 1459 def _GetRelPath(self, filename): |
1465 """Get relative path of a file according to the current directory, | 1460 """Get relative path of a file according to the current directory, |
1466 given its logical path in the repo.""" | 1461 given its logical path in the repo.""" |
1467 assert filename.startswith(self.subdir), (filename, self.subdir) | 1462 absname = os.path.join(self.repo_dir, filename) |
1468 return filename[len(self.subdir):].lstrip(r"\/") | 1463 return os.path.relpath(absname) |
1469 | 1464 |
1470 def GenerateDiff(self, extra_args): | 1465 def GenerateDiff(self, extra_args): |
1471 cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args | 1466 cmd = [ |
| 1467 "hg", "diff", "--color", "never", "--git", "-r", self.base_rev |
| 1468 ] + extra_args |
1472 data = RunShell(cmd, silent_ok=True) | 1469 data = RunShell(cmd, silent_ok=True) |
1473 svndiff = [] | 1470 svndiff = [] |
1474 filecount = 0 | 1471 filecount = 0 |
1475 for line in data.splitlines(): | 1472 for line in data.splitlines(): |
1476 m = re.match("diff --git a/(\S+) b/(\S+)", line) | 1473 m = re.match("diff --git a/(\S+) b/(\S+)", line) |
1477 if m: | 1474 if m: |
1478 # Modify line to make it look like as it comes from svn diff. | 1475 # Modify line to make it look like as it comes from svn diff. |
1479 # With this modification no changes on the server side are required | 1476 # With this modification no changes on the server side are required |
1480 # to make upload.py work with Mercurial repos. | 1477 # to make upload.py work with Mercurial repos. |
1481 # NOTE: for proper handling of moved/copied files, we have to use | 1478 # NOTE: for proper handling of moved/copied files, we have to use |
1482 # the second filename. | 1479 # the second filename. |
1483 filename = m.group(2) | 1480 filename = m.group(2) |
1484 svndiff.append("Index: %s" % filename) | 1481 svndiff.append("Index: %s" % filename) |
1485 svndiff.append("=" * 67) | 1482 svndiff.append("=" * 67) |
1486 filecount += 1 | 1483 filecount += 1 |
1487 logging.info(line) | 1484 logging.info(line) |
1488 else: | 1485 else: |
1489 svndiff.append(line) | 1486 svndiff.append(line) |
1490 if not filecount: | 1487 if not filecount: |
1491 ErrorExit("No valid patches found in output from hg diff") | 1488 ErrorExit("No valid patches found in output from hg diff") |
1492 return "\n".join(svndiff) + "\n" | 1489 return "\n".join(svndiff) + "\n" |
1493 | 1490 |
1494 def GetUnknownFiles(self): | 1491 def GetUnknownFiles(self): |
1495 """Return a list of files unknown to the VCS.""" | 1492 """Return a list of files unknown to the VCS.""" |
1496 args = [] | 1493 args = [] |
1497 status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."], | 1494 status = RunShell( |
| 1495 ["hg", "status", "--color", "never", "--rev", self.base_rev, "-u", "."], |
1498 silent_ok=True) | 1496 silent_ok=True) |
1499 unknown_files = [] | 1497 unknown_files = [] |
1500 for line in status.splitlines(): | 1498 for line in status.splitlines(): |
1501 st, fn = line.split(" ", 1) | 1499 st, fn = line.split(" ", 1) |
1502 if st == "?": | 1500 if st == "?": |
1503 unknown_files.append(fn) | 1501 unknown_files.append(fn) |
1504 return unknown_files | 1502 return unknown_files |
1505 | 1503 |
1506 def GetBaseFile(self, filename): | 1504 def GetBaseFile(self, filename): |
1507 # "hg status" and "hg cat" both take a path relative to the current subdir | 1505 # "hg status" and "hg cat" both take a path relative to the current subdir, |
1508 # rather than to the repo root, but "hg diff" has given us the full path | 1506 # but "hg diff" has given us the path relative to the repo root. |
1509 # to the repo root. | |
1510 base_content = "" | 1507 base_content = "" |
1511 new_content = None | 1508 new_content = None |
1512 is_binary = False | 1509 is_binary = False |
1513 oldrelpath = relpath = self._GetRelPath(filename) | 1510 oldrelpath = relpath = self._GetRelPath(filename) |
1514 # "hg status -C" returns two lines for moved/copied files, one otherwise | 1511 # "hg status -C" returns two lines for moved/copied files, one otherwise |
1515 out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath]) | 1512 out = RunShell( |
| 1513 [ "hg", "status", "--color", "never", "-C", "--rev", self.base_rev, |
| 1514 relpath]) |
1516 out = out.splitlines() | 1515 out = out.splitlines() |
1517 # HACK: strip error message about missing file/directory if it isn't in | 1516 # HACK: strip error message about missing file/directory if it isn't in |
1518 # the working copy | 1517 # the working copy |
1519 if out[0].startswith('%s: ' % relpath): | 1518 if out[0].startswith('%s: ' % relpath): |
1520 out = out[1:] | 1519 out = out[1:] |
1521 status, _ = out[0].split(' ', 1) | 1520 status, _ = out[0].split(' ', 1) |
1522 if len(out) > 1 and status == "A": | 1521 if len(out) > 1 and status == "A": |
1523 # Moved/copied => considered as modified, use old filename to | 1522 # Moved/copied => considered as modified, use old filename to |
1524 # retrieve base contents | 1523 # retrieve base contents |
1525 oldrelpath = out[1].strip() | 1524 oldrelpath = out[1].strip() |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1568 ErrorExit("A changelist id is required") | 1567 ErrorExit("A changelist id is required") |
1569 if (options.revision): | 1568 if (options.revision): |
1570 ErrorExit("--rev is not supported for perforce") | 1569 ErrorExit("--rev is not supported for perforce") |
1571 | 1570 |
1572 self.p4_port = options.p4_port | 1571 self.p4_port = options.p4_port |
1573 self.p4_client = options.p4_client | 1572 self.p4_client = options.p4_client |
1574 self.p4_user = options.p4_user | 1573 self.p4_user = options.p4_user |
1575 | 1574 |
1576 ConfirmLogin() | 1575 ConfirmLogin() |
1577 | 1576 |
1578 if not options.message: | 1577 if not options.title: |
1579 description = self.RunPerforceCommand(["describe", self.p4_changelist], | 1578 description = self.RunPerforceCommand(["describe", self.p4_changelist], |
1580 marshal_output=True) | 1579 marshal_output=True) |
1581 if description and "desc" in description: | 1580 if description and "desc" in description: |
1582 # Rietveld doesn't support multi-line descriptions | 1581 # Rietveld doesn't support multi-line descriptions |
1583 raw_message = description["desc"].strip() | 1582 raw_title = description["desc"].strip() |
1584 lines = raw_message.splitlines() | 1583 lines = raw_title.splitlines() |
1585 if len(lines): | 1584 if len(lines): |
1586 options.message = lines[0] | 1585 options.title = lines[0] |
1587 | 1586 |
1588 def GetGUID(self): | 1587 def GetGUID(self): |
1589 """For now we don't know how to get repository ID for Perforce""" | 1588 """For now we don't know how to get repository ID for Perforce""" |
1590 return | 1589 return |
1591 | 1590 |
1592 def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False, | 1591 def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False, |
1593 universal_newlines=True): | 1592 universal_newlines=True): |
1594 args = ["p4"] | 1593 args = ["p4"] |
1595 if marshal_output: | 1594 if marshal_output: |
1596 # -G makes perforce format its output as marshalled python objects | 1595 # -G makes perforce format its output as marshalled python objects |
(...skipping 370 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1967 if errcode != errno.ENOENT: # command not found code | 1966 if errcode != errno.ENOENT: # command not found code |
1968 raise | 1967 raise |
1969 | 1968 |
1970 # Mercurial has a command to get the base directory of a repository | 1969 # Mercurial has a command to get the base directory of a repository |
1971 # Try running it, but don't die if we don't have hg installed. | 1970 # Try running it, but don't die if we don't have hg installed. |
1972 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. | 1971 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. |
1973 res = RunDetectCommand(VCS_MERCURIAL, ["hg", "root"]) | 1972 res = RunDetectCommand(VCS_MERCURIAL, ["hg", "root"]) |
1974 if res != None: | 1973 if res != None: |
1975 return res | 1974 return res |
1976 | 1975 |
1977 # Subversion has a .svn in all working directories. | 1976 # Subversion from 1.7 has a single centralized .svn folder |
1978 if os.path.isdir('.svn'): | 1977 # ( see http://subversion.apache.org/docs/release-notes/1.7.html#wc-ng ) |
1979 logging.info("Guessed VCS = Subversion") | 1978 # That's why we use 'svn info' instead of checking for .svn dir |
1980 return (VCS_SUBVERSION, None) | 1979 res = RunDetectCommand(VCS_SUBVERSION, ["svn", "info"]) |
| 1980 if res != None: |
| 1981 return res |
1981 | 1982 |
1982 # Git has a command to test if you're in a git tree. | 1983 # Git has a command to test if you're in a git tree. |
1983 # Try running it, but don't die if we don't have git installed. | 1984 # Try running it, but don't die if we don't have git installed. |
1984 res = RunDetectCommand(VCS_GIT, ["git", "rev-parse", | 1985 res = RunDetectCommand(VCS_GIT, ["git", "rev-parse", |
1985 "--is-inside-work-tree"]) | 1986 "--is-inside-work-tree"]) |
1986 if res != None: | 1987 if res != None: |
1987 return res | 1988 return res |
1988 | 1989 |
1989 # detect CVS repos use `cvs status && $? == 0` rules | 1990 # detect CVS repos use `cvs status && $? == 0` rules |
1990 res = RunDetectCommand(VCS_CVS, ["cvs", "status"]) | 1991 res = RunDetectCommand(VCS_CVS, ["cvs", "status"]) |
(...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2212 if data is None: | 2213 if data is None: |
2213 data = vcs.GenerateDiff(args) | 2214 data = vcs.GenerateDiff(args) |
2214 data = vcs.PostProcessDiff(data) | 2215 data = vcs.PostProcessDiff(data) |
2215 if options.print_diffs: | 2216 if options.print_diffs: |
2216 print "Rietveld diff start:*****" | 2217 print "Rietveld diff start:*****" |
2217 print data | 2218 print data |
2218 print "Rietveld diff end:*****" | 2219 print "Rietveld diff end:*****" |
2219 files = vcs.GetBaseFiles(data) | 2220 files = vcs.GetBaseFiles(data) |
2220 if verbosity >= 1: | 2221 if verbosity >= 1: |
2221 print "Upload server:", options.server, "(change with -s/--server)" | 2222 print "Upload server:", options.server, "(change with -s/--server)" |
2222 if options.issue: | |
2223 prompt = "Message describing this patch set: " | |
2224 else: | |
2225 prompt = "New issue subject: " | |
2226 message = options.message or raw_input(prompt).strip() | |
2227 if not message: | |
2228 ErrorExit("A non-empty message is required") | |
2229 rpc_server = GetRpcServer(options.server, | 2223 rpc_server = GetRpcServer(options.server, |
2230 options.email, | 2224 options.email, |
2231 options.host, | 2225 options.host, |
2232 options.save_cookies, | 2226 options.save_cookies, |
2233 options.account_type) | 2227 options.account_type) |
2234 form_fields = [("subject", message)] | 2228 form_fields = [] |
2235 | 2229 |
2236 repo_guid = vcs.GetGUID() | 2230 repo_guid = vcs.GetGUID() |
2237 if repo_guid: | 2231 if repo_guid: |
2238 form_fields.append(("repo_guid", repo_guid)) | 2232 form_fields.append(("repo_guid", repo_guid)) |
2239 if base: | 2233 if base: |
2240 b = urlparse.urlparse(base) | 2234 b = urlparse.urlparse(base) |
2241 username, netloc = urllib.splituser(b.netloc) | 2235 username, netloc = urllib.splituser(b.netloc) |
2242 if username: | 2236 if username: |
2243 logging.info("Removed username from base URL") | 2237 logging.info("Removed username from base URL") |
2244 base = urlparse.urlunparse((b.scheme, netloc, b.path, b.params, | 2238 base = urlparse.urlunparse((b.scheme, netloc, b.path, b.params, |
2245 b.query, b.fragment)) | 2239 b.query, b.fragment)) |
2246 form_fields.append(("base", base)) | 2240 form_fields.append(("base", base)) |
2247 if options.issue: | 2241 if options.issue: |
2248 form_fields.append(("issue", str(options.issue))) | 2242 form_fields.append(("issue", str(options.issue))) |
2249 if options.email: | 2243 if options.email: |
2250 form_fields.append(("user", options.email)) | 2244 form_fields.append(("user", options.email)) |
2251 if options.reviewers: | 2245 if options.reviewers: |
2252 for reviewer in options.reviewers.split(','): | 2246 for reviewer in options.reviewers.split(','): |
2253 CheckReviewer(reviewer) | 2247 CheckReviewer(reviewer) |
2254 form_fields.append(("reviewers", options.reviewers)) | 2248 form_fields.append(("reviewers", options.reviewers)) |
2255 if options.cc: | 2249 if options.cc: |
2256 for cc in options.cc.split(','): | 2250 for cc in options.cc.split(','): |
2257 CheckReviewer(cc) | 2251 CheckReviewer(cc) |
2258 form_fields.append(("cc", options.cc)) | 2252 form_fields.append(("cc", options.cc)) |
2259 description = options.description | 2253 |
2260 if options.description_file: | 2254 # Process --message, --title and --file. |
2261 if options.description: | 2255 message = options.message or "" |
2262 ErrorExit("Can't specify description and description_file") | 2256 title = options.title or "" |
2263 file = open(options.description_file, 'r') | 2257 if options.file: |
2264 description = file.read() | 2258 if options.message: |
| 2259 ErrorExit("Can't specify both message and message file options") |
| 2260 file = open(options.file, 'r') |
| 2261 message = file.read() |
2265 file.close() | 2262 file.close() |
2266 if description: | 2263 if options.issue: |
2267 form_fields.append(("description", description)) | 2264 prompt = "Title describing this patch set: " |
| 2265 else: |
| 2266 prompt = "New issue subject: " |
| 2267 title = ( |
| 2268 title or message.split('\n', 1)[0].strip() or raw_input(prompt).strip()) |
| 2269 if not title and not options.issue: |
| 2270 ErrorExit("A non-empty title is required for a new issue") |
| 2271 # For existing issues, it's fine to give a patchset an empty name. Rietveld |
| 2272 # doesn't accept that so use a whitespace. |
| 2273 title = title or " " |
| 2274 if len(title) > 100: |
| 2275 title = title[:99] + '…' |
| 2276 if title and not options.issue: |
| 2277 message = message or title |
| 2278 |
| 2279 form_fields.append(("subject", title)) |
| 2280 if message: |
| 2281 if not options.issue: |
| 2282 form_fields.append(("description", message)) |
| 2283 else: |
| 2284 # TODO: [ ] Use /<issue>/publish to add a comment. |
| 2285 pass |
| 2286 |
2268 # Send a hash of all the base file so the server can determine if a copy | 2287 # Send a hash of all the base file so the server can determine if a copy |
2269 # already exists in an earlier patchset. | 2288 # already exists in an earlier patchset. |
2270 base_hashes = "" | 2289 base_hashes = "" |
2271 for file, info in files.iteritems(): | 2290 for file, info in files.iteritems(): |
2272 if not info[0] is None: | 2291 if not info[0] is None: |
2273 checksum = md5(info[0]).hexdigest() | 2292 checksum = md5(info[0]).hexdigest() |
2274 if base_hashes: | 2293 if base_hashes: |
2275 base_hashes += "|" | 2294 base_hashes += "|" |
2276 base_hashes += checksum + ":" + file | 2295 base_hashes += checksum + ":" + file |
2277 form_fields.append(("base_hashes", base_hashes)) | 2296 form_fields.append(("base_hashes", base_hashes)) |
2278 if options.private: | 2297 if options.private: |
2279 if options.issue: | 2298 if options.issue: |
2280 print "Warning: Private flag ignored when updating an existing issue." | 2299 print "Warning: Private flag ignored when updating an existing issue." |
2281 else: | 2300 else: |
2282 form_fields.append(("private", "1")) | 2301 form_fields.append(("private", "1")) |
2283 if options.send_patch: | 2302 if options.send_patch: |
2284 options.send_mail = True | 2303 options.send_mail = True |
2285 # If we're uploading base files, don't send the email before the uploads, so | |
2286 # that it contains the file status. | |
2287 if options.send_mail and options.download_base: | |
2288 form_fields.append(("send_mail", "1")) | |
2289 if not options.download_base: | 2304 if not options.download_base: |
2290 form_fields.append(("content_upload", "1")) | 2305 form_fields.append(("content_upload", "1")) |
2291 if len(data) > MAX_UPLOAD_SIZE: | 2306 if len(data) > MAX_UPLOAD_SIZE: |
2292 print "Patch is large, so uploading file patches separately." | 2307 print "Patch is large, so uploading file patches separately." |
2293 uploaded_diff_file = [] | 2308 uploaded_diff_file = [] |
2294 form_fields.append(("separate_patches", "1")) | 2309 form_fields.append(("separate_patches", "1")) |
2295 else: | 2310 else: |
2296 uploaded_diff_file = [("data", "data.diff", data)] | 2311 uploaded_diff_file = [("data", "data.diff", data)] |
2297 ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file) | 2312 ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file) |
2298 response_body = rpc_server.Send("/upload", body, content_type=ctype) | 2313 response_body = rpc_server.Send("/upload", body, content_type=ctype) |
(...skipping 14 matching lines...) Expand all Loading... |
2313 sys.exit(0) | 2328 sys.exit(0) |
2314 issue = msg[msg.rfind("/")+1:] | 2329 issue = msg[msg.rfind("/")+1:] |
2315 | 2330 |
2316 if not uploaded_diff_file: | 2331 if not uploaded_diff_file: |
2317 result = UploadSeparatePatches(issue, rpc_server, patchset, data, options) | 2332 result = UploadSeparatePatches(issue, rpc_server, patchset, data, options) |
2318 if not options.download_base: | 2333 if not options.download_base: |
2319 patches = result | 2334 patches = result |
2320 | 2335 |
2321 if not options.download_base: | 2336 if not options.download_base: |
2322 vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files) | 2337 vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files) |
2323 if options.send_mail: | 2338 |
2324 payload = "" | 2339 payload = {} # payload for final request |
2325 if options.send_patch: | 2340 if options.send_mail: |
2326 payload=urllib.urlencode({"attach_patch": "yes"}) | 2341 payload["send_mail"] = "yes" |
2327 rpc_server.Send("/" + issue + "/mail", payload=payload) | 2342 if options.send_patch: |
| 2343 payload["attach_patch"] = "yes" |
| 2344 payload = urllib.urlencode(payload) |
| 2345 rpc_server.Send("/" + issue + "/upload_complete/" + (patchset or ""), |
| 2346 payload=payload) |
2328 return issue, patchset | 2347 return issue, patchset |
2329 | 2348 |
2330 | 2349 |
2331 def main(): | 2350 def main(): |
2332 try: | 2351 try: |
2333 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" | 2352 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" |
2334 "%(lineno)s %(message)s ")) | 2353 "%(lineno)s %(message)s ")) |
2335 os.environ['LC_ALL'] = 'C' | 2354 os.environ['LC_ALL'] = 'C' |
2336 RealMain(sys.argv) | 2355 RealMain(sys.argv) |
2337 except KeyboardInterrupt: | 2356 except KeyboardInterrupt: |
2338 print | 2357 print |
2339 StatusUpdate("Interrupted.") | 2358 StatusUpdate("Interrupted.") |
2340 sys.exit(1) | 2359 sys.exit(1) |
2341 | 2360 |
2342 | 2361 |
2343 if __name__ == "__main__": | 2362 if __name__ == "__main__": |
2344 main() | 2363 main() |
OLD | NEW |