Incorrect accquiring bugdir command line argument
[be.git] / libbe / storage / vcs / bzr.py
1 # Copyright (C) 2005-2012 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 #                         Michel Alexandre Salim <salimma@fedoraproject.org>
7 #                         W. Trevor King <wking@tremily.us>
8 #
9 # This file is part of Bugs Everywhere.
10 #
11 # Bugs Everywhere is free software: you can redistribute it and/or modify it
12 # under the terms of the GNU General Public License as published by the Free
13 # Software Foundation, either version 2 of the License, or (at your option) any
14 # later version.
15 #
16 # Bugs Everywhere is distributed in the hope that it will be useful, but
17 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
19 # more details.
20 #
21 # You should have received a copy of the GNU General Public License along with
22 # Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.
23
24 """Bazaar_ (bzr) backend.
25
26 .. _Bazaar: http://bazaar.canonical.com/
27 """
28
29 try:
30     import bzrlib
31     import bzrlib.branch
32     import bzrlib.builtins
33     import bzrlib.config
34     import bzrlib.errors
35     import bzrlib.option
36 except ImportError:
37     bzrlib = None
38 import os
39 import os.path
40 import re
41 import shutil
42 import StringIO
43 import sys
44
45 import libbe
46 import base
47
48 if libbe.TESTING == True:
49     import doctest
50     import unittest
51
52
53 def new():
54     return Bzr()
55
56 class Bzr(base.VCS):
57     """:py:class:`base.VCS` implementation for Bazaar.
58     """
59     name = 'bzr'
60     client = None # bzrlib module
61
62     def __init__(self, *args, **kwargs):
63         base.VCS.__init__(self, *args, **kwargs)
64         self.versioned = True
65
66     def _vcs_version(self):
67         if bzrlib == None:
68             return None
69         return bzrlib.__version__
70
71     def _vcs_get_user_id(self):
72         # excerpted from bzrlib.builtins.cmd_whoami.run()
73         try:
74             c = bzrlib.branch.Branch.open_containing(self.repo)[0].get_config()
75         except errors.NotBranchError:
76             c = bzrlib.config.GlobalConfig()
77         return c.username()
78
79     def _vcs_detect(self, path):
80         if self._u_search_parent_directories(path, '.bzr') != None :
81             return True
82         return False
83
84     def _vcs_root(self, path):
85         """Find the root of the deepest repository containing path."""
86         cmd = bzrlib.builtins.cmd_root()
87         cmd.outf = StringIO.StringIO()
88         cmd.run(filename=path)
89         if self.version_cmp(2,2,0) < 0:
90             cmd.cleanup_now()
91         return cmd.outf.getvalue().rstrip('\n')
92
93     def _vcs_init(self, path):
94         cmd = bzrlib.builtins.cmd_init()
95         cmd.outf = StringIO.StringIO()
96         cmd.run(location=path)
97         if self.version_cmp(2,2,0) < 0:
98             cmd.cleanup_now()
99
100     def _vcs_destroy(self):
101         vcs_dir = os.path.join(self.repo, '.bzr')
102         if os.path.exists(vcs_dir):
103             shutil.rmtree(vcs_dir)
104
105     def _vcs_add(self, path):
106         path = os.path.join(self.repo, path)
107         cmd = bzrlib.builtins.cmd_add()
108         cmd.outf = StringIO.StringIO()
109         kwargs = {'file_ids_from': self.repo}
110         if self.repo == os.path.realpath(os.getcwd()):
111             # Work around bzr file locking on Windows.
112             # See: https://lists.ubuntu.com/archives/bazaar/2011q1/071705.html
113             kwargs.pop('file_ids_from')
114         cmd.run(file_list=[path], **kwargs)
115         if self.version_cmp(2,2,0) < 0:
116             cmd.cleanup_now()
117
118     def _vcs_exists(self, path, revision=None):
119         manifest = self._vcs_listdir(
120             self.repo, revision=revision, recursive=True)
121         if path in manifest:
122             return True
123         return False
124
125     def _vcs_remove(self, path):
126         # --force to also remove unversioned files.
127         path = os.path.join(self.repo, path)
128         cmd = bzrlib.builtins.cmd_remove()
129         cmd.outf = StringIO.StringIO()
130         cmd.run(file_list=[path], file_deletion_strategy='force')
131         if self.version_cmp(2,2,0) < 0:
132             cmd.cleanup_now()
133
134     def _vcs_update(self, path):
135         pass
136
137     def _parse_revision_string(self, revision=None):
138         if revision == None:
139             return revision
140         rev_opt = bzrlib.option.Option.OPTIONS['revision']
141         try:
142             rev_spec = rev_opt.type(revision)
143         except bzrlib.errors.NoSuchRevisionSpec:
144             raise base.InvalidRevision(revision)
145         return rev_spec
146
147     def _vcs_get_file_contents(self, path, revision=None):
148         if revision == None:
149             return base.VCS._vcs_get_file_contents(self, path, revision)
150         path = os.path.join(self.repo, path)
151         revision = self._parse_revision_string(revision)
152         cmd = bzrlib.builtins.cmd_cat()
153         cmd.outf = StringIO.StringIO()
154         if self.version_cmp(1,6,0) < 0:
155             # old bzrlib cmd_cat uses sys.stdout not self.outf for output.
156             stdout = sys.stdout
157             sys.stdout = cmd.outf
158         try:
159             cmd.run(filename=path, revision=revision)
160         except bzrlib.errors.BzrCommandError, e:
161             if 'not present in revision' in str(e):
162                 raise base.InvalidPath(path, root=self.repo, revision=revision)
163             raise
164         finally:
165             if self.version_cmp(2,0,0) < 0:
166                 cmd.outf = sys.stdout
167                 sys.stdout = stdout
168             if self.version_cmp(2,2,0) < 0:
169                 cmd.cleanup_now()
170         return cmd.outf.getvalue()
171
172     def _vcs_path(self, id, revision):
173         manifest = self._vcs_listdir(
174             self.repo, revision=revision, recursive=True)
175         return self._u_find_id_from_manifest(id, manifest, revision=revision)
176
177     def _vcs_isdir(self, path, revision):
178         try:
179             self._vcs_listdir(path, revision)
180         except AttributeError, e:
181             if 'children' in str(e):
182                 return False
183             raise
184         return True
185
186     def _vcs_listdir(self, path, revision, recursive=False):
187         path = os.path.join(self.repo, path)
188         revision = self._parse_revision_string(revision)
189         cmd = bzrlib.builtins.cmd_ls()
190         cmd.outf = StringIO.StringIO()
191         try:
192             if self.version_cmp(2,0,0) >= 0:
193                 cmd.run(revision=revision, path=path, recursive=recursive)
194             else:
195                 # Pre-2.0 Bazaar (non_recursive)
196                 # + working around broken non_recursive+path implementation
197                 #   (https://bugs.launchpad.net/bzr/+bug/158690)
198                 cmd.run(revision=revision, path=path,
199                         non_recursive=False)
200         except bzrlib.errors.BzrCommandError, e:
201             if 'not present in revision' in str(e):
202                 raise base.InvalidPath(path, root=self.repo, revision=revision)
203             raise
204         finally:
205             if self.version_cmp(2,2,0) < 0:
206                 cmd.cleanup_now()
207         children = cmd.outf.getvalue().rstrip('\n').splitlines()
208         children = [self._u_rel_path(c, path) for c in children]
209         if self.version_cmp(2,0,0) < 0 and recursive == False:
210             children = [c for c in children if os.path.sep not in c]
211         return children
212
213     def _vcs_commit(self, commitfile, allow_empty=False):
214         cmd = bzrlib.builtins.cmd_commit()
215         cmd.outf = StringIO.StringIO()
216         cwd = os.getcwd()
217         os.chdir(self.repo)
218         try:
219             cmd.run(file=commitfile, unchanged=allow_empty)
220         except bzrlib.errors.BzrCommandError, e:
221             strings = ['no changes to commit.', # bzr 1.3.1
222                        'No changes to commit.'] # bzr 1.15.1
223             if self._u_any_in_string(strings, str(e)) == True:
224                 raise base.EmptyCommit()
225             raise
226         finally:
227             os.chdir(cwd)
228             if self.version_cmp(2,2,0) < 0:
229                 cmd.cleanup_now()
230         return self._vcs_revision_id(-1)
231
232     def _vcs_revision_id(self, index):
233         cmd = bzrlib.builtins.cmd_revno()
234         cmd.outf = StringIO.StringIO()
235         cmd.run(location=self.repo)
236         if self.version_cmp(2,2,0) < 0:
237             cmd.cleanup_now()
238         current_revision = int(cmd.outf.getvalue())
239         if index > current_revision or index < -current_revision:
240             return None
241         if index >= 0:
242             return str(index) # bzr commit 0 is the empty tree.
243         return str(current_revision+index+1)
244
245     def _diff(self, revision):
246         revision = self._parse_revision_string(revision)
247         cmd = bzrlib.builtins.cmd_diff()
248         cmd.outf = StringIO.StringIO()
249         # for some reason, cmd_diff uses sys.stdout not self.outf for output.
250         stdout = sys.stdout
251         sys.stdout = cmd.outf
252         try:
253             status = cmd.run(revision=revision, file_list=[self.repo])
254         finally:
255             sys.stdout = stdout
256             if self.version_cmp(2,2,0) < 0:
257                 cmd.cleanup_now()
258         assert status in [0,1], "Invalid status %d" % status
259         return cmd.outf.getvalue()
260
261     def _parse_diff(self, diff_text):
262         """_parse_diff(diff_text) -> (new,modified,removed)
263
264         `new`, `modified`, and `removed` are lists of files.
265
266         Example diff text::
267
268           === modified file 'dir/changed'
269           --- dir/changed       2010-01-16 01:54:53 +0000
270           +++ dir/changed       2010-01-16 01:54:54 +0000
271           @@ -1,3 +1,3 @@
272            hi
273           -there
274           +everyone and
275            joe
276           
277           === removed file 'dir/deleted'
278           --- dir/deleted       2010-01-16 01:54:53 +0000
279           +++ dir/deleted       1970-01-01 00:00:00 +0000
280           @@ -1,3 +0,0 @@
281           -in
282           -the
283           -beginning
284           
285           === removed file 'dir/moved'
286           --- dir/moved 2010-01-16 01:54:53 +0000
287           +++ dir/moved 1970-01-01 00:00:00 +0000
288           @@ -1,4 +0,0 @@
289           -the
290           -ants
291           -go
292           -marching
293           
294           === added file 'dir/moved2'
295           --- dir/moved2        1970-01-01 00:00:00 +0000
296           +++ dir/moved2        2010-01-16 01:54:34 +0000
297           @@ -0,0 +1,4 @@
298           +the
299           +ants
300           +go
301           +marching
302           
303           === added file 'dir/new'
304           --- dir/new   1970-01-01 00:00:00 +0000
305           +++ dir/new   2010-01-16 01:54:54 +0000
306           @@ -0,0 +1,2 @@
307           +hello
308           +world
309           
310         """
311         new = []
312         modified = []
313         removed = []
314         for line in diff_text.splitlines():
315             if not line.startswith('=== '):
316                 continue
317             fields = line.split()
318             action = fields[1]
319             file = fields[-1].strip("'")
320             if action == 'added':
321                 new.append(file)
322             elif action == 'modified':
323                 modified.append(file)
324             elif action == 'removed':
325                 removed.append(file)
326         return (new,modified,removed)
327
328     def _vcs_changed(self, revision):
329         return self._parse_diff(self._diff(revision))
330
331
332 if libbe.TESTING == True:
333     base.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__])
334
335     unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
336     suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])