| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 # | 5 # |
| 6 # Wrapper script around Rietveld's upload.py that groups files into | 6 # Wrapper script around Rietveld's upload.py that groups files into |
| 7 # changelists. | 7 # changelists. |
| 8 | 8 |
| 9 import getpass | 9 import getpass |
| 10 import os | 10 import os |
| 11 import random | 11 import random |
| 12 import re | 12 import re |
| 13 import string | 13 import string |
| 14 import subprocess | 14 import subprocess |
| 15 import sys | 15 import sys |
| 16 import tempfile | 16 import tempfile |
| 17 import upload | 17 import upload |
| 18 import urllib2 | 18 import urllib2 |
| 19 import xml.dom.minidom | 19 import xml.dom.minidom |
| 20 | 20 |
| 21 # gcl now depends on gclient. |
| 22 import gclient |
| 21 | 23 |
| 22 __version__ = '1.0' | 24 __version__ = '1.0' |
| 23 | 25 |
| 24 | 26 |
| 25 CODEREVIEW_SETTINGS = { | 27 CODEREVIEW_SETTINGS = { |
| 26 # Default values. | 28 # Default values. |
| 27 "CODE_REVIEW_SERVER": "codereview.chromium.org", | 29 "CODE_REVIEW_SERVER": "codereview.chromium.org", |
| 28 "CC_LIST": "chromium-reviews@googlegroups.com", | 30 "CC_LIST": "chromium-reviews@googlegroups.com", |
| 29 "VIEW_VC": "http://src.chromium.org/viewvc/chrome?view=rev&revision=", | 31 "VIEW_VC": "http://src.chromium.org/viewvc/chrome?view=rev&revision=", |
| 30 } | 32 } |
| 31 | 33 |
| 32 # globals that store the root of the current repository and the directory where | 34 # globals that store the root of the current repository and the directory where |
| 33 # we store information about changelists. | 35 # we store information about changelists. |
| 34 repository_root = "" | 36 repository_root = "" |
| 35 gcl_info_dir = "" | 37 gcl_info_dir = "" |
| 36 | 38 |
| 37 # Filename where we store repository specific information for gcl. | 39 # Filename where we store repository specific information for gcl. |
| 38 CODEREVIEW_SETTINGS_FILE = "codereview.settings" | 40 CODEREVIEW_SETTINGS_FILE = "codereview.settings" |
| 39 | 41 |
| 40 # Warning message when the change appears to be missing tests. | 42 # Warning message when the change appears to be missing tests. |
| 41 MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!" | 43 MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!" |
| 42 | 44 |
| 43 # Caches whether we read the codereview.settings file yet or not. | 45 # Caches whether we read the codereview.settings file yet or not. |
| 44 read_gcl_info = False | 46 read_gcl_info = False |
| 45 | 47 |
| 46 | 48 |
| 47 ### Simplified XML processing functions. | |
| 48 | |
| 49 def ParseXML(output): | |
| 50 try: | |
| 51 return xml.dom.minidom.parseString(output) | |
| 52 except xml.parsers.expat.ExpatError: | |
| 53 return None | |
| 54 | |
| 55 def GetNamedNodeText(node, node_name): | |
| 56 child_nodes = node.getElementsByTagName(node_name) | |
| 57 if not child_nodes: | |
| 58 return None | |
| 59 assert len(child_nodes) == 1 and child_nodes[0].childNodes.length == 1 | |
| 60 return child_nodes[0].firstChild.nodeValue | |
| 61 | |
| 62 | |
| 63 def GetNodeNamedAttributeText(node, node_name, attribute_name): | |
| 64 child_nodes = node.getElementsByTagName(node_name) | |
| 65 if not child_nodes: | |
| 66 return None | |
| 67 assert len(child_nodes) == 1 | |
| 68 return child_nodes[0].getAttribute(attribute_name) | |
| 69 | |
| 70 | |
| 71 ### SVN Functions | 49 ### SVN Functions |
| 72 | 50 |
| 73 def IsSVNMoved(filename): | 51 def IsSVNMoved(filename): |
| 74 """Determine if a file has been added through svn mv""" | 52 """Determine if a file has been added through svn mv""" |
| 75 info = GetSVNFileInfo(filename) | 53 info = gclient.CaptureSVNInfo(filename) |
| 76 return (info.get('Copied From URL') and | 54 return (info.get('Copied From URL') and |
| 77 info.get('Copied From Rev') and | 55 info.get('Copied From Rev') and |
| 78 info.get('Schedule') == 'add') | 56 info.get('Schedule') == 'add') |
| 79 | 57 |
| 80 | 58 |
| 81 def GetSVNFileInfo(file): | |
| 82 """Returns a dictionary from the svn info output for the given file.""" | |
| 83 output = RunShell(["svn", "info", "--xml", file]) | |
| 84 dom = ParseXML(output) | |
| 85 result = {} | |
| 86 if dom: | |
| 87 # /info/entry/ | |
| 88 # url | |
| 89 # reposityory/(root|uuid) | |
| 90 # wc-info/(schedule|depth) | |
| 91 # commit/(author|date) | |
| 92 result['Node Kind'] = GetNodeNamedAttributeText(dom, 'entry', 'kind') | |
| 93 result['Repository Root'] = GetNamedNodeText(dom, 'root') | |
| 94 result['Schedule'] = GetNamedNodeText(dom, 'schedule') | |
| 95 result['URL'] = GetNamedNodeText(dom, 'url') | |
| 96 result['Path'] = GetNodeNamedAttributeText(dom, 'entry', 'path') | |
| 97 result['Copied From URL'] = GetNamedNodeText(dom, 'copy-from-url') | |
| 98 result['Copied From Rev'] = GetNamedNodeText(dom, 'copy-from-rev') | |
| 99 return result | |
| 100 | |
| 101 | |
| 102 def GetSVNFileProperty(file, property_name): | 59 def GetSVNFileProperty(file, property_name): |
| 103 """Returns the value of an SVN property for the given file. | 60 """Returns the value of an SVN property for the given file. |
| 104 | 61 |
| 105 Args: | 62 Args: |
| 106 file: The file to check | 63 file: The file to check |
| 107 property_name: The name of the SVN property, e.g. "svn:mime-type" | 64 property_name: The name of the SVN property, e.g. "svn:mime-type" |
| 108 | 65 |
| 109 Returns: | 66 Returns: |
| 110 The value of the property, which will be the empty string if the property | 67 The value of the property, which will be the empty string if the property |
| 111 is not set on the file. If the file is not under version control, the | 68 is not set on the file. If the file is not under version control, the |
| (...skipping 26 matching lines...) Expand all Loading... |
| 138 'deleted': 'D', | 95 'deleted': 'D', |
| 139 'ignored': 'I', | 96 'ignored': 'I', |
| 140 'missing': '!', | 97 'missing': '!', |
| 141 'modified': 'M', | 98 'modified': 'M', |
| 142 'normal': ' ', | 99 'normal': ' ', |
| 143 'replaced': 'R', | 100 'replaced': 'R', |
| 144 'unversioned': '?', | 101 'unversioned': '?', |
| 145 # TODO(maruel): Find the corresponding strings for X, ~ | 102 # TODO(maruel): Find the corresponding strings for X, ~ |
| 146 } | 103 } |
| 147 output = RunShell(command) | 104 output = RunShell(command) |
| 148 dom = ParseXML(output) | 105 dom = gclient.ParseXML(output) |
| 149 results = [] | 106 results = [] |
| 150 if dom: | 107 if dom: |
| 151 # /status/target/entry/(wc-status|commit|author|date) | 108 # /status/target/entry/(wc-status|commit|author|date) |
| 152 for target in dom.getElementsByTagName('target'): | 109 for target in dom.getElementsByTagName('target'): |
| 153 base_path = target.getAttribute('path') | 110 base_path = target.getAttribute('path') |
| 154 for entry in target.getElementsByTagName('entry'): | 111 for entry in target.getElementsByTagName('entry'): |
| 155 file = entry.getAttribute('path') | 112 file = entry.getAttribute('path') |
| 156 wc_status = entry.getElementsByTagName('wc-status') | 113 wc_status = entry.getElementsByTagName('wc-status') |
| 157 assert len(wc_status) == 1 | 114 assert len(wc_status) == 1 |
| 158 # Emulate svn 1.5 status ouput... | 115 # Emulate svn 1.5 status ouput... |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 193 return [item[1] for item in GetSVNStatus(extra_args) if item[0][0] == '?'] | 150 return [item[1] for item in GetSVNStatus(extra_args) if item[0][0] == '?'] |
| 194 | 151 |
| 195 | 152 |
| 196 def GetRepositoryRoot(): | 153 def GetRepositoryRoot(): |
| 197 """Returns the top level directory of the current repository. | 154 """Returns the top level directory of the current repository. |
| 198 | 155 |
| 199 The directory is returned as an absolute path. | 156 The directory is returned as an absolute path. |
| 200 """ | 157 """ |
| 201 global repository_root | 158 global repository_root |
| 202 if not repository_root: | 159 if not repository_root: |
| 203 cur_dir_repo_root = GetSVNFileInfo(os.getcwd()).get("Repository Root") | 160 infos = gclient.CaptureSVNInfo(os.getcwd(), print_error=False) |
| 161 cur_dir_repo_root = infos.get("Repository Root") |
| 204 if not cur_dir_repo_root: | 162 if not cur_dir_repo_root: |
| 205 raise Exception("gcl run outside of repository") | 163 raise Exception("gcl run outside of repository") |
| 206 | 164 |
| 207 repository_root = os.getcwd() | 165 repository_root = os.getcwd() |
| 208 while True: | 166 while True: |
| 209 parent = os.path.dirname(repository_root) | 167 parent = os.path.dirname(repository_root) |
| 210 if GetSVNFileInfo(parent).get("Repository Root") != cur_dir_repo_root: | 168 if (gclient.CaptureSVNInfo(parent).get("Repository Root") != |
| 169 cur_dir_repo_root): |
| 211 break | 170 break |
| 212 repository_root = parent | 171 repository_root = parent |
| 213 return repository_root | 172 return repository_root |
| 214 | 173 |
| 215 | 174 |
| 216 def GetInfoDir(): | 175 def GetInfoDir(): |
| 217 """Returns the directory where gcl info files are stored.""" | 176 """Returns the directory where gcl info files are stored.""" |
| 218 global gcl_info_dir | 177 global gcl_info_dir |
| 219 if not gcl_info_dir: | 178 if not gcl_info_dir: |
| 220 gcl_info_dir = os.path.join(GetRepositoryRoot(), '.svn', 'gcl_info') | 179 gcl_info_dir = os.path.join(GetRepositoryRoot(), '.svn', 'gcl_info') |
| 221 return gcl_info_dir | 180 return gcl_info_dir |
| 222 | 181 |
| 223 | 182 |
| 224 def GetCodeReviewSetting(key): | 183 def GetCodeReviewSetting(key): |
| 225 """Returns a value for the given key for this repository.""" | 184 """Returns a value for the given key for this repository.""" |
| 226 global read_gcl_info | 185 global read_gcl_info |
| 227 if not read_gcl_info: | 186 if not read_gcl_info: |
| 228 read_gcl_info = True | 187 read_gcl_info = True |
| 229 # First we check if we have a cached version. | 188 # First we check if we have a cached version. |
| 230 cached_settings_file = os.path.join(GetInfoDir(), CODEREVIEW_SETTINGS_FILE) | 189 cached_settings_file = os.path.join(GetInfoDir(), CODEREVIEW_SETTINGS_FILE) |
| 231 if (not os.path.exists(cached_settings_file) or | 190 if (not os.path.exists(cached_settings_file) or |
| 232 os.stat(cached_settings_file).st_mtime > 60*60*24*3): | 191 os.stat(cached_settings_file).st_mtime > 60*60*24*3): |
| 233 dir_info = GetSVNFileInfo(".") | 192 dir_info = gclient.CaptureSVNInfo(".") |
| 234 repo_root = dir_info["Repository Root"] | 193 repo_root = dir_info["Repository Root"] |
| 235 url_path = dir_info["URL"] | 194 url_path = dir_info["URL"] |
| 236 settings = "" | 195 settings = "" |
| 237 while True: | 196 while True: |
| 238 # Look for the codereview.settings file at the current level. | 197 # Look for the codereview.settings file at the current level. |
| 239 svn_path = url_path + "/" + CODEREVIEW_SETTINGS_FILE | 198 svn_path = url_path + "/" + CODEREVIEW_SETTINGS_FILE |
| 240 settings, rc = RunShellWithReturnCode(["svn", "cat", svn_path]) | 199 settings, rc = RunShellWithReturnCode(["svn", "cat", svn_path]) |
| 241 if not rc: | 200 if not rc: |
| 242 # Exit the loop if the file was found. | 201 # Exit the loop if the file was found. |
| 243 break | 202 break |
| (...skipping 502 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 746 previous_cwd = os.getcwd() | 705 previous_cwd = os.getcwd() |
| 747 if root is None: | 706 if root is None: |
| 748 os.chdir(GetRepositoryRoot()) | 707 os.chdir(GetRepositoryRoot()) |
| 749 else: | 708 else: |
| 750 os.chdir(root) | 709 os.chdir(root) |
| 751 | 710 |
| 752 diff = [] | 711 diff = [] |
| 753 for file in files: | 712 for file in files: |
| 754 # Use svn info output instead of os.path.isdir because the latter fails | 713 # Use svn info output instead of os.path.isdir because the latter fails |
| 755 # when the file is deleted. | 714 # when the file is deleted. |
| 756 if GetSVNFileInfo(file).get("Node Kind") in ("dir", "directory"): | 715 if gclient.CaptureSVNInfo(file).get("Node Kind") in ("dir", "directory"): |
| 757 continue | 716 continue |
| 758 # If the user specified a custom diff command in their svn config file, | 717 # If the user specified a custom diff command in their svn config file, |
| 759 # then it'll be used when we do svn diff, which we don't want to happen | 718 # then it'll be used when we do svn diff, which we don't want to happen |
| 760 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 719 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
| 761 # work, since they can have another diff executable in their path that | 720 # work, since they can have another diff executable in their path that |
| 762 # gives different line endings. So we use a bogus temp directory as the | 721 # gives different line endings. So we use a bogus temp directory as the |
| 763 # config directory, which gets around these problems. | 722 # config directory, which gets around these problems. |
| 764 if sys.platform.startswith("win"): | 723 if sys.platform.startswith("win"): |
| 765 parent_dir = tempfile.gettempdir() | 724 parent_dir = tempfile.gettempdir() |
| 766 else: | 725 else: |
| (...skipping 420 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1187 # the files. This allows commands such as 'gcl diff xxx' to work. | 1146 # the files. This allows commands such as 'gcl diff xxx' to work. |
| 1188 args =["svn", command] | 1147 args =["svn", command] |
| 1189 root = GetRepositoryRoot() | 1148 root = GetRepositoryRoot() |
| 1190 args.extend([os.path.join(root, x) for x in change_info.FileList()]) | 1149 args.extend([os.path.join(root, x) for x in change_info.FileList()]) |
| 1191 RunShell(args, True) | 1150 RunShell(args, True) |
| 1192 return 0 | 1151 return 0 |
| 1193 | 1152 |
| 1194 | 1153 |
| 1195 if __name__ == "__main__": | 1154 if __name__ == "__main__": |
| 1196 sys.exit(main()) | 1155 sys.exit(main()) |
| OLD | NEW |