1 # Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
2 # Ben Finney <benf@cybersource.com.au>
3 # Gianluca Montecchi <gian@grys.it>
4 # Marien Zwart <marien.zwart@gmail.com>
5 # W. Trevor King <wking@drexel.edu>
7 # This file is part of Bugs Everywhere.
9 # Bugs Everywhere is free software; you can redistribute it and/or modify it
10 # under the terms of the GNU General Public License as published by the
11 # Free Software Foundation, either version 2 of the License, or (at your
12 # option) any later version.
14 # Bugs Everywhere is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 # General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with Bugs Everywhere. If not, see <http://www.gnu.org/licenses/>.
22 """Bazaar_ (bzr) backend.
24 .. _Bazaar: http://bazaar.canonical.com/
30 import bzrlib.builtins
47 if libbe.TESTING == True:
56 """:class:`base.VCS` implementation for Bazaar.
59 client = None # bzrlib module
61 def __init__(self, *args, **kwargs):
62 base.VCS.__init__(self, *args, **kwargs)
65 def _vcs_version(self):
68 return bzrlib.__version__
70 def version_cmp(self, *args):
71 """Compare the installed Bazaar version `V_i` with another version
72 `V_o` (given in `*args`). Returns
84 >>> b._version = '2.3.1 (release)'
85 >>> b.version_cmp(2,3,1)
87 >>> b.version_cmp(2,3,2)
89 >>> b.version_cmp(2,3,0)
93 >>> b._version = '2.0.0pre2'
94 >>> b._parsed_version = None
97 >>> b.version_cmp(2,0,1)
98 Traceback (most recent call last):
100 NotImplementedError: Cannot parse non-integer portion "0pre2" of Bzr version "2.0.0pre2"
102 if not hasattr(self, '_parsed_version') \
103 or self._parsed_version == None:
104 num_part = self.version().split(' ')[0]
105 self._parsed_version = []
106 for num in num_part.split('.'):
108 self._parsed_version.append(int(num))
109 except ValueError, e:
110 self._parsed_version.append(num)
111 for current,other in zip(self._parsed_version, args):
112 if type(current) != types.IntType:
113 raise NotImplementedError(
114 'Cannot parse non-integer portion "%s" of Bzr version "%s"'
115 % (current, self.version()))
116 c = cmp(current,other)
121 def _vcs_get_user_id(self):
122 # excerpted from bzrlib.builtins.cmd_whoami.run()
124 c = bzrlib.branch.Branch.open_containing(self.repo)[0].get_config()
125 except errors.NotBranchError:
126 c = bzrlib.config.GlobalConfig()
129 def _vcs_detect(self, path):
130 if self._u_search_parent_directories(path, '.bzr') != None :
134 def _vcs_root(self, path):
135 """Find the root of the deepest repository containing path."""
136 cmd = bzrlib.builtins.cmd_root()
137 cmd.outf = StringIO.StringIO()
138 cmd.run(filename=path)
140 return cmd.outf.getvalue().rstrip('\n')
142 def _vcs_init(self, path):
143 cmd = bzrlib.builtins.cmd_init()
144 cmd.outf = StringIO.StringIO()
145 cmd.run(location=path)
148 def _vcs_destroy(self):
149 vcs_dir = os.path.join(self.repo, '.bzr')
150 if os.path.exists(vcs_dir):
151 shutil.rmtree(vcs_dir)
153 def _vcs_add(self, path):
154 path = os.path.join(self.repo, path)
155 cmd = bzrlib.builtins.cmd_add()
156 cmd.outf = StringIO.StringIO()
157 cmd.run(file_list=[path], file_ids_from=self.repo)
160 def _vcs_exists(self, path, revision=None):
161 manifest = self._vcs_listdir(
162 self.repo, revision=revision, recursive=True)
167 def _vcs_remove(self, path):
168 # --force to also remove unversioned files.
169 path = os.path.join(self.repo, path)
170 cmd = bzrlib.builtins.cmd_remove()
171 cmd.outf = StringIO.StringIO()
172 cmd.run(file_list=[path], file_deletion_strategy='force')
175 def _vcs_update(self, path):
178 def _parse_revision_string(self, revision=None):
181 rev_opt = bzrlib.option.Option.OPTIONS['revision']
183 rev_spec = rev_opt.type(revision)
184 except bzrlib.errors.NoSuchRevisionSpec:
185 raise base.InvalidRevision(revision)
188 def _vcs_get_file_contents(self, path, revision=None):
190 return base.VCS._vcs_get_file_contents(self, path, revision)
191 path = os.path.join(self.repo, path)
192 revision = self._parse_revision_string(revision)
193 cmd = bzrlib.builtins.cmd_cat()
194 cmd.outf = StringIO.StringIO()
195 if self.version_cmp(1,6,0) < 0:
196 # old bzrlib cmd_cat uses sys.stdout not self.outf for output.
198 sys.stdout = cmd.outf
200 cmd.run(filename=path, revision=revision)
201 except bzrlib.errors.BzrCommandError, e:
202 if 'not present in revision' in str(e):
203 raise base.InvalidPath(path, root=self.repo, revision=revision)
206 if self.version_cmp(2,0,0) < 0:
207 cmd.outf = sys.stdout
210 return cmd.outf.getvalue()
212 def _vcs_path(self, id, revision):
213 manifest = self._vcs_listdir(
214 self.repo, revision=revision, recursive=True)
215 return self._u_find_id_from_manifest(id, manifest, revision=revision)
217 def _vcs_isdir(self, path, revision):
219 self._vcs_listdir(path, revision)
220 except AttributeError, e:
221 if 'children' in str(e):
226 def _vcs_listdir(self, path, revision, recursive=False):
227 path = os.path.join(self.repo, path)
228 revision = self._parse_revision_string(revision)
229 cmd = bzrlib.builtins.cmd_ls()
230 cmd.outf = StringIO.StringIO()
232 if self.version_cmp(2,0,0) >= 0:
233 cmd.run(revision=revision, path=path, recursive=recursive)
235 # Pre-2.0 Bazaar (non_recursive)
236 # + working around broken non_recursive+path implementation
237 # (https://bugs.launchpad.net/bzr/+bug/158690)
238 cmd.run(revision=revision, path=path,
240 except bzrlib.errors.BzrCommandError, e:
241 if 'not present in revision' in str(e):
242 raise base.InvalidPath(path, root=self.repo, revision=revision)
246 children = cmd.outf.getvalue().rstrip('\n').splitlines()
247 children = [self._u_rel_path(c, path) for c in children]
248 if self.version_cmp(2,0,0) < 0 and recursive == False:
249 children = [c for c in children if os.path.sep not in c]
252 def _vcs_commit(self, commitfile, allow_empty=False):
253 cmd = bzrlib.builtins.cmd_commit()
254 cmd.outf = StringIO.StringIO()
258 cmd.run(file=commitfile, unchanged=allow_empty)
259 except bzrlib.errors.BzrCommandError, e:
260 strings = ['no changes to commit.', # bzr 1.3.1
261 'No changes to commit.'] # bzr 1.15.1
262 if self._u_any_in_string(strings, str(e)) == True:
263 raise base.EmptyCommit()
268 return self._vcs_revision_id(-1)
270 def _vcs_revision_id(self, index):
271 cmd = bzrlib.builtins.cmd_revno()
272 cmd.outf = StringIO.StringIO()
273 cmd.run(location=self.repo)
275 current_revision = int(cmd.outf.getvalue())
276 if index > current_revision or index < -current_revision:
279 return str(index) # bzr commit 0 is the empty tree.
280 return str(current_revision+index+1)
282 def _diff(self, revision):
283 revision = self._parse_revision_string(revision)
284 cmd = bzrlib.builtins.cmd_diff()
285 cmd.outf = StringIO.StringIO()
286 # for some reason, cmd_diff uses sys.stdout not self.outf for output.
288 sys.stdout = cmd.outf
290 status = cmd.run(revision=revision, file_list=[self.repo])
294 assert status in [0,1], "Invalid status %d" % status
295 return cmd.outf.getvalue()
297 def _parse_diff(self, diff_text):
298 """_parse_diff(diff_text) -> (new,modified,removed)
300 `new`, `modified`, and `removed` are lists of files.
304 === modified file 'dir/changed'
305 --- dir/changed 2010-01-16 01:54:53 +0000
306 +++ dir/changed 2010-01-16 01:54:54 +0000
313 === removed file 'dir/deleted'
314 --- dir/deleted 2010-01-16 01:54:53 +0000
315 +++ dir/deleted 1970-01-01 00:00:00 +0000
321 === removed file 'dir/moved'
322 --- dir/moved 2010-01-16 01:54:53 +0000
323 +++ dir/moved 1970-01-01 00:00:00 +0000
330 === added file 'dir/moved2'
331 --- dir/moved2 1970-01-01 00:00:00 +0000
332 +++ dir/moved2 2010-01-16 01:54:34 +0000
339 === added file 'dir/new'
340 --- dir/new 1970-01-01 00:00:00 +0000
341 +++ dir/new 2010-01-16 01:54:54 +0000
350 for line in diff_text.splitlines():
351 if not line.startswith('=== '):
353 fields = line.split()
355 file = fields[-1].strip("'")
356 if action == 'added':
358 elif action == 'modified':
359 modified.append(file)
360 elif action == 'removed':
362 return (new,modified,removed)
364 def _vcs_changed(self, revision):
365 return self._parse_diff(self._diff(revision))
368 if libbe.TESTING == True:
369 base.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__])
371 unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
372 suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])