Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(314)

Side by Side Diff: static/upload.py

Issue 3270: Show images in Rietveld (Closed) Base URL: http://rietveld.googlecode.com/svn/trunk/
Patch Set: latest update Created 16 years, 6 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « static/blank.jpg ('k') | templates/diff.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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()
OLDNEW
« no previous file with comments | « static/blank.jpg ('k') | templates/diff.html » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b