1 # Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
2 # Ben Finney <benf@cybersource.com.au>
3 # Chris Ball <cjb@laptop.org>
4 # Gianluca Montecchi <gian@grys.it>
5 # Marien Zwart <marien.zwart@gmail.com>
6 # W. Trevor King <wking@drexel.edu>
8 # This file is part of Bugs Everywhere.
10 # Bugs Everywhere is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by the
12 # Free Software Foundation, either version 2 of the License, or (at your
13 # option) any later version.
15 # Bugs Everywhere is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 # General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with Bugs Everywhere. If not, see <http://www.gnu.org/licenses/>.
23 """Bazaar_ (bzr) backend.
25 .. _Bazaar: http://bazaar.canonical.com/
31 import bzrlib.builtins
48 if libbe.TESTING == True:
57 """:class:`base.VCS` implementation for Bazaar.
60 client = None # bzrlib module
62 def __init__(self, *args, **kwargs):
63 base.VCS.__init__(self, *args, **kwargs)
66 def _vcs_version(self):
69 return bzrlib.__version__
71 def version_cmp(self, *args):
72 """Compare the installed Bazaar version `V_i` with another version
73 `V_o` (given in `*args`). Returns
85 >>> b._version = '2.3.1 (release)'
86 >>> b.version_cmp(2,3,1)
88 >>> b.version_cmp(2,3,2)
90 >>> b.version_cmp(2,3,'a',5)
92 >>> b.version_cmp(2,3,0)
94 >>> b.version_cmp(2,3,1,'a',5)
96 >>> b.version_cmp(2,3,1,1)
100 >>> b._version = '2.0.0pre2'
101 >>> b._parsed_version = None
104 >>> b.version_cmp(2,0,1)
106 >>> b.version_cmp(2,0,0,'pre',1)
108 >>> b.version_cmp(2,0,0,'pre',2)
110 >>> b.version_cmp(2,0,0,'pre',3)
112 >>> b.version_cmp(2,0,0,'a',3)
114 >>> b.version_cmp(2,0,0,'rc',1)
117 if not hasattr(self, '_parsed_version') \
118 or self._parsed_version == None:
119 num_part = self.version().split(' ')[0]
120 self._parsed_version = []
121 for num in num_part.split('.'):
123 self._parsed_version.append(int(num))
124 except ValueError, e:
125 # bzr version number might contain non-numerical tags
127 splitter = re.compile(r'[\D]') # Match non-digits
128 splits = splitter.split(num)
129 # if len(tag) > 1 some splits will be empty; remove
130 splits = filter(lambda s: s != '', splits)
131 tag_starti = len(splits[0])
132 num_starti = num.find(splits[1], tag_starti)
133 tag = num[tag_starti:num_starti]
134 self._parsed_version.append(int(splits[0]))
135 self._parsed_version.append(tag)
136 self._parsed_version.append(int(splits[1]))
137 for current,other in zip(self._parsed_version, args):
138 if type(current) != type (other):
139 # one of them is a pre-release string
140 if type(current) != types.IntType:
144 c = cmp(current,other)
147 # see if one is longer than the other
148 verlen = len(self._parsed_version)
152 elif verlen > arglen:
153 if type(self._parsed_version[arglen]) != types.IntType:
154 return -1 # self is a prerelease
158 if type(args[verlen]) != types.IntType:
159 return 1 # args is a prerelease
163 def _vcs_get_user_id(self):
164 # excerpted from bzrlib.builtins.cmd_whoami.run()
166 c = bzrlib.branch.Branch.open_containing(self.repo)[0].get_config()
167 except errors.NotBranchError:
168 c = bzrlib.config.GlobalConfig()
171 def _vcs_detect(self, path):
172 if self._u_search_parent_directories(path, '.bzr') != None :
176 def _vcs_root(self, path):
177 """Find the root of the deepest repository containing path."""
178 cmd = bzrlib.builtins.cmd_root()
179 cmd.outf = StringIO.StringIO()
180 cmd.run(filename=path)
181 if self.version_cmp(2,2,0) < 0:
183 return cmd.outf.getvalue().rstrip('\n')
185 def _vcs_init(self, path):
186 cmd = bzrlib.builtins.cmd_init()
187 cmd.outf = StringIO.StringIO()
188 cmd.run(location=path)
189 if self.version_cmp(2,2,0) < 0:
192 def _vcs_destroy(self):
193 vcs_dir = os.path.join(self.repo, '.bzr')
194 if os.path.exists(vcs_dir):
195 shutil.rmtree(vcs_dir)
197 def _vcs_add(self, path):
198 path = os.path.join(self.repo, path)
199 cmd = bzrlib.builtins.cmd_add()
200 cmd.outf = StringIO.StringIO()
201 kwargs = {'file_ids_from': self.repo}
202 if self.repo == os.path.realpath(os.getcwd()):
203 # Work around bzr file locking on Windows.
204 # See: https://lists.ubuntu.com/archives/bazaar/2011q1/071705.html
205 kwargs.pop('file_ids_from')
206 cmd.run(file_list=[path], **kwargs)
207 if self.version_cmp(2,2,0) < 0:
210 def _vcs_exists(self, path, revision=None):
211 manifest = self._vcs_listdir(
212 self.repo, revision=revision, recursive=True)
217 def _vcs_remove(self, path):
218 # --force to also remove unversioned files.
219 path = os.path.join(self.repo, path)
220 cmd = bzrlib.builtins.cmd_remove()
221 cmd.outf = StringIO.StringIO()
222 cmd.run(file_list=[path], file_deletion_strategy='force')
223 if self.version_cmp(2,2,0) < 0:
226 def _vcs_update(self, path):
229 def _parse_revision_string(self, revision=None):
232 rev_opt = bzrlib.option.Option.OPTIONS['revision']
234 rev_spec = rev_opt.type(revision)
235 except bzrlib.errors.NoSuchRevisionSpec:
236 raise base.InvalidRevision(revision)
239 def _vcs_get_file_contents(self, path, revision=None):
241 return base.VCS._vcs_get_file_contents(self, path, revision)
242 path = os.path.join(self.repo, path)
243 revision = self._parse_revision_string(revision)
244 cmd = bzrlib.builtins.cmd_cat()
245 cmd.outf = StringIO.StringIO()
246 if self.version_cmp(1,6,0) < 0:
247 # old bzrlib cmd_cat uses sys.stdout not self.outf for output.
249 sys.stdout = cmd.outf
251 cmd.run(filename=path, revision=revision)
252 except bzrlib.errors.BzrCommandError, e:
253 if 'not present in revision' in str(e):
254 raise base.InvalidPath(path, root=self.repo, revision=revision)
257 if self.version_cmp(2,0,0) < 0:
258 cmd.outf = sys.stdout
260 if self.version_cmp(2,2,0) < 0:
262 return cmd.outf.getvalue()
264 def _vcs_path(self, id, revision):
265 manifest = self._vcs_listdir(
266 self.repo, revision=revision, recursive=True)
267 return self._u_find_id_from_manifest(id, manifest, revision=revision)
269 def _vcs_isdir(self, path, revision):
271 self._vcs_listdir(path, revision)
272 except AttributeError, e:
273 if 'children' in str(e):
278 def _vcs_listdir(self, path, revision, recursive=False):
279 path = os.path.join(self.repo, path)
280 revision = self._parse_revision_string(revision)
281 cmd = bzrlib.builtins.cmd_ls()
282 cmd.outf = StringIO.StringIO()
284 if self.version_cmp(2,0,0) >= 0:
285 cmd.run(revision=revision, path=path, recursive=recursive)
287 # Pre-2.0 Bazaar (non_recursive)
288 # + working around broken non_recursive+path implementation
289 # (https://bugs.launchpad.net/bzr/+bug/158690)
290 cmd.run(revision=revision, path=path,
292 except bzrlib.errors.BzrCommandError, e:
293 if 'not present in revision' in str(e):
294 raise base.InvalidPath(path, root=self.repo, revision=revision)
297 if self.version_cmp(2,2,0) < 0:
299 children = cmd.outf.getvalue().rstrip('\n').splitlines()
300 children = [self._u_rel_path(c, path) for c in children]
301 if self.version_cmp(2,0,0) < 0 and recursive == False:
302 children = [c for c in children if os.path.sep not in c]
305 def _vcs_commit(self, commitfile, allow_empty=False):
306 cmd = bzrlib.builtins.cmd_commit()
307 cmd.outf = StringIO.StringIO()
311 cmd.run(file=commitfile, unchanged=allow_empty)
312 except bzrlib.errors.BzrCommandError, e:
313 strings = ['no changes to commit.', # bzr 1.3.1
314 'No changes to commit.'] # bzr 1.15.1
315 if self._u_any_in_string(strings, str(e)) == True:
316 raise base.EmptyCommit()
320 if self.version_cmp(2,2,0) < 0:
322 return self._vcs_revision_id(-1)
324 def _vcs_revision_id(self, index):
325 cmd = bzrlib.builtins.cmd_revno()
326 cmd.outf = StringIO.StringIO()
327 cmd.run(location=self.repo)
328 if self.version_cmp(2,2,0) < 0:
330 current_revision = int(cmd.outf.getvalue())
331 if index > current_revision or index < -current_revision:
334 return str(index) # bzr commit 0 is the empty tree.
335 return str(current_revision+index+1)
337 def _diff(self, revision):
338 revision = self._parse_revision_string(revision)
339 cmd = bzrlib.builtins.cmd_diff()
340 cmd.outf = StringIO.StringIO()
341 # for some reason, cmd_diff uses sys.stdout not self.outf for output.
343 sys.stdout = cmd.outf
345 status = cmd.run(revision=revision, file_list=[self.repo])
348 if self.version_cmp(2,2,0) < 0:
350 assert status in [0,1], "Invalid status %d" % status
351 return cmd.outf.getvalue()
353 def _parse_diff(self, diff_text):
354 """_parse_diff(diff_text) -> (new,modified,removed)
356 `new`, `modified`, and `removed` are lists of files.
360 === modified file 'dir/changed'
361 --- dir/changed 2010-01-16 01:54:53 +0000
362 +++ dir/changed 2010-01-16 01:54:54 +0000
369 === removed file 'dir/deleted'
370 --- dir/deleted 2010-01-16 01:54:53 +0000
371 +++ dir/deleted 1970-01-01 00:00:00 +0000
377 === removed file 'dir/moved'
378 --- dir/moved 2010-01-16 01:54:53 +0000
379 +++ dir/moved 1970-01-01 00:00:00 +0000
386 === added file 'dir/moved2'
387 --- dir/moved2 1970-01-01 00:00:00 +0000
388 +++ dir/moved2 2010-01-16 01:54:34 +0000
395 === added file 'dir/new'
396 --- dir/new 1970-01-01 00:00:00 +0000
397 +++ dir/new 2010-01-16 01:54:54 +0000
406 for line in diff_text.splitlines():
407 if not line.startswith('=== '):
409 fields = line.split()
411 file = fields[-1].strip("'")
412 if action == 'added':
414 elif action == 'modified':
415 modified.append(file)
416 elif action == 'removed':
418 return (new,modified,removed)
420 def _vcs_changed(self, revision):
421 return self._parse_diff(self._diff(revision))
424 if libbe.TESTING == True:
425 base.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__])
427 unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
428 suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])