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 481 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
492 | 492 |
493 def GetContentType(filename): | 493 def GetContentType(filename): |
494 """Helper to guess the content-type from the filename.""" | 494 """Helper to guess the content-type from the filename.""" |
495 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' | 495 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' |
496 | 496 |
497 | 497 |
498 # Use a shell for subcommands on Windows to get a PATH search. | 498 # Use a shell for subcommands on Windows to get a PATH search. |
499 use_shell = sys.platform.startswith("win") | 499 use_shell = sys.platform.startswith("win") |
500 | 500 |
501 | 501 |
502 def RunShell(command, silent_ok=False): | 502 def RunShell(command, silent_ok=False, universal_newlines=True): |
503 logging.info("Running %s", command) | 503 logging.info("Running %s", command) |
504 p = subprocess.Popen(command, stdout=subprocess.PIPE, | 504 p = subprocess.Popen(command, stdout=subprocess.PIPE, |
505 stderr=subprocess.STDOUT, shell=use_shell, | 505 stderr=subprocess.STDOUT, shell=use_shell, |
506 universal_newlines=True) | 506 universal_newlines=universal_newlines) |
507 data = p.stdout.read() | 507 data = p.stdout.read() |
508 p.wait() | 508 p.wait() |
509 p.stdout.close() | 509 p.stdout.close() |
510 if p.returncode: | 510 if p.returncode: |
511 ErrorExit("Got error status from %s" % command) | 511 ErrorExit("Got error status from %s" % command) |
512 if not silent_ok and not data: | 512 if not silent_ok and not data: |
513 ErrorExit("No output from %s" % command) | 513 ErrorExit("No output from %s" % command) |
514 return data | 514 return data |
515 | 515 |
516 | 516 |
(...skipping 23 matching lines...) Expand all Loading... |
540 print line | 540 print line |
541 prompt = "Are you sure to continue?(y/N) " | 541 prompt = "Are you sure to continue?(y/N) " |
542 answer = raw_input(prompt).strip() | 542 answer = raw_input(prompt).strip() |
543 if answer != "y": | 543 if answer != "y": |
544 ErrorExit("User aborted") | 544 ErrorExit("User aborted") |
545 | 545 |
546 def GetBaseFile(self, filename): | 546 def GetBaseFile(self, filename): |
547 """Get the content of the upstream version of a file. | 547 """Get the content of the upstream version of a file. |
548 | 548 |
549 Returns: | 549 Returns: |
550 A tuple (content, status) representing the file content and the status of | 550 A tuple (base_content, new_content, is_binary, status) |
551 the file. | 551 base_content: The contents of the base file. |
| 552 new_content: For text files, this is empty. For binary files, this is |
| 553 the contents of the new file, since the diff output won't contain |
| 554 information to reconstruct the current file. |
| 555 is_binary: True iff the file is binary. |
| 556 status: The status of the file. |
552 """ | 557 """ |
553 | 558 |
554 raise NotImplementedError( | 559 raise NotImplementedError( |
555 "abstract method -- subclass %s must override" % self.__class__) | 560 "abstract method -- subclass %s must override" % self.__class__) |
556 | 561 |
557 def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options): | 562 def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options): |
558 """Uploads the base files.""" | 563 """Uploads the base files (and if necessary, the current ones as well).""" |
559 patches = dict() | 564 |
560 [patches.setdefault(v, k) for k, v in patch_list] | 565 def UploadFile(filename, file_id, content, is_binary, status, is_base): |
561 for filename in patches.keys(): | 566 """Uploads a file to the server.""" |
562 content, status = self.GetBaseFile(filename) | 567 file_too_large = False |
563 no_base_file = False | 568 if is_base: |
| 569 type = "base" |
| 570 else: |
| 571 type = "current" |
564 if len(content) > MAX_UPLOAD_SIZE: | 572 if len(content) > MAX_UPLOAD_SIZE: |
565 print ("Not uploading the base file for " + filename + | 573 print ("Not uploading the %s file for %s because it's too large." % |
566 " because the file is too large.") | 574 (type, filename)) |
567 no_base_file = True | 575 file_too_large = True |
568 content = "" | 576 content = "" |
569 checksum = md5.new(content).hexdigest() | 577 checksum = md5.new(content).hexdigest() |
570 if options.verbose > 0: | 578 if options.verbose > 0 and not file_too_large: |
571 print "Uploading %s" % filename | 579 print "Uploading %s file for %s" % (type, filename) |
572 url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), | 580 url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id) |
573 int(patches.get(filename))) | |
574 form_fields = [("filename", filename), | 581 form_fields = [("filename", filename), |
575 ("status", status), | 582 ("status", status), |
576 ("checksum", checksum), | 583 ("checksum", checksum), |
577 ] | 584 ("is_binary", str(is_binary)), |
578 if no_base_file: | 585 ("is_current", str(not is_base)), |
579 form_fields.append(("no_base_file", "1")) | 586 ] |
| 587 if file_too_large: |
| 588 form_fields.append(("file_too_large", "1")) |
580 if options.email: | 589 if options.email: |
581 form_fields.append(("user", options.email)) | 590 form_fields.append(("user", options.email)) |
582 ctype, body = EncodeMultipartFormData(form_fields, | 591 ctype, body = EncodeMultipartFormData(form_fields, |
583 [("data", filename, content)]) | 592 [("data", filename, content)]) |
584 response_body = rpc_server.Send(url, body, content_type=ctype) | 593 response_body = rpc_server.Send(url, body, |
| 594 content_type=ctype) |
585 if not response_body.startswith("OK"): | 595 if not response_body.startswith("OK"): |
586 StatusUpdate(" --> %s" % response_body) | 596 StatusUpdate(" --> %s" % response_body) |
587 sys.exit(False) | 597 sys.exit(1) |
| 598 |
| 599 patches = dict() |
| 600 [patches.setdefault(v, k) for k, v in patch_list] |
| 601 for filename in patches.keys(): |
| 602 file_id = int(patches.get(filename)) |
| 603 base_content, new_content, is_binary, status = self.GetBaseFile(filename) |
| 604 if base_content: |
| 605 UploadFile(filename, file_id, base_content, is_binary, status, True) |
| 606 if new_content: |
| 607 UploadFile(filename, file_id, new_content, is_binary, status, False) |
588 | 608 |
589 | 609 |
590 class SubversionVCS(VersionControlSystem): | 610 class SubversionVCS(VersionControlSystem): |
591 """Implementation of the VersionControlSystem interface for Subversion.""" | 611 """Implementation of the VersionControlSystem interface for Subversion.""" |
592 | 612 |
593 def GuessBase(self, required): | 613 def GuessBase(self, required): |
594 """Returns the SVN base URL. | 614 """Returns the SVN base URL. |
595 | 615 |
596 Args: | 616 Args: |
597 required: If true, exits if the url can't be guessed, otherwise None is | 617 required: If true, exits if the url can't be guessed, otherwise None is |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
681 return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) | 701 return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content) |
682 | 702 |
683 def GetUnknownFiles(self): | 703 def GetUnknownFiles(self): |
684 status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) | 704 status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True) |
685 unknown_files = [] | 705 unknown_files = [] |
686 for line in status.split("\n"): | 706 for line in status.split("\n"): |
687 if line and line[0] == "?": | 707 if line and line[0] == "?": |
688 unknown_files.append(line) | 708 unknown_files.append(line) |
689 return unknown_files | 709 return unknown_files |
690 | 710 |
| 711 def IsImage(self, filename): |
| 712 """Returns true if the filename has an image extension.""" |
| 713 mimetype = mimetypes.guess_type(filename)[0] |
| 714 if not mimetype: |
| 715 return False |
| 716 return mimetype.startswith("image/") |
| 717 |
| 718 def ReadFile(self, filename): |
| 719 """Returns the contents of a file.""" |
| 720 file = open(filename, 'rb') |
| 721 result = "" |
| 722 try: |
| 723 result = file.read() |
| 724 finally: |
| 725 file.close() |
| 726 return result |
| 727 |
691 def GetBaseFile(self, filename): | 728 def GetBaseFile(self, filename): |
692 status = RunShell(["svn", "status", "--ignore-externals", filename]) | 729 status = RunShell(["svn", "status", "--ignore-externals", filename]) |
693 if not status: | 730 if not status: |
694 StatusUpdate("svn status returned no output for %s" % filename) | 731 StatusUpdate("svn status returned no output for %s" % filename) |
695 sys.exit(False) | 732 sys.exit(1) |
696 status_lines = status.splitlines() | 733 status_lines = status.splitlines() |
697 # If file is in a cl, the output will begin with | 734 # If file is in a cl, the output will begin with |
698 # "\n--- Changelist 'cl_name':\n". See | 735 # "\n--- Changelist 'cl_name':\n". See |
699 # http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt | 736 # http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt |
700 if (len(status_lines) == 3 and | 737 if (len(status_lines) == 3 and |
701 not status_lines[0] and | 738 not status_lines[0] and |
702 status_lines[1].startswith("--- Changelist")): | 739 status_lines[1].startswith("--- Changelist")): |
703 status = status_lines[2] | 740 status = status_lines[2] |
704 else: | 741 else: |
705 status = status_lines[0] | 742 status = status_lines[0] |
| 743 base_content = "" |
| 744 new_content = "" |
| 745 |
706 # If a file is copied its status will be "A +", which signifies | 746 # If a file is copied its status will be "A +", which signifies |
707 # "addition-with-history". See "svn st" for more information. We need to | 747 # "addition-with-history". See "svn st" for more information. We need to |
708 # upload the original file or else diff parsing will fail if the file was | 748 # upload the original file or else diff parsing will fail if the file was |
709 # edited. | 749 # edited. |
710 if ((status[0] == "A" and status[3] != "+") or | 750 if status[0] == "A" and status[3] != "+": |
711 (status[0] == " " and status[1] == "M")): # property changed | 751 # We'll need to upload the new content if we're adding a binary file |
712 content = "" | 752 # since diff's output won't contain it. |
| 753 mimetype = RunShell(["svn", "propget", "svn:mime-type", filename], |
| 754 silent_ok=True) |
| 755 is_binary = mimetype and not mimetype.startswith("text/") |
| 756 if is_binary and self.IsImage(filename): |
| 757 new_content = self.ReadFile(filename) |
| 758 elif status[0] == " " and status[1] == "M": |
| 759 # Property changed, don't need to do anything. |
| 760 pass |
713 elif (status[0] in ("M", "D", "R") or | 761 elif (status[0] in ("M", "D", "R") or |
714 (status[0] == "A" and status[3] == "+")): | 762 (status[0] == "A" and status[3] == "+")): |
715 mimetype = RunShell(["svn", "-rBASE", "propget", "svn:mime-type", | 763 mimetype = RunShell(["svn", "-rBASE", "propget", "svn:mime-type", |
716 filename], | 764 filename], |
717 silent_ok=True) | 765 silent_ok=True) |
718 if mimetype.startswith("application/octet-stream"): | 766 is_binary = mimetype and not mimetype.startswith("text/") |
719 content = "" | 767 get_base = False |
720 else: | 768 if not is_binary: |
721 content = RunShell(["svn", "cat", filename]) | 769 get_base = True |
722 keywords = RunShell(["svn", "-rBASE", "propget", "svn:keywords", | 770 elif self.IsImage(filename) and status[0] == "M":·········· |
723 filename], | 771 new_content = self.ReadFile(filename) |
724 silent_ok=True) | 772 get_base = True |
725 if keywords: | 773 |
726 content = self._CollapseKeywords(content, keywords) | 774 if get_base: |
| 775 if is_binary: |
| 776 universal_newlines = False |
| 777 else: |
| 778 universal_newlines = True |
| 779 base_content = RunShell(["svn", "cat", filename], |
| 780 universal_newlines=universal_newlines) |
| 781 if not is_binary: |
| 782 keywords = RunShell(["svn", "-rBASE", "propget", "svn:keywords", |
| 783 filename], |
| 784 silent_ok=True) |
| 785 if keywords: |
| 786 base_content = self._CollapseKeywords(base_content, keywords) |
727 else: | 787 else: |
728 StatusUpdate("svn status returned unexpected output: %s" % status) | 788 StatusUpdate("svn status returned unexpected output: %s" % status) |
729 sys.exit(False) | 789 sys.exit(1) |
730 return content, status[0:5] | 790 return base_content, new_content, is_binary, status[0:5] |
731 | 791 |
732 | 792 |
733 class GitVCS(VersionControlSystem): | 793 class GitVCS(VersionControlSystem): |
734 """Implementation of the VersionControlSystem interface for Git.""" | 794 """Implementation of the VersionControlSystem interface for Git.""" |
735 | 795 |
736 def __init__(self): | 796 def __init__(self): |
737 # Map of filename -> hash of base file. | 797 # Map of filename -> hash of base file. |
738 self.base_hashes = {} | 798 self.base_hashes = {} |
739 | 799 |
740 def GenerateDiff(self, extra_args): | 800 def GenerateDiff(self, extra_args): |
(...skipping 22 matching lines...) Expand all Loading... |
763 ErrorExit("No valid patches found in output from git diff") | 823 ErrorExit("No valid patches found in output from git diff") |
764 return "".join(svndiff) | 824 return "".join(svndiff) |
765 | 825 |
766 def GetUnknownFiles(self): | 826 def GetUnknownFiles(self): |
767 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], | 827 status = RunShell(["git", "ls-files", "--exclude-standard", "--others"], |
768 silent_ok=True) | 828 silent_ok=True) |
769 return status.splitlines() | 829 return status.splitlines() |
770 | 830 |
771 def GetBaseFile(self, filename): | 831 def GetBaseFile(self, filename): |
772 hash = self.base_hashes[filename] | 832 hash = self.base_hashes[filename] |
| 833 base_content = "" |
| 834 new_content = "" |
| 835 is_binary = False |
773 if hash == "0" * 40: # All-zero hash indicates no base file. | 836 if hash == "0" * 40: # All-zero hash indicates no base file. |
774 return ("", "A") | 837 status = "A" |
775 else: | 838 else: |
776 return (RunShell(["git", "show", hash]), "M") | 839 status = "M" |
| 840 base_content = RunShell(["git", "show", hash]) |
| 841 return (base_content, new_content, is_binary, status) |
777 | 842 |
778 | 843 |
779 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. | 844 # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. |
780 def SplitPatch(data): | 845 def SplitPatch(data): |
781 """Splits a patch into separate pieces for each file. | 846 """Splits a patch into separate pieces for each file. |
782 | 847 |
783 Args: | 848 Args: |
784 data: A string containing the output of svn diff. | 849 data: A string containing the output of svn diff. |
785 | 850 |
786 Returns: | 851 Returns: |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
833 if not options.download_base: | 898 if not options.download_base: |
834 form_fields.append(("content_upload", "1")) | 899 form_fields.append(("content_upload", "1")) |
835 files = [("data", "data.diff", patch[1])] | 900 files = [("data", "data.diff", patch[1])] |
836 ctype, body = EncodeMultipartFormData(form_fields, files) | 901 ctype, body = EncodeMultipartFormData(form_fields, files) |
837 url = "/%d/upload_patch/%d" % (int(issue), int(patchset)) | 902 url = "/%d/upload_patch/%d" % (int(issue), int(patchset)) |
838 print "Uploading patch for " + patch[0] | 903 print "Uploading patch for " + patch[0] |
839 response_body = rpc_server.Send(url, body, content_type=ctype) | 904 response_body = rpc_server.Send(url, body, content_type=ctype) |
840 lines = response_body.splitlines() | 905 lines = response_body.splitlines() |
841 if not lines or lines[0] != "OK": | 906 if not lines or lines[0] != "OK": |
842 StatusUpdate(" --> %s" % response_body) | 907 StatusUpdate(" --> %s" % response_body) |
843 sys.exit(False) | 908 sys.exit(1) |
844 rv.append([lines[1], patch[0]]) | 909 rv.append([lines[1], patch[0]]) |
845 return rv | 910 return rv |
846 | 911 |
847 | 912 |
848 def GuessVCS(): | 913 def GuessVCS(): |
849 """Helper to guess the version control system. | 914 """Helper to guess the version control system. |
850 | 915 |
851 This examines the current directory, guesses which VersionControlSystem | 916 This examines the current directory, guesses which VersionControlSystem |
852 we're using, and returns an instance of the appropriate class. Exit with an | 917 we're using, and returns an instance of the appropriate class. Exit with an |
853 error if we can't figure it out. | 918 error if we can't figure it out. |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
982 try: | 1047 try: |
983 RealMain(sys.argv) | 1048 RealMain(sys.argv) |
984 except KeyboardInterrupt: | 1049 except KeyboardInterrupt: |
985 print | 1050 print |
986 StatusUpdate("Interrupted.") | 1051 StatusUpdate("Interrupted.") |
987 sys.exit(1) | 1052 sys.exit(1) |
988 | 1053 |
989 | 1054 |
990 if __name__ == "__main__": | 1055 if __name__ == "__main__": |
991 main() | 1056 main() |
OLD | NEW |