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 # James Rowe <jnrowe@ukfsn.org>
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 Free
12 # Software Foundation, either version 2 of the License, or (at your option) any
15 # Bugs Everywhere is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
20 # You should have received a copy of the GNU General Public License along with
21 # Bugs Everywhere. If not, see <http://www.gnu.org/licenses/>.
23 """GNU Arch_ (tla) backend.
25 .. _Arch: http://www.gnu.org/software/gnu-arch/
34 import time # work around http://mercurial.selenic.com/bts/issue618
37 from ...ui.util import user as _user
38 from ...util.id import uuid_gen
39 from ...util.subproc import CommandError
40 from ..util import config as _config
43 if libbe.TESTING == True:
48 class CantAddFile(Exception):
49 def __init__(self, file):
51 Exception.__init__(self, "Can't automatically add file %s" % file)
53 DEFAULT_CLIENT = 'tla'
55 client = _config.get_val(
56 'arch_client', default=DEFAULT_CLIENT)
62 """:class:`base.VCS` implementation for GNU Arch.
71 _arch_paramdir = os.path.expanduser('~/.arch-params')
73 def __init__(self, *args, **kwargs):
74 base.VCS.__init__(self, *args, **kwargs)
76 self.interspersed_vcs_files = True
78 self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618
80 def _vcs_version(self):
82 status,output,error = self._u_invoke_client('--version')
83 except CommandError: # command not found?
85 version = '\n'.join(output.splitlines()[:2])
88 def _vcs_detect(self, path):
89 """Detect whether a directory is revision-controlled using Arch"""
90 if self._u_search_parent_directories(path, '{arch}') != None :
91 _config.set_val('arch_client', client)
95 def _vcs_init(self, path):
96 self._create_archive(path)
97 self._create_project(path)
98 self._add_project_code(path)
100 def _create_archive(self, path):
101 """Create a temporary Arch archive in the directory PATH. This
102 archive will be removed by::
104 destroy->_vcs_destroy->_remove_archive
106 # http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive
107 assert self._archive_name == None
108 id = self.get_user_id()
109 name, email = _user.parse_user_id(id)
111 email = '%s@example.com' % name
112 trailer = '%s-%s' % ('bugs-everywhere-auto', uuid_gen()[0:8])
113 self._archive_name = '%s--%s' % (email, trailer)
114 self._archive_dir = '/tmp/%s' % trailer
115 self._tmp_archive = True
116 self._u_invoke_client('make-archive', self._archive_name,
117 self._archive_dir, cwd=path)
119 def _invoke_client(self, *args, **kwargs):
120 """Invoke the client on our archive.
122 assert self._archive_name != None
128 arglist = [command, '-A', self._archive_name]
129 arglist.extend(tailargs)
130 args = tuple(arglist)
131 return self._u_invoke_client(*args, **kwargs)
133 def _remove_archive(self):
134 assert self._tmp_archive == True
135 assert self._archive_dir != None
136 assert self._archive_name != None
137 os.remove(os.path.join(self._arch_paramdir,
138 '=locations', self._archive_name))
139 shutil.rmtree(self._archive_dir)
140 self._tmp_archive = False
141 self._archive_dir = False
142 self._archive_name = False
144 def _create_project(self, path):
146 Create a temporary Arch project in the directory PATH. This
147 project will be removed by
148 destroy->_vcs_destroy->_remove_project
150 # http://mwolson.org/projects/GettingStartedWithArch.html
151 # http://regexps.srparish.net/tutorial-tla/new-project.html#Starting_a_New_Project
152 category = 'bugs-everywhere'
155 self._project_name = '%s--%s--%s' % (category, branch, version)
156 self._invoke_client('archive-setup', self._project_name,
158 self._tmp_project = True
160 def _remove_project(self):
161 assert self._tmp_project == True
162 assert self._project_name != None
163 assert self._archive_dir != None
164 shutil.rmtree(os.path.join(self._archive_dir, self._project_name))
165 self._tmp_project = False
166 self._project_name = False
168 def _archive_project_name(self):
169 assert self._archive_name != None
170 assert self._project_name != None
171 return '%s/%s' % (self._archive_name, self._project_name)
173 def _adjust_naming_conventions(self, path):
174 """Adjust `Arch naming conventions`_ so ``.be`` is considered source
177 By default, Arch restricts source code filenames to::
181 Since our bug directory ``.be`` doesn't satisfy these conventions,
182 we need to adjust them. The conventions are specified in::
184 project-root/{arch}/=tagging-method
186 .. _Arch naming conventions:
187 http://regexps.srparish.net/tutorial-tla/naming-conventions.html
189 tagpath = os.path.join(path, '{arch}', '=tagging-method')
191 f = codecs.open(tagpath, 'r', self.encoding)
193 if line.startswith('source '):
194 lines_out.append('source ^[._=a-zA-X0-9].*$\n')
196 lines_out.append(line)
198 f = codecs.open(tagpath, 'w', self.encoding)
199 f.write(''.join(lines_out))
202 def _add_project_code(self, path):
203 # http://mwolson.org/projects/GettingStartedWithArch.html
204 # http://regexps.srparish.net/tutorial-tla/new-source.html
205 # http://regexps.srparish.net/tutorial-tla/importing-first.html
206 self._invoke_client('init-tree', self._project_name,
208 self._adjust_naming_conventions(path)
209 self._invoke_client('import', '--summary', 'Began versioning',
212 def _vcs_destroy(self):
213 if self._tmp_project == True:
214 self._remove_project()
215 if self._tmp_archive == True:
216 self._remove_archive()
217 vcs_dir = os.path.join(self.repo, '{arch}')
218 if os.path.exists(vcs_dir):
219 shutil.rmtree(vcs_dir)
220 self._archive_name = None
222 def _vcs_root(self, path):
223 if not os.path.isdir(path):
224 dirname = os.path.dirname(path)
227 status,output,error = self._u_invoke_client('tree-root', dirname)
228 root = output.rstrip('\n')
230 self._get_archive_project_name(root)
234 def _get_archive_name(self, root):
235 status,output,error = self._u_invoke_client('archives')
236 lines = output.split('\n')
238 # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52
239 # /tmp/BEtestXXXXXX/rootdir
241 for archive,location in zip(lines[::2], lines[1::2]):
242 if os.path.realpath(location) == os.path.realpath(root):
243 self._archive_name = archive
244 assert self._archive_name != None
246 def _get_archive_project_name(self, root):
248 status,output,error = self._u_invoke_client('tree-version', cwd=root)
250 # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52/be--mainline--0.1
251 archive_name,project_name = output.rstrip('\n').split('/')
252 self._archive_name = archive_name
253 self._project_name = project_name
255 def _vcs_get_user_id(self):
257 status,output,error = self._u_invoke_client('my-id')
258 return output.rstrip('\n')
260 if 'no arch user id set' in e.args[0]:
265 def _vcs_add(self, path):
266 self._u_invoke_client('add-id', path)
267 realpath = os.path.realpath(self._u_abspath(path))
268 pathAdded = realpath in self._list_added(self.repo)
269 if self.paranoid and not pathAdded:
270 self._force_source(path)
272 def _list_added(self, root):
273 assert os.path.exists(root)
274 assert os.access(root, os.X_OK)
275 root = os.path.realpath(root)
276 status,output,error = self._u_invoke_client('inventory', '--source',
277 '--both', '--all', root)
278 inv_str = output.rstrip('\n')
279 return [os.path.join(root, p) for p in inv_str.split('\n')]
281 def _add_dir_rule(self, rule, dirname, root):
282 inv_path = os.path.join(dirname, '.arch-inventory')
283 f = codecs.open(inv_path, 'a', self.encoding)
286 if os.path.realpath(inv_path) not in self._list_added(root):
287 paranoid = self.paranoid
288 self.paranoid = False
290 self.paranoid = paranoid
292 def _force_source(self, path):
293 rule = 'source %s\n' % self._u_rel_path(path)
294 self._add_dir_rule(rule, os.path.dirname(path), self.repo)
295 if os.path.realpath(path) not in self._list_added(self.repo):
296 raise CantAddFile(path)
298 def _vcs_remove(self, path):
299 if self._vcs_is_versioned(path):
300 self._u_invoke_client('delete-id', path)
301 arch_ids = os.path.join(self.repo, path, '.arch-ids')
302 if os.path.exists(arch_ids):
303 shutil.rmtree(arch_ids)
305 def _vcs_update(self, path):
306 self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618
308 def _vcs_is_versioned(self, path):
309 if '.arch-ids' in path:
313 def _vcs_get_file_contents(self, path, revision=None):
315 return base.VCS._vcs_get_file_contents(self, path, revision)
317 relpath = self._file_find(path, revision, relpath=True)
318 return base.VCS._vcs_get_file_contents(self, relpath)
320 def _file_find(self, path, revision, relpath=False):
322 status,output,error = \
324 'file-find', '--unescaped', path, revision)
325 path = output.rstrip('\n').splitlines()[-1]
326 except CommandError, e:
328 and 'illegally formed changeset index' in e.stderr:
329 raise NotImplementedError(
330 """Outstanding tla bug, see
331 https://bugs.launchpad.net/ubuntu/+source/tla/+bug/513472
336 return os.path.abspath(os.path.join(self.repo, path))
338 def _vcs_path(self, id, revision):
339 return self._u_find_id(id, revision)
341 def _vcs_isdir(self, path, revision):
342 abspath = self._file_find(path, revision)
343 return os.path.isdir(abspath)
345 def _vcs_listdir(self, path, revision):
346 abspath = self._file_find(path, revision)
347 return [p for p in os.listdir(abspath) if self._vcs_is_versioned(p)]
349 def _vcs_commit(self, commitfile, allow_empty=False):
350 if allow_empty == False:
351 # arch applies empty commits without complaining, so check first
352 status,output,error = self._u_invoke_client('changes',expect=(0,1))
354 # work around http://mercurial.selenic.com/bts/issue618
356 for path in self.__updated:
357 os.utime(os.path.join(self.repo, path), None)
359 status,output,error = self._u_invoke_client('changes',expect=(0,1))
362 raise base.EmptyCommit()
363 summary,body = self._u_parse_commitfile(commitfile)
364 args = ['commit', '--summary', summary]
366 args.extend(['--log-message',body])
367 status,output,error = self._u_invoke_client(*args)
369 revline = re.compile('[*] committed (.*)')
370 match = revline.search(output)
371 assert match != None, output+error
372 assert len(match.groups()) == 1
373 revpath = match.groups()[0]
374 assert not " " in revpath, revpath
375 assert revpath.startswith(self._archive_project_name()+'--')
376 revision = revpath[len(self._archive_project_name()+'--'):]
379 def _vcs_revision_id(self, index):
380 status,output,error = self._u_invoke_client('logs')
381 logs = output.splitlines()
382 first_log = logs.pop(0)
383 assert first_log == 'base-0', first_log
393 return '%s--%s' % (self._archive_project_name(), log)
395 def _diff(self, revision):
396 status,output,error = self._u_invoke_client(
397 'diff', '--summary', '--unescaped', revision, expect=(0,1))
400 def _parse_diff(self, diff_text):
404 * local directory is at ...
405 * build pristine tree for ...
406 * from import revision: ...
407 * patching for revision: ...
409 D .be/dir/bugs/.arch-ids/moved.id
410 D .be/dir/bugs/.arch-ids/removed.id
412 D .be/dir/bugs/removed
413 A .be/dir/bugs/.arch-ids/moved2.id
414 A .be/dir/bugs/.arch-ids/new.id
415 A .be/dir/bugs/moved2
417 A {arch}/bugs-everywhere/bugs-everywhere--mainline/...
418 M .be/dir/bugs/modified
423 lines = diff_text.splitlines()
424 for i,line in enumerate(lines):
425 if line.startswith('* ') or '/.arch-ids/' in line:
427 change,file = line.split(' ',1)
428 if file.startswith('{arch}/'):
433 modified.append(file)
436 return (new,modified,removed)
438 def _vcs_changed(self, revision):
439 return self._parse_diff(self._diff(revision))
442 if libbe.TESTING == True:
443 base.make_vcs_testcase_subclasses(Arch, sys.modules[__name__])
445 unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
446 suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])