OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright 2007 Google Inc. | 3 # Copyright 2007 Google Inc. |
4 # | 4 # |
5 # Licensed under the Apache License, Version 2.0 (the "License"); | 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
6 # you may not use this file except in compliance with the License. | 6 # you may not use this file except in compliance with the License. |
7 # You may obtain a copy of the License at | 7 # You may obtain a copy of the License at |
8 # | 8 # |
9 # http://www.apache.org/licenses/LICENSE-2.0 | 9 # http://www.apache.org/licenses/LICENSE-2.0 |
10 # | 10 # |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
47 import urllib2 | 47 import urllib2 |
48 import urlparse | 48 import urlparse |
49 | 49 |
50 # The md5 module was deprecated in Python 2.5. | 50 # The md5 module was deprecated in Python 2.5. |
51 try: | 51 try: |
52 from hashlib import md5 | 52 from hashlib import md5 |
53 except ImportError: | 53 except ImportError: |
54 from md5 import md5 | 54 from md5 import md5 |
55 | 55 |
56 try: | 56 try: |
57 import readline | 57 import readline # pylint: disable=W0611 |
M-A Ruel
2011/03/13 20:45:48
Don't modify this file here, you need to modify it
| |
58 except ImportError: | 58 except ImportError: |
59 pass | 59 pass |
60 | 60 |
61 try: | 61 try: |
62 import keyring | 62 import keyring # pylint: disable=F0401 |
63 except ImportError: | 63 except ImportError: |
64 keyring = None | 64 keyring = None |
65 | 65 |
66 # don't worry about methods that could be functions | |
67 # pylint: disable=R0201 | |
68 | |
66 # The logging verbosity: | 69 # The logging verbosity: |
67 # 0: Errors only. | 70 # 0: Errors only. |
68 # 1: Status messages. | 71 # 1: Status messages. |
69 # 2: Info logs. | 72 # 2: Info logs. |
70 # 3: Debug logs. | 73 # 3: Debug logs. |
71 verbosity = 1 | 74 verbosity = 1 |
72 | 75 |
73 # The account type used for authentication. | 76 # The account type used for authentication. |
74 # This line could be changed by the review server (see handler for | 77 # This line could be changed by the review server (see handler for |
75 # upload.py). | 78 # upload.py). |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
115 | 118 |
116 """ | 119 """ |
117 last_email_file_name = os.path.expanduser("~/.last_codereview_email_address") | 120 last_email_file_name = os.path.expanduser("~/.last_codereview_email_address") |
118 last_email = "" | 121 last_email = "" |
119 if os.path.exists(last_email_file_name): | 122 if os.path.exists(last_email_file_name): |
120 try: | 123 try: |
121 last_email_file = open(last_email_file_name, "r") | 124 last_email_file = open(last_email_file_name, "r") |
122 last_email = last_email_file.readline().strip("\n") | 125 last_email = last_email_file.readline().strip("\n") |
123 last_email_file.close() | 126 last_email_file.close() |
124 prompt += " [%s]" % last_email | 127 prompt += " [%s]" % last_email |
125 except IOError, e: | 128 except IOError: |
126 pass | 129 pass |
127 email = raw_input(prompt + ": ").strip() | 130 email = raw_input(prompt + ": ").strip() |
128 if email: | 131 if email: |
129 try: | 132 try: |
130 last_email_file = open(last_email_file_name, "w") | 133 last_email_file = open(last_email_file_name, "w") |
131 last_email_file.write(email) | 134 last_email_file.write(email) |
132 last_email_file.close() | 135 last_email_file.close() |
133 except IOError, e: | 136 except IOError: |
134 pass | 137 pass |
135 else: | 138 else: |
136 email = last_email | 139 email = last_email |
137 return email | 140 return email |
138 | 141 |
139 | 142 |
140 def StatusUpdate(msg): | 143 def StatusUpdate(msg): |
141 """Print a status message to stdout. | 144 """Print a status message to stdout. |
142 | 145 |
143 If 'verbosity' is greater than 0, print the message. | 146 If 'verbosity' is greater than 0, print the message. |
144 | 147 |
145 Args: | 148 Args: |
146 msg: The string to print. | 149 msg: The string to print. |
147 """ | 150 """ |
148 if verbosity > 0: | 151 if verbosity > 0: |
149 print msg | 152 print msg |
150 | 153 |
151 | 154 |
152 def ErrorExit(msg): | 155 def ErrorExit(msg): |
153 """Print an error message to stderr and exit.""" | 156 """Print an error message to stderr and exit.""" |
154 print >>sys.stderr, msg | 157 print >> sys.stderr, msg |
155 sys.exit(1) | 158 sys.exit(1) |
156 | 159 |
157 | 160 |
158 class ClientLoginError(urllib2.HTTPError): | 161 class ClientLoginError(urllib2.HTTPError): |
159 """Raised to indicate there was an error authenticating with ClientLogin.""" | 162 """Raised to indicate there was an error authenticating with ClientLogin.""" |
160 | 163 |
161 def __init__(self, url, code, msg, headers, args): | 164 def __init__(self, url, code, msg, headers, args): |
162 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) | 165 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) |
163 self.args = args | 166 self.args = args |
164 self.reason = args["Error"] | 167 self.reason = args["Error"] |
165 | 168 |
166 | 169 |
167 class AbstractRpcServer(object): | 170 class AbstractRpcServer(object): |
168 """Provides a common interface for a simple RPC server.""" | 171 """Provides a common interface for a simple RPC server.""" |
169 | 172 |
170 def __init__(self, host, auth_function, host_override=None, extra_headers={}, | 173 def __init__(self, host, auth_function, host_override=None, |
171 save_cookies=False, account_type=AUTH_ACCOUNT_TYPE): | 174 extra_headers=None, save_cookies=False, |
175 account_type=AUTH_ACCOUNT_TYPE): | |
172 """Creates a new HttpRpcServer. | 176 """Creates a new HttpRpcServer. |
173 | 177 |
174 Args: | 178 Args: |
175 host: The host to send requests to. | 179 host: The host to send requests to. |
176 auth_function: A function that takes no arguments and returns an | 180 auth_function: A function that takes no arguments and returns an |
177 (email, password) tuple when called. Will be called if authentication | 181 (email, password) tuple when called. Will be called if authentication |
178 is required. | 182 is required. |
179 host_override: The host header to send to the server (defaults to host). | 183 host_override: The host header to send to the server (defaults to host). |
180 extra_headers: A dict of extra headers to append to every request. | 184 extra_headers: A dict of extra headers to append to every request. |
181 save_cookies: If True, save the authentication cookies to local disk. | 185 save_cookies: If True, save the authentication cookies to local disk. |
182 If False, use an in-memory cookiejar instead. Subclasses must | 186 If False, use an in-memory cookiejar instead. Subclasses must |
183 implement this functionality. Defaults to False. | 187 implement this functionality. Defaults to False. |
184 account_type: Account type used for authentication. Defaults to | 188 account_type: Account type used for authentication. Defaults to |
185 AUTH_ACCOUNT_TYPE. | 189 AUTH_ACCOUNT_TYPE. |
186 """ | 190 """ |
191 extra_headers = extra_headers or {} | |
187 self.host = host | 192 self.host = host |
188 if (not self.host.startswith("http://") and | 193 if (not self.host.startswith("http://") and |
189 not self.host.startswith("https://")): | 194 not self.host.startswith("https://")): |
190 self.host = "http://" + self.host | 195 self.host = "http://" + self.host |
191 assert re.match(r'^[a-z]+://[a-z0-9\.-_]+(|:[0-9]+)$', self.host), ( | 196 assert re.match(r'^[a-z]+://[a-z0-9\.-_]+(|:[0-9]+)$', self.host), ( |
192 '%s is malformed' % host) | 197 '%s is malformed' % host) |
193 self.host_override = host_override | 198 self.host_override = host_override |
194 self.auth_function = auth_function | 199 self.auth_function = auth_function |
195 self.authenticated = False | 200 self.authenticated = False |
196 self.extra_headers = extra_headers | 201 self.extra_headers = extra_headers |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
310 2) We use ClientLogin to obtain an AUTH token for the user | 315 2) We use ClientLogin to obtain an AUTH token for the user |
311 (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). | 316 (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). |
312 3) We pass the auth token to /_ah/login on the server to obtain an | 317 3) We pass the auth token to /_ah/login on the server to obtain an |
313 authentication cookie. If login was successful, it tries to redirect | 318 authentication cookie. If login was successful, it tries to redirect |
314 us to the URL we provided. | 319 us to the URL we provided. |
315 | 320 |
316 If we attempt to access the upload API without first obtaining an | 321 If we attempt to access the upload API without first obtaining an |
317 authentication cookie, it returns a 401 response (or a 302) and | 322 authentication cookie, it returns a 401 response (or a 302) and |
318 directs us to authenticate ourselves with ClientLogin. | 323 directs us to authenticate ourselves with ClientLogin. |
319 """ | 324 """ |
320 for i in range(3): | 325 for i in range(3): # pylint: disable=W0612 |
321 credentials = self.auth_function() | 326 credentials = self.auth_function() |
322 try: | 327 try: |
323 auth_token = self._GetAuthToken(host, credentials[0], credentials[1]) | 328 auth_token = self._GetAuthToken(host, credentials[0], credentials[1]) |
324 except ClientLoginError, e: | 329 except ClientLoginError, e: |
325 if e.reason == "BadAuthentication": | 330 if e.reason == "BadAuthentication": |
326 print >>sys.stderr, "Invalid username or password." | 331 print >> sys.stderr, "Invalid username or password." |
327 continue | 332 continue |
328 if e.reason == "CaptchaRequired": | 333 if e.reason == "CaptchaRequired": |
329 print >>sys.stderr, ( | 334 print >> sys.stderr, ( |
330 "Please go to\n" | 335 "Please go to\n" |
331 "https://www.google.com/accounts/DisplayUnlockCaptcha\n" | 336 "https://www.google.com/accounts/DisplayUnlockCaptcha\n" |
332 "and verify you are a human. Then try again.\n" | 337 "and verify you are a human. Then try again.\n" |
333 "If you are using a Google Apps account the URL is:\n" | 338 "If you are using a Google Apps account the URL is:\n" |
334 "https://www.google.com/a/yourdomain.com/UnlockCaptcha") | 339 "https://www.google.com/a/yourdomain.com/UnlockCaptcha") |
335 break | 340 break |
336 if e.reason == "NotVerified": | 341 if e.reason == "NotVerified": |
337 print >>sys.stderr, "Account not verified." | 342 print >> sys.stderr, "Account not verified." |
338 break | 343 break |
339 if e.reason == "TermsNotAgreed": | 344 if e.reason == "TermsNotAgreed": |
340 print >>sys.stderr, "User has not agreed to TOS." | 345 print >> sys.stderr, "User has not agreed to TOS." |
341 break | 346 break |
342 if e.reason == "AccountDeleted": | 347 if e.reason == "AccountDeleted": |
343 print >>sys.stderr, "The user account has been deleted." | 348 print >> sys.stderr, "The user account has been deleted." |
344 break | 349 break |
345 if e.reason == "AccountDisabled": | 350 if e.reason == "AccountDisabled": |
346 print >>sys.stderr, "The user account has been disabled." | 351 print >> sys.stderr, "The user account has been disabled." |
347 break | 352 break |
348 if e.reason == "ServiceDisabled": | 353 if e.reason == "ServiceDisabled": |
349 print >>sys.stderr, ("The user's access to the service has been " | 354 print >> sys.stderr, ("The user's access to the service has been " |
350 "disabled.") | 355 "disabled.") |
351 break | 356 break |
352 if e.reason == "ServiceUnavailable": | 357 if e.reason == "ServiceUnavailable": |
353 print >>sys.stderr, "The service is not available; try again later." | 358 print >> sys.stderr, "The service is not available; try again later." |
354 break | 359 break |
355 raise | 360 raise |
356 self._GetAuthCookie(host, auth_token) | 361 self._GetAuthCookie(host, auth_token) |
357 return | 362 return |
358 | 363 |
359 def Send(self, request_path, payload=None, | 364 def Send(self, request_path, payload=None, |
360 content_type="application/octet-stream", | 365 content_type="application/octet-stream", |
361 timeout=None, | 366 timeout=None, |
362 extra_headers=None, | 367 extra_headers=None, |
363 **kwargs): | 368 **kwargs): |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
444 Returns: | 449 Returns: |
445 A urllib2.OpenerDirector object. | 450 A urllib2.OpenerDirector object. |
446 """ | 451 """ |
447 opener = urllib2.OpenerDirector() | 452 opener = urllib2.OpenerDirector() |
448 opener.add_handler(urllib2.ProxyHandler()) | 453 opener.add_handler(urllib2.ProxyHandler()) |
449 opener.add_handler(urllib2.UnknownHandler()) | 454 opener.add_handler(urllib2.UnknownHandler()) |
450 opener.add_handler(urllib2.HTTPHandler()) | 455 opener.add_handler(urllib2.HTTPHandler()) |
451 opener.add_handler(urllib2.HTTPDefaultErrorHandler()) | 456 opener.add_handler(urllib2.HTTPDefaultErrorHandler()) |
452 opener.add_handler(urllib2.HTTPSHandler()) | 457 opener.add_handler(urllib2.HTTPSHandler()) |
453 opener.add_handler(urllib2.HTTPErrorProcessor()) | 458 opener.add_handler(urllib2.HTTPErrorProcessor()) |
459 # pylint: disable=W0201 | |
454 if self.save_cookies: | 460 if self.save_cookies: |
455 self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies") | 461 self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies") |
456 self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file) | 462 self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file) |
457 if os.path.exists(self.cookie_file): | 463 if os.path.exists(self.cookie_file): |
458 try: | 464 try: |
459 self.cookie_jar.load() | 465 self.cookie_jar.load() |
460 self.authenticated = True | 466 self.authenticated = True |
461 StatusUpdate("Loaded authentication cookies from %s" % | 467 StatusUpdate("Loaded authentication cookies from %s" % |
462 self.cookie_file) | 468 self.cookie_file) |
463 except (cookielib.LoadError, IOError): | 469 except (cookielib.LoadError, IOError): |
(...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
668 | 674 |
669 def GetContentType(filename): | 675 def GetContentType(filename): |
670 """Helper to guess the content-type from the filename.""" | 676 """Helper to guess the content-type from the filename.""" |
671 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' | 677 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' |
672 | 678 |
673 | 679 |
674 # Use a shell for subcommands on Windows to get a PATH search. | 680 # Use a shell for subcommands on Windows to get a PATH search. |
675 use_shell = sys.platform.startswith("win") | 681 use_shell = sys.platform.startswith("win") |
676 | 682 |
677 def RunShellWithReturnCode(command, print_output=False, | 683 def RunShellWithReturnCode(command, print_output=False, |
678 universal_newlines=True, | 684 universal_newlines=True, env=None): |
679 env=os.environ): | |
680 """Executes a command and returns the output from stdout and the return code. | 685 """Executes a command and returns the output from stdout and the return code. |
681 | 686 |
682 Args: | 687 Args: |
683 command: Command to execute. | 688 command: Command to execute. |
684 print_output: If True, the output is printed to stdout. | 689 print_output: If True, the output is printed to stdout. |
685 If False, both stdout and stderr are ignored. | 690 If False, both stdout and stderr are ignored. |
686 universal_newlines: Use universal_newlines flag (default: True). | 691 universal_newlines: Use universal_newlines flag (default: True). |
687 | 692 |
688 Returns: | 693 Returns: |
689 Tuple (output, return code) | 694 Tuple (output, return code) |
690 """ | 695 """ |
691 logging.info("Running %s", command) | 696 logging.info("Running %s", command) |
692 p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | 697 p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
693 shell=use_shell, universal_newlines=universal_newlines, | 698 shell=use_shell, universal_newlines=universal_newlines, |
694 env=env) | 699 env=env) |
695 if print_output: | 700 if print_output: |
696 output_array = [] | 701 output_array = [] |
697 while True: | 702 while True: |
698 line = p.stdout.readline() | 703 line = p.stdout.readline() |
699 if not line: | 704 if not line: |
700 break | 705 break |
701 print line.strip("\n") | 706 print line.strip("\n") |
702 output_array.append(line) | 707 output_array.append(line) |
703 output = "".join(output_array) | 708 output = "".join(output_array) |
704 else: | 709 else: |
705 output = p.stdout.read() | 710 output = p.stdout.read() |
706 p.wait() | 711 p.wait() |
707 errout = p.stderr.read() | 712 errout = p.stderr.read() |
708 if print_output and errout: | 713 if print_output and errout: |
709 print >>sys.stderr, errout | 714 print >> sys.stderr, errout |
710 p.stdout.close() | 715 p.stdout.close() |
711 p.stderr.close() | 716 p.stderr.close() |
712 return output, p.returncode | 717 return output, p.returncode |
713 | 718 |
714 | 719 |
715 def RunShell(command, silent_ok=False, universal_newlines=True, | 720 def RunShell(command, silent_ok=False, universal_newlines=True, |
716 print_output=False, env=os.environ): | 721 print_output=False, env=None): |
717 data, retcode = RunShellWithReturnCode(command, print_output, | 722 data, retcode = RunShellWithReturnCode(command, print_output, |
718 universal_newlines, env) | 723 universal_newlines, env=env) |
719 if retcode: | 724 if retcode: |
720 ErrorExit("Got error status from %s:\n%s" % (command, data)) | 725 ErrorExit("Got error status from %s:\n%s" % (command, data)) |
721 if not silent_ok and not data: | 726 if not silent_ok and not data: |
722 ErrorExit("No output from %s" % command) | 727 ErrorExit("No output from %s" % command) |
723 return data | 728 return data |
724 | 729 |
725 | 730 |
726 class VersionControlSystem(object): | 731 class VersionControlSystem(object): |
727 """Abstract base class providing an interface to the VCS.""" | 732 """Abstract base class providing an interface to the VCS.""" |
728 | 733 |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
786 """Helper that calls GetBase file for each file in the patch. | 791 """Helper that calls GetBase file for each file in the patch. |
787 | 792 |
788 Returns: | 793 Returns: |
789 A dictionary that maps from filename to GetBaseFile's tuple. Filenames | 794 A dictionary that maps from filename to GetBaseFile's tuple. Filenames |
790 are retrieved based on lines that start with "Index:" or | 795 are retrieved based on lines that start with "Index:" or |
791 "Property changes on:". | 796 "Property changes on:". |
792 """ | 797 """ |
793 files = {} | 798 files = {} |
794 for line in diff.splitlines(True): | 799 for line in diff.splitlines(True): |
795 if line.startswith('Index:') or line.startswith('Property changes on:'): | 800 if line.startswith('Index:') or line.startswith('Property changes on:'): |
796 unused, filename = line.split(':', 1) | 801 _, filename = line.split(':', 1) |
797 # On Windows if a file has property changes its filename uses '\' | 802 # On Windows if a file has property changes its filename uses '\' |
798 # instead of '/'. | 803 # instead of '/'. |
799 filename = filename.strip().replace('\\', '/') | 804 filename = filename.strip().replace('\\', '/') |
800 files[filename] = self.GetBaseFile(filename) | 805 files[filename] = self.GetBaseFile(filename) |
801 return files | 806 return files |
802 | 807 |
803 | 808 |
804 def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options, | 809 def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options, |
805 files): | 810 files): |
806 """Uploads the base files (and if necessary, the current ones as well).""" | 811 """Uploads the base files (and if necessary, the current ones as well).""" |
807 | 812 |
808 def UploadFile(filename, file_id, content, is_binary, status, is_base): | 813 def UploadFile(filename, file_id, content, is_binary, status, is_base): |
809 """Uploads a file to the server.""" | 814 """Uploads a file to the server.""" |
810 file_too_large = False | 815 file_too_large = False |
811 if is_base: | 816 if is_base: |
812 type = "base" | 817 file_type = "base" |
813 else: | 818 else: |
814 type = "current" | 819 file_type = "current" |
815 if len(content) > MAX_UPLOAD_SIZE: | 820 if len(content) > MAX_UPLOAD_SIZE: |
816 print ("Not uploading the %s file for %s because it's too large." % | 821 print ("Not uploading the %s file for %s because it's too large." % |
817 (type, filename)) | 822 (type, filename)) |
818 file_too_large = True | 823 file_too_large = True |
819 content = "" | 824 content = "" |
820 checksum = md5(content).hexdigest() | 825 checksum = md5(content).hexdigest() |
821 if options.verbose > 0 and not file_too_large: | 826 if options.verbose > 0 and not file_too_large: |
822 print "Uploading %s file for %s" % (type, filename) | 827 print "Uploading %s file for %s" % (file_type, filename) |
823 url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id) | 828 url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id) |
824 form_fields = [("filename", filename), | 829 form_fields = [("filename", filename), |
825 ("status", status), | 830 ("status", status), |
826 ("checksum", checksum), | 831 ("checksum", checksum), |
827 ("is_binary", str(is_binary)), | 832 ("is_binary", str(is_binary)), |
828 ("is_current", str(not is_base)), | 833 ("is_current", str(not is_base)), |
829 ] | 834 ] |
830 if file_too_large: | 835 if file_too_large: |
831 form_fields.append(("file_too_large", "1")) | 836 form_fields.append(("file_too_large", "1")) |
832 if options.email: | 837 if options.email: |
833 form_fields.append(("user", options.email)) | 838 form_fields.append(("user", options.email)) |
834 ctype, body = EncodeMultipartFormData(form_fields, | 839 ctype, body = EncodeMultipartFormData(form_fields, |
835 [("data", filename, content)]) | 840 [("data", filename, content)]) |
836 response_body = rpc_server.Send(url, body, | 841 response_body = rpc_server.Send(url, body, |
837 content_type=ctype) | 842 content_type=ctype) |
838 if not response_body.startswith("OK"): | 843 if not response_body.startswith("OK"): |
839 StatusUpdate(" --> %s" % response_body) | 844 StatusUpdate(" --> %s" % response_body) |
840 sys.exit(1) | 845 sys.exit(1) |
841 | 846 |
842 patches = dict() | 847 patches = dict() |
843 [patches.setdefault(v, k) for k, v in patch_list] | 848 for k, v in patch_list: |
849 patches.setdefault(v, k) | |
844 for filename in patches.keys(): | 850 for filename in patches.keys(): |
845 base_content, new_content, is_binary, status = files[filename] | 851 base_content, new_content, is_binary, status = files[filename] |
846 file_id_str = patches.get(filename) | 852 file_id_str = patches.get(filename) |
847 if file_id_str.find("nobase") != -1: | 853 if file_id_str.find("nobase") != -1: |
848 base_content = None | 854 base_content = None |
849 file_id_str = file_id_str[file_id_str.rfind("_") + 1:] | 855 file_id_str = file_id_str[file_id_str.rfind("_") + 1:] |
850 file_id = int(file_id_str) | 856 file_id = int(file_id_str) |
851 if base_content != None: | 857 if base_content != None: |
852 UploadFile(filename, file_id, base_content, is_binary, status, True) | 858 UploadFile(filename, file_id, base_content, is_binary, status, True) |
853 if new_content != None: | 859 if new_content != None: |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
959 'Id': ['Id'], | 965 'Id': ['Id'], |
960 | 966 |
961 # Aliases | 967 # Aliases |
962 'LastChangedDate': ['LastChangedDate', 'Date'], | 968 'LastChangedDate': ['LastChangedDate', 'Date'], |
963 'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'], | 969 'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'], |
964 'LastChangedBy': ['LastChangedBy', 'Author'], | 970 'LastChangedBy': ['LastChangedBy', 'Author'], |
965 'URL': ['URL', 'HeadURL'], | 971 'URL': ['URL', 'HeadURL'], |
966 } | 972 } |
967 | 973 |
968 def repl(m): | 974 def repl(m): |
969 if m.group(2): | 975 if m.group(2): |
970 return "$%s::%s$" % (m.group(1), " " * len(m.group(3))) | 976 return "$%s::%s$" % (m.group(1), " " * len(m.group(3))) |
971 return "$%s$" % m.group(1) | 977 return "$%s$" % m.group(1) |
972 keywords = [keyword | 978 keywords = [keyword |
973 for name in keyword_str.split(" ") | 979 for name in keyword_str.split(" ") |
974 for keyword in svn_keywords.get(name, [])] | 980 for keyword in svn_keywords.get(name, [])] |
975 return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) | 981 return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) |
976 | 982 |
977 def GetUnknownFiles(self): | 983 def GetUnknownFiles(self): |
978 status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) | 984 status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) |
979 unknown_files = [] | 985 unknown_files = [] |
980 for line in status.split("\n"): | 986 for line in status.split("\n"): |
981 if line and line[0] == "?": | 987 if line and line[0] == "?": |
982 unknown_files.append(line) | 988 unknown_files.append(line) |
983 return unknown_files | 989 return unknown_files |
984 | 990 |
985 def ReadFile(self, filename): | 991 def ReadFile(self, filename): |
986 """Returns the contents of a file.""" | 992 """Returns the contents of a file.""" |
987 file = open(filename, 'rb') | 993 f = open(filename, 'rb') |
988 result = "" | 994 result = "" |
989 try: | 995 try: |
990 result = file.read() | 996 result = f.read() |
991 finally: | 997 finally: |
992 file.close() | 998 f.close() |
993 return result | 999 return result |
994 | 1000 |
995 def GetStatus(self, filename): | 1001 def GetStatus(self, filename): |
996 """Returns the status of a file.""" | 1002 """Returns the status of a file.""" |
997 if not self.options.revision: | 1003 if not self.options.revision: |
998 status = RunShell(["svn", "status", "--ignore-externals", filename]) | 1004 status = RunShell(["svn", "status", "--ignore-externals", filename]) |
999 if not status: | 1005 if not status: |
1000 ErrorExit("svn status returned no output for %s" % filename) | 1006 ErrorExit("svn status returned no output for %s" % filename) |
1001 status_lines = status.splitlines() | 1007 status_lines = status.splitlines() |
1002 # If file is in a cl, the output will begin with | 1008 # If file is in a cl, the output will begin with |
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1205 if self.options.revision: | 1211 if self.options.revision: |
1206 if ":" in self.options.revision: | 1212 if ":" in self.options.revision: |
1207 extra_args = self.options.revision.split(":", 1) + extra_args | 1213 extra_args = self.options.revision.split(":", 1) + extra_args |
1208 else: | 1214 else: |
1209 extra_args = [self.options.revision] + extra_args | 1215 extra_args = [self.options.revision] + extra_args |
1210 | 1216 |
1211 # --no-ext-diff is broken in some versions of Git, so try to work around | 1217 # --no-ext-diff is broken in some versions of Git, so try to work around |
1212 # this by overriding the environment (but there is still a problem if the | 1218 # this by overriding the environment (but there is still a problem if the |
1213 # git config key "diff.external" is used). | 1219 # git config key "diff.external" is used). |
1214 env = os.environ.copy() | 1220 env = os.environ.copy() |
1215 if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF'] | 1221 if 'GIT_EXTERNAL_DIFF' in env: |
1222 del env['GIT_EXTERNAL_DIFF'] | |
1216 return RunShell(["git", "diff", "--no-ext-diff", "--full-index", "-M"] | 1223 return RunShell(["git", "diff", "--no-ext-diff", "--full-index", "-M"] |
1217 + extra_args, env=env) | 1224 + extra_args, env=env) |
1218 | 1225 |
1219 def GetUnknownFiles(self): | 1226 def GetUnknownFiles(self): |
1220 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], | 1227 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], |
1221 silent_ok=True) | 1228 silent_ok=True) |
1222 return status.splitlines() | 1229 return status.splitlines() |
1223 | 1230 |
1224 def GetFileContent(self, file_hash, is_binary): | 1231 def GetFileContent(self, file_hash, is_binary): |
1225 """Returns the content of a file identified by its git hash.""" | 1232 """Returns the content of a file identified by its git hash.""" |
1226 data, retcode = RunShellWithReturnCode(["git", "show", file_hash], | 1233 data, retcode = RunShellWithReturnCode(["git", "show", file_hash], |
1227 universal_newlines=not is_binary) | 1234 universal_newlines=not is_binary) |
1228 if retcode: | 1235 if retcode: |
1229 ErrorExit("Got error status from 'git show %s'" % file_hash) | 1236 ErrorExit("Got error status from 'git show %s'" % file_hash) |
1230 return data | 1237 return data |
1231 | 1238 |
1232 def GetBaseFile(self, filename): | 1239 def GetBaseFile(self, filename): |
1233 hash_before, hash_after = self.hashes.get(filename, (None,None)) | 1240 hash_before, hash_after = self.hashes.get(filename, (None, None)) |
1234 base_content = None | 1241 base_content = None |
1235 new_content = None | 1242 new_content = None |
1236 is_binary = self.IsBinary(filename) | 1243 is_binary = self.IsBinary(filename) |
1237 status = None | 1244 status = None |
1238 | 1245 |
1239 if filename in self.renames: | 1246 if filename in self.renames: |
1240 status = "A +" # Match svn attribute name for renames. | 1247 status = "A +" # Match svn attribute name for renames. |
1241 if filename not in self.hashes: | 1248 if filename not in self.hashes: |
1242 # If a rename doesn't change the content, we never get a hash. | 1249 # If a rename doesn't change the content, we never get a hash. |
1243 base_content = RunShell(["git", "show", "HEAD:" + filename]) | 1250 base_content = RunShell(["git", "show", "HEAD:" + filename]) |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1306 filecount += 1 | 1313 filecount += 1 |
1307 logging.info(line) | 1314 logging.info(line) |
1308 else: | 1315 else: |
1309 svndiff.append(line) | 1316 svndiff.append(line) |
1310 if not filecount: | 1317 if not filecount: |
1311 ErrorExit("No valid patches found in output from hg diff") | 1318 ErrorExit("No valid patches found in output from hg diff") |
1312 return "\n".join(svndiff) + "\n" | 1319 return "\n".join(svndiff) + "\n" |
1313 | 1320 |
1314 def GetUnknownFiles(self): | 1321 def GetUnknownFiles(self): |
1315 """Return a list of files unknown to the VCS.""" | 1322 """Return a list of files unknown to the VCS.""" |
1316 args = [] | |
1317 status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."], | 1323 status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."], |
1318 silent_ok=True) | 1324 silent_ok=True) |
1319 unknown_files = [] | 1325 unknown_files = [] |
1320 for line in status.splitlines(): | 1326 for line in status.splitlines(): |
1321 st, fn = line.split(" ", 1) | 1327 st, fn = line.split(" ", 1) |
1322 if st == "?": | 1328 if st == "?": |
1323 unknown_files.append(fn) | 1329 unknown_files.append(fn) |
1324 return unknown_files | 1330 return unknown_files |
1325 | 1331 |
1326 def GetBaseFile(self, filename): | 1332 def GetBaseFile(self, filename): |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1374 Returns: | 1380 Returns: |
1375 A list of 2-tuple (filename, text) where text is the svn diff output | 1381 A list of 2-tuple (filename, text) where text is the svn diff output |
1376 pertaining to filename. | 1382 pertaining to filename. |
1377 """ | 1383 """ |
1378 patches = [] | 1384 patches = [] |
1379 filename = None | 1385 filename = None |
1380 diff = [] | 1386 diff = [] |
1381 for line in data.splitlines(True): | 1387 for line in data.splitlines(True): |
1382 new_filename = None | 1388 new_filename = None |
1383 if line.startswith('Index:'): | 1389 if line.startswith('Index:'): |
1384 unused, new_filename = line.split(':', 1) | 1390 _, new_filename = line.split(':', 1) |
1385 new_filename = new_filename.strip() | 1391 new_filename = new_filename.strip() |
1386 elif line.startswith('Property changes on:'): | 1392 elif line.startswith('Property changes on:'): |
1387 unused, temp_filename = line.split(':', 1) | 1393 _, temp_filename = line.split(':', 1) |
1388 # When a file is modified, paths use '/' between directories, however | 1394 # When a file is modified, paths use '/' between directories, however |
1389 # when a property is modified '\' is used on Windows. Make them the same | 1395 # when a property is modified '\' is used on Windows. Make them the same |
1390 # otherwise the file shows up twice. | 1396 # otherwise the file shows up twice. |
1391 temp_filename = temp_filename.strip().replace('\\', '/') | 1397 temp_filename = temp_filename.strip().replace('\\', '/') |
1392 if temp_filename != filename: | 1398 if temp_filename != filename: |
1393 # File has property changes but no modifications, create a new diff. | 1399 # File has property changes but no modifications, create a new diff. |
1394 new_filename = temp_filename | 1400 new_filename = temp_filename |
1395 if new_filename: | 1401 if new_filename: |
1396 if filename and diff: | 1402 if filename and diff: |
1397 patches.append((filename, ''.join(diff))) | 1403 patches.append((filename, ''.join(diff))) |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1445 output is a string containing any interesting output from the vcs | 1451 output is a string containing any interesting output from the vcs |
1446 detection routine, or None if there is nothing interesting. | 1452 detection routine, or None if there is nothing interesting. |
1447 """ | 1453 """ |
1448 # Mercurial has a command to get the base directory of a repository | 1454 # Mercurial has a command to get the base directory of a repository |
1449 # Try running it, but don't die if we don't have hg installed. | 1455 # Try running it, but don't die if we don't have hg installed. |
1450 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. | 1456 # NOTE: we try Mercurial first as it can sit on top of an SVN working copy. |
1451 try: | 1457 try: |
1452 out, returncode = RunShellWithReturnCode(["hg", "root"]) | 1458 out, returncode = RunShellWithReturnCode(["hg", "root"]) |
1453 if returncode == 0: | 1459 if returncode == 0: |
1454 return (VCS_MERCURIAL, out.strip()) | 1460 return (VCS_MERCURIAL, out.strip()) |
1455 except OSError, (errno, message): | 1461 except OSError, e: |
1456 if errno != 2: # ENOENT -- they don't have hg installed. | 1462 if e.errno != 2: # ENOENT -- they don't have hg installed. |
1457 raise | 1463 raise |
1458 | 1464 |
1459 # Subversion has a .svn in all working directories. | 1465 # Subversion has a .svn in all working directories. |
1460 if os.path.isdir('.svn'): | 1466 if os.path.isdir('.svn'): |
1461 logging.info("Guessed VCS = Subversion") | 1467 logging.info("Guessed VCS = Subversion") |
1462 return (VCS_SUBVERSION, None) | 1468 return (VCS_SUBVERSION, None) |
1463 | 1469 |
1464 # Git has a command to test if you're in a git tree. | 1470 # Git has a command to test if you're in a git tree. |
1465 # Try running it, but don't die if we don't have git installed. | 1471 # Try running it, but don't die if we don't have git installed. |
1466 try: | 1472 try: |
1467 out, returncode = RunShellWithReturnCode(["git", "rev-parse", | 1473 out, returncode = RunShellWithReturnCode(["git", "rev-parse", |
1468 "--is-inside-work-tree"]) | 1474 "--is-inside-work-tree"]) |
1469 if returncode == 0: | 1475 if returncode == 0: |
1470 return (VCS_GIT, None) | 1476 return (VCS_GIT, None) |
1471 except OSError, (errno, message): | 1477 except OSError, e: |
1472 if errno != 2: # ENOENT -- they don't have git installed. | 1478 if e.errno != 2: # ENOENT -- they don't have git installed. |
1473 raise | 1479 raise |
1474 | 1480 |
1475 return (VCS_UNKNOWN, None) | 1481 return (VCS_UNKNOWN, None) |
1476 | 1482 |
1477 | 1483 |
1478 def GuessVCS(options): | 1484 def GuessVCS(options): |
1479 """Helper to guess the version control system. | 1485 """Helper to guess the version control system. |
1480 | 1486 |
1481 This verifies any user-specified VersionControlSystem (by command line | 1487 This verifies any user-specified VersionControlSystem (by command line |
1482 or environment variable). If the user didn't specify one, this examines | 1488 or environment variable). If the user didn't specify one, this examines |
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1711 CheckReviewer(reviewer) | 1717 CheckReviewer(reviewer) |
1712 form_fields.append(("reviewers", options.reviewers)) | 1718 form_fields.append(("reviewers", options.reviewers)) |
1713 if options.cc: | 1719 if options.cc: |
1714 for cc in options.cc.split(','): | 1720 for cc in options.cc.split(','): |
1715 CheckReviewer(cc) | 1721 CheckReviewer(cc) |
1716 form_fields.append(("cc", options.cc)) | 1722 form_fields.append(("cc", options.cc)) |
1717 description = options.description | 1723 description = options.description |
1718 if options.description_file: | 1724 if options.description_file: |
1719 if options.description: | 1725 if options.description: |
1720 ErrorExit("Can't specify description and description_file") | 1726 ErrorExit("Can't specify description and description_file") |
1721 file = open(options.description_file, 'r') | 1727 f = open(options.description_file, 'r') |
1722 description = file.read() | 1728 description = f.read() |
1723 file.close() | 1729 f.close() |
1724 if description: | 1730 if description: |
1725 form_fields.append(("description", description)) | 1731 form_fields.append(("description", description)) |
1726 # Send a hash of all the base file so the server can determine if a copy | 1732 # Send a hash of all the base file so the server can determine if a copy |
1727 # already exists in an earlier patchset. | 1733 # already exists in an earlier patchset. |
1728 base_hashes = "" | 1734 base_hashes = "" |
1729 for file, info in files.iteritems(): | 1735 for filename, info in files.iteritems(): |
1730 if not info[0] is None: | 1736 if not info[0] is None: |
1731 checksum = md5(info[0]).hexdigest() | 1737 checksum = md5(info[0]).hexdigest() |
1732 if base_hashes: | 1738 if base_hashes: |
1733 base_hashes += "|" | 1739 base_hashes += "|" |
1734 base_hashes += checksum + ":" + file | 1740 base_hashes += checksum + ":" + filename |
1735 form_fields.append(("base_hashes", base_hashes)) | 1741 form_fields.append(("base_hashes", base_hashes)) |
1736 if options.private: | 1742 if options.private: |
1737 if options.issue: | 1743 if options.issue: |
1738 print "Warning: Private flag ignored when updating an existing issue." | 1744 print "Warning: Private flag ignored when updating an existing issue." |
1739 else: | 1745 else: |
1740 form_fields.append(("private", "1")) | 1746 form_fields.append(("private", "1")) |
1741 # If we're uploading base files, don't send the email before the uploads, so | 1747 # If we're uploading base files, don't send the email before the uploads, so |
1742 # that it contains the file status. | 1748 # that it contains the file status. |
1743 if options.send_mail and options.download_base: | 1749 if options.send_mail and options.download_base: |
1744 form_fields.append(("send_mail", "1")) | 1750 form_fields.append(("send_mail", "1")) |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1785 try: | 1791 try: |
1786 RealMain(sys.argv) | 1792 RealMain(sys.argv) |
1787 except KeyboardInterrupt: | 1793 except KeyboardInterrupt: |
1788 print | 1794 print |
1789 StatusUpdate("Interrupted.") | 1795 StatusUpdate("Interrupted.") |
1790 sys.exit(1) | 1796 sys.exit(1) |
1791 | 1797 |
1792 | 1798 |
1793 if __name__ == "__main__": | 1799 if __name__ == "__main__": |
1794 main() | 1800 main() |
OLD | NEW |