Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / Scripts / webkitpy / common / checkout / scm / svn.py
1 # Copyright (c) 2009, 2010, 2011 Google Inc. All rights reserved.
2 # Copyright (c) 2009 Apple Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import logging
31 import os
32 import random
33 import re
34 import shutil
35 import string
36 import sys
37 import tempfile
38
39 from webkitpy.common.memoized import memoized
40 from webkitpy.common.system.executive import Executive, ScriptError
41
42 from .scm import SCM
43
44 _log = logging.getLogger(__name__)
45
46
47 class SVN(SCM):
48
49     executable_name = "svn"
50
51     _svn_metadata_files = frozenset(['.svn', '_svn'])
52
53     def __init__(self, cwd, patch_directories, **kwargs):
54         SCM.__init__(self, cwd, **kwargs)
55         self._bogus_dir = None
56         if patch_directories == []:
57             raise Exception(message='Empty list of patch directories passed to SCM.__init__')
58         elif patch_directories == None:
59             self._patch_directories = [self._filesystem.relpath(cwd, self.checkout_root)]
60         else:
61             self._patch_directories = patch_directories
62
63     @classmethod
64     def in_working_directory(cls, path, executive=None):
65         if os.path.isdir(os.path.join(path, '.svn')):
66             # This is a fast shortcut for svn info that is usually correct for SVN < 1.7,
67             # but doesn't work for SVN >= 1.7.
68             return True
69
70         executive = executive or Executive()
71         svn_info_args = [cls.executable_name, 'info']
72         try:
73             exit_code = executive.run_command(svn_info_args, cwd=path, return_exit_code=True)
74         except OSError, e:
75             # svn is not installed
76             return False
77         return (exit_code == 0)
78
79     def _find_uuid(self, path):
80         if not self.in_working_directory(path):
81             return None
82         return self.value_from_svn_info(path, 'Repository UUID')
83
84     @classmethod
85     def value_from_svn_info(cls, path, field_name):
86         svn_info_args = [cls.executable_name, 'info']
87         # FIXME: This method should use a passed in executive or be made an instance method and use self._executive.
88         info_output = Executive().run_command(svn_info_args, cwd=path).rstrip()
89         match = re.search("^%s: (?P<value>.+)$" % field_name, info_output, re.MULTILINE)
90         if not match:
91             raise ScriptError(script_args=svn_info_args, message='svn info did not contain a %s.' % field_name)
92         return match.group('value').rstrip('\r')
93
94     def find_checkout_root(self, path):
95         uuid = self._find_uuid(path)
96         # If |path| is not in a working directory, we're supposed to return |path|.
97         if not uuid:
98             return path
99         # Search up the directory hierarchy until we find a different UUID.
100         last_path = None
101         while True:
102             if uuid != self._find_uuid(path):
103                 return last_path
104             last_path = path
105             (path, last_component) = self._filesystem.split(path)
106             if last_path == path:
107                 return None
108
109     def _run_svn(self, args, **kwargs):
110         return self._run([self.executable_name] + args, **kwargs)
111
112     @memoized
113     def _svn_version(self):
114         return self._run_svn(['--version', '--quiet'])
115
116     def has_working_directory_changes(self):
117         # FIXME: What about files which are not committed yet?
118         return self._run_svn(["diff"], cwd=self.checkout_root, decode_output=False) != ""
119
120     def status_command(self):
121         return [self.executable_name, 'status']
122
123     def _status_regexp(self, expected_types):
124         field_count = 6 if self._svn_version() > "1.6" else 5
125         return "^(?P<status>[%s]).{%s} (?P<filename>.+)$" % (expected_types, field_count)
126
127     def _add_parent_directories(self, path, recurse):
128         """Does 'svn add' to the path and its parents."""
129         if self.in_working_directory(path):
130             return
131         self.add(path, recurse=recurse)
132
133     def add_list(self, paths, return_exit_code=False, recurse=True):
134         for path in paths:
135             self._add_parent_directories(os.path.dirname(os.path.abspath(path)),
136                                          recurse=False)
137         if recurse:
138             cmd = ["add"] + paths
139         else:
140             cmd = ["add", "--depth", "empty"] + paths
141         return self._run_svn(cmd, return_exit_code=return_exit_code)
142
143     def _delete_parent_directories(self, path):
144         if not self.in_working_directory(path):
145             return
146         if set(os.listdir(path)) - self._svn_metadata_files:
147             return  # Directory has non-trivial files in it.
148         self.delete(path)
149
150     def delete_list(self, paths):
151         for path in paths:
152             abs_path = os.path.abspath(path)
153             parent, base = os.path.split(abs_path)
154             result = self._run_svn(["delete", "--force", base], cwd=parent)
155             self._delete_parent_directories(os.path.dirname(abs_path))
156         return result
157
158     def move(self, origin, destination):
159         return self._run_svn(["mv", "--force", origin, destination], return_exit_code=True)
160
161     def exists(self, path):
162         return not self._run_svn(["info", path], return_exit_code=True, decode_output=False)
163
164     def changed_files(self, git_commit=None):
165         status_command = [self.executable_name, "status"]
166         status_command.extend(self._patch_directories)
167         # ACDMR: Addded, Conflicted, Deleted, Modified or Replaced
168         return self._run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR"))
169
170     def _added_files(self):
171         return self._run_status_and_extract_filenames(self.status_command(), self._status_regexp("A"))
172
173     def _deleted_files(self):
174         return self._run_status_and_extract_filenames(self.status_command(), self._status_regexp("D"))
175
176     @staticmethod
177     def supports_local_commits():
178         return False
179
180     def display_name(self):
181         return "svn"
182
183     def svn_revision(self, path):
184         return self.value_from_svn_info(path, 'Revision')
185
186     def timestamp_of_revision(self, path, revision):
187         # We use --xml to get timestamps like 2013-02-08T08:18:04.964409Z
188         repository_root = self.value_from_svn_info(self.checkout_root, 'Repository Root')
189         info_output = Executive().run_command([self.executable_name, 'log', '-r', revision, '--xml', repository_root], cwd=path).rstrip()
190         match = re.search(r"^<date>(?P<value>.+)</date>\r?$", info_output, re.MULTILINE)
191         return match.group('value')
192
193     def create_patch(self, git_commit=None, changed_files=None):
194         """Returns a byte array (str()) representing the patch file.
195         Patch files are effectively binary since they may contain
196         files of multiple different encodings."""
197         if changed_files == []:
198             return ""
199         elif changed_files == None:
200             changed_files = []
201         return self._run([self._filesystem.join(self.checkout_root, 'Tools', 'Scripts', 'svn-create-patch')] + changed_files,
202             cwd=self.checkout_root, return_stderr=False,
203             decode_output=False)
204
205     def blame(self, path):
206         return self._run_svn(['blame', path])