Merge lp:~a1s/bzr-externals/breezy into lp:bzr-externals

Proposed by Aleksandr Smyshliaev
Status: Needs review
Proposed branch: lp:~a1s/bzr-externals/breezy
Merge into: lp:bzr-externals
Diff against target: 660 lines (+282/-279)
4 files modified
__init__.py (+9/-9)
commands.py (+16/-18)
externals.py (+254/-249)
setup.py (+3/-3)
To merge this branch: bzr merge lp:~a1s/bzr-externals/breezy
Reviewer Review Type Date Requested Status
Eugene Tarasenko Pending
Review via email: mp+435589@code.launchpad.net

Commit message

Update for Breezy and Python3

Description of the change

- Change imports from bzrlib to breezy
- Use Python3 text file operations to read and write bzrmeta files
- Fix reading externals configuration from an InventoryTree
- Use command "brz ignore" instead of writing to IGNORE_FILENAME (Breezy doesn't export IGNORE_FILENAME)

To post a comment you must log in.

Unmerged revisions

59. By Aleksandr Smyshliaev

Fix adding external working tree to the ignore list

58. By Aleksandr Smyshliaev

Fix reading externals config from a revision tree: the argument for get_file_text() must be a path, not file id

57. By Aleksandr Smyshliaev

Normalize line endings

56. By Aleksandr Smyshliaev

fix import paths in the commands module

55. By Aleksandr Smyshliaev

use Python3 text mode for file operations

54. By Aleksandr Smyshliaev

Change imports from bzrlib to breezy

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '__init__.py'
2--- __init__.py 2012-03-15 02:32:58 +0000
3+++ __init__.py 2023-01-11 14:30:26 +0000
4@@ -56,15 +56,15 @@
5 url directory [revisionspec]
6 """
7
8-from bzrlib.lazy_import import lazy_import
9+from breezy.lazy_import import lazy_import
10 lazy_import(globals(), """
11-from bzrlib.plugins.externals import externals as lazy_externals
12-from bzrlib.plugins.externals import commands as lazy_commands
13+from breezy.plugins.externals import externals as lazy_externals
14+from breezy.plugins.externals import commands as lazy_commands
15 """)
16
17-from bzrlib.branch import Branch
18-from bzrlib.mutabletree import MutableTree
19-from bzrlib.commands import Command, plugin_cmds
20+from breezy.branch import Branch
21+from breezy.mutabletree import MutableTree
22+from breezy.commands import Command, plugin_cmds
23
24 plugin_name = 'externals'
25 version_info = (1, 3, 4, 'dev', 2)
26@@ -115,7 +115,7 @@
27 install_hooks()
28
29 def register_commands():
30- module = 'bzrlib.plugins.externals.commands'
31+ module = 'breezy.plugins.externals.commands'
32 plugin_cmds.register_lazy('cmd_externals_add',
33 ['eadd'], module)
34 plugin_cmds.register_lazy('cmd_externals_command',
35@@ -135,9 +135,9 @@
36 def new_finished(self):
37 old_finished(self)
38 # clear line
39- from bzrlib.ui import ui_factory
40+ from breezy.ui import ui_factory
41 ui_factory.clear_term()
42
43-from bzrlib.progress import ProgressTask
44+from breezy.progress import ProgressTask
45 old_finished = ProgressTask.finished
46 ProgressTask.finished = new_finished
47
48=== modified file 'commands.py'
49--- commands.py 2012-03-15 02:32:58 +0000
50+++ commands.py 2023-01-11 14:30:26 +0000
51@@ -16,14 +16,14 @@
52
53 import os.path
54
55-from bzrlib.osutils import getcwd, get_user_encoding, isdir, isfile, pathjoin, relpath
56-from bzrlib.commands import Command, run_bzr_catch_user_errors
57-from bzrlib.option import Option
58-from bzrlib.trace import is_quiet, note
59-from bzrlib.bzrdir import BzrDir
60-from bzrlib.workingtree import WorkingTree
61+from breezy.osutils import getcwd, get_user_encoding, isdir, isfile, pathjoin, relpath
62+from breezy.commands import Command, run_bzr_catch_user_errors
63+from breezy.option import Option
64+from breezy.trace import is_quiet, note
65+from breezy.bzr.bzrdir import BzrDir
66+from breezy.workingtree import WorkingTree
67
68-import externals
69+from . import externals
70
71 _main_cwd = getcwd()
72 _main_base = None
73@@ -92,12 +92,10 @@
74 self._add_to_file(root, externals.SNAPSHOT_PATH, line)
75
76 # add ignore mask
77- from bzrlib import IGNORE_FILENAME
78- self._add_to_file(root, IGNORE_FILENAME, './' + to_location)
79+ run_bzr_catch_user_errors(['ignore', './' + to_location])
80
81 # add config files to repository
82 cmd = ['add',
83- '.bzrignore',
84 '.bzrmeta/externals',
85 '.bzrmeta/externals-snapshot']
86 run_bzr_catch_user_errors(cmd)
87@@ -112,7 +110,7 @@
88 content = ''
89 path = pathjoin(root, file_name)
90 if isfile(path):
91- f = open(path, 'rU')
92+ f = open(path, 'rt', encoding='utf-8')
93 try:
94 content = f.read()
95 finally:
96@@ -121,8 +119,8 @@
97 # add at end of file the char '\n' if needed
98 content += '\n'
99
100- content += line.encode('utf-8') + '\n'
101- f = open(path, 'w')
102+ content += line + '\n'
103+ f = open(path, 'wt', encoding='utf-8')
104 try:
105 f.write(content)
106 finally:
107@@ -210,10 +208,10 @@
108 def _substitute_in_commandlist(self, command_list, rel_path):
109 return [x.replace('{relpath}', rel_path) for x in command_list]
110
111-import bzrlib.builtins
112+import breezy.builtins
113
114-class cmd_branch(bzrlib.builtins.cmd_branch):
115- __doc__ = bzrlib.builtins.cmd_branch.__doc__
116+class cmd_branch(breezy.builtins.cmd_branch):
117+ __doc__ = breezy.builtins.cmd_branch.__doc__
118
119 def plugin_name(self):
120 return None
121@@ -222,8 +220,8 @@
122 externals.command_kwargs = kwargs
123 super(cmd_branch, self).run(**kwargs)
124
125-class cmd_checkout(bzrlib.builtins.cmd_checkout):
126- __doc__ = bzrlib.builtins.cmd_checkout.__doc__
127+class cmd_checkout(breezy.builtins.cmd_checkout):
128+ __doc__ = breezy.builtins.cmd_checkout.__doc__
129
130 def plugin_name(self):
131 return None
132
133=== modified file 'externals.py'
134--- externals.py 2012-01-20 00:19:27 +0000
135+++ externals.py 2023-01-11 14:30:26 +0000
136@@ -1,249 +1,254 @@
137-# Copyright (C) 2009 Eugene Tarasenko, Alexander Belchenko
138-#
139-# This program is free software; you can redistribute it and/or modify
140-# it under the terms of the GNU General Public License as published by
141-# the Free Software Foundation; either version 2 of the License, or
142-# (at your option) any later version.
143-#
144-# This program is distributed in the hope that it will be useful,
145-# but WITHOUT ANY WARRANTY; without even the implied warranty of
146-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
147-# GNU General Public License for more details.
148-#
149-# You should have received a copy of the GNU General Public License
150-# along with this program; if not, write to the Free Software
151-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
152-
153-import os
154-
155-from bzrlib.urlutils import join, local_path_from_url
156-from bzrlib.osutils import check_legal_path, getcwd, get_user_encoding, isdir, isfile, pathjoin, relpath
157-from bzrlib.commands import run_bzr_catch_user_errors
158-from bzrlib.cmdline import split
159-from bzrlib.trace import is_quiet, is_verbose, note, warning
160-
161-CONFIG_PATH = '.bzrmeta/externals'
162-SNAPSHOT_PATH = '.bzrmeta/externals-snapshot'
163-
164-disable_hooks = False # for prevent double running external command
165-command_kwargs = None # each new command changed value
166-
167-class Externals:
168-
169- def __init__(self, branch, revid, root=None):
170- self.branch = branch
171- self.revid = revid
172- if root is not None:
173- self.root = root
174- else:
175- self.root = local_path_from_url(branch.base)
176- self.cwd = getcwd()
177- self.config = []
178- self.bound = True
179- self.use_snapshot = command_kwargs and 'revision' in command_kwargs
180-
181- def _set_config(self, text):
182- lines = text.splitlines()
183- for line in lines:
184- if len(line.strip()) > 0 and not line.startswith('#'):
185- # function cmdline.split does not support windows path separator '\'
186- line = line.decode('utf-8').replace('\\', '/')
187- arg = split(line)
188- if arg not in self.config:
189- self.config.append(arg)
190-
191- def read_config(self):
192- path = pathjoin(self.root, CONFIG_PATH)
193- if self.use_snapshot or not isfile(path):
194- # config file not exist in directory
195- return False
196- f = open(path, 'rU')
197- try:
198- self._set_config(f.read())
199- finally:
200- f.close()
201- return len(self.config) > 0
202-
203- def read_config_from_repository(self):
204- rev_tree = self.branch.repository.revision_tree(self.revid)
205- if self.use_snapshot:
206- file_id = rev_tree.path2id(SNAPSHOT_PATH)
207- if not file_id:
208- file_id = rev_tree.path2id(CONFIG_PATH)
209- if file_id:
210- warning('warning: for this revision there is no snapshot of external branches!')
211- else:
212- file_id = rev_tree.path2id(CONFIG_PATH)
213- if not file_id:
214- # there is no config or snapshot files in repository
215- return False
216- rev_tree.lock_read()
217- try:
218- text = rev_tree.get_file_text(file_id)
219- self._set_config(text)
220- finally:
221- rev_tree.unlock()
222- return len(self.config) > 0
223-
224- def _relpath(self, path):
225- try:
226- return relpath(self.cwd, path)
227- except:
228- pass
229- return path
230-
231- @staticmethod
232- def _relurljoin(base, relative):
233- if relative.startswith('//'):
234- # urlutils.join not supports relative urls start with '//'
235- scheme = base.partition('://')
236- return scheme[0] + ':' + relative
237- else:
238- return join(base, relative)
239-
240- @staticmethod
241- def _report(cmd):
242- if not is_quiet():
243- note('External ' + ' '.join(cmd))
244-
245- @staticmethod
246- def adjust_verbosity(cmd):
247- if is_quiet():
248- cmd += ['-q']
249- if is_verbose():
250- cmd += ['-v']
251-
252- def branch_iterator(self, target_root=None):
253- if len(self.config) == 0:
254- # branch not have externals configuration
255- return
256-
257- self.bound = True
258- if not target_root:
259- target_root = self.branch.get_bound_location()
260- if not target_root:
261- self.bound = False
262- target_root = self.branch.get_parent()
263- if not target_root:
264- # support new braches with no parent yet
265- target_root = self.branch.base
266-
267- for arg in self.config: # url directory [revisionspec]
268- location = self._relurljoin(target_root, arg[0])
269- if target_root.startswith('file:///'):
270- # try to pull externals from the parent for the feature branch
271- path = pathjoin(local_path_from_url(target_root), arg[1])
272- if isdir(path):
273- location = self._relpath(path)
274- else:
275- # parent is local master branch
276- if location.startswith('file:///'):
277- location = self._relpath(local_path_from_url(location))
278-
279- check_legal_path(arg[1])
280- rel_path = self._relpath(pathjoin(self.root, arg[1]))
281-
282- revision = None
283- if len(arg) > 2:
284- revision = arg[2]
285- yield location, rel_path, revision
286-
287- def pull(self):
288- if disable_hooks:
289- return
290-
291- # need use merged config from repository and working tree
292- # because new added external branch from repository or working tree
293- # need pull/update for correct snapshot
294- self.read_config()
295- self.read_config_from_repository()
296- if len(self.config) == 0:
297- return
298-
299- for location, path, revision in self.branch_iterator():
300- if location == path:
301- # not create feature branch for directory above the root
302- continue
303-
304- # select what do it
305- if isdir(pathjoin(path, '.bzr')) or isdir(pathjoin(path, '.svn')):
306- if self.bound:
307- cmd = ['update', path]
308- else:
309- cmd = ['pull', location, '--directory', path]
310- else:
311- if self.bound:
312- cmd = ['checkout', location, path]
313- else:
314- cmd = ['branch', location, path]
315-
316- # command branch don't create recursive directory
317- dirs = path.rpartition('/')
318- if dirs[0] != '' and not isdir(dirs[0]):
319- os.makedirs(dirs[0].encode(get_user_encoding()))
320-
321- # if use revision options but not for 'update'
322- if revision is not None and cmd[0] != 'update':
323- cmd += ['--revision', revision]
324-
325- self.adjust_verbosity(cmd)
326- self._report(cmd)
327- run_bzr_catch_user_errors(cmd)
328-
329- def push(self, target):
330- if disable_hooks or not self.read_config():
331- return
332-
333- for location, path, revision in self.branch_iterator(target):
334- if location == path:
335- # don't push into itself
336- continue
337-
338- # XXX: maybe he should rather decorate the push command
339- # so that we can get the commandline args
340- # alternatively the plugin infrastructure must provide it to us?!
341- cmd = ['push', location, '--directory', path, '--no-strict']
342-
343- if revision is not None:
344- # not push if use revision
345- continue
346-
347- self.adjust_verbosity(cmd)
348- self._report(cmd)
349- run_bzr_catch_user_errors(cmd)
350-
351- @staticmethod
352- def _quoted_if_need(text):
353- if text.find(' ') != -1:
354- text = '"' + text + '"'
355- return text
356-
357- def commit(self, mutable_tree):
358- # BUG: not run recursively if in above branch not have changes
359- if disable_hooks or not self.read_config():
360- return
361-
362- from bzrlib.workingtree import WorkingTree
363- snapshot = []
364- for arg in self.config: # url directory [revisionspec]
365- wt = WorkingTree.open(pathjoin(self.root, arg[1]))
366- if wt.has_changes(wt.basis_tree()):
367- cmd = ['ci']
368- os.chdir(wt.basedir)
369- try:
370- run_bzr_catch_user_errors(cmd)
371- finally:
372- os.chdir(self.cwd)
373-
374- if len(arg) < 3:
375- arg.append('')
376- arg[2] = 'revid:' + wt.last_revision()
377- arg[1] = self._quoted_if_need(arg[1])
378- snapshot.append(' '.join(arg).encode('utf-8'))
379-
380- path = pathjoin(self.root, SNAPSHOT_PATH)
381- f = open(path, 'w')
382- try:
383- f.write('\n'.join(snapshot))
384- finally:
385- f.close()
386+# Copyright (C) 2009 Eugene Tarasenko, Alexander Belchenko
387+#
388+# This program is free software; you can redistribute it and/or modify
389+# it under the terms of the GNU General Public License as published by
390+# the Free Software Foundation; either version 2 of the License, or
391+# (at your option) any later version.
392+#
393+# This program is distributed in the hope that it will be useful,
394+# but WITHOUT ANY WARRANTY; without even the implied warranty of
395+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
396+# GNU General Public License for more details.
397+#
398+# You should have received a copy of the GNU General Public License
399+# along with this program; if not, write to the Free Software
400+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
401+
402+import os
403+
404+from breezy.urlutils import join, local_path_from_url
405+from breezy.osutils import check_legal_path, getcwd, get_user_encoding, isdir, isfile, pathjoin, relpath
406+from breezy.commands import run_bzr_catch_user_errors
407+from breezy.cmdline import split
408+from breezy.trace import is_quiet, is_verbose, note, warning
409+
410+CONFIG_PATH = '.bzrmeta/externals'
411+SNAPSHOT_PATH = '.bzrmeta/externals-snapshot'
412+
413+disable_hooks = False # for prevent double running external command
414+command_kwargs = None # each new command changed value
415+
416+class Externals:
417+
418+ def __init__(self, branch, revid, root=None):
419+ self.branch = branch
420+ self.revid = revid
421+ if root is not None:
422+ self.root = root
423+ else:
424+ self.root = local_path_from_url(branch.base)
425+ self.cwd = getcwd()
426+ self.config = []
427+ self.bound = True
428+ self.use_snapshot = command_kwargs and 'revision' in command_kwargs
429+
430+ def _set_config(self, text):
431+ lines = text.splitlines()
432+ for line in lines:
433+ if len(line.strip()) > 0 and not line.startswith('#'):
434+ # function cmdline.split does not support windows path separator '\'
435+ line = line.replace('\\', '/')
436+ arg = split(line)
437+ if arg not in self.config:
438+ self.config.append(arg)
439+
440+ def read_config(self):
441+ path = pathjoin(self.root, CONFIG_PATH)
442+ if self.use_snapshot or not isfile(path):
443+ # config file not exist in directory
444+ return False
445+ f = open(path, 'rt', encoding='utf-8')
446+ try:
447+ self._set_config(f.read())
448+ finally:
449+ f.close()
450+ return len(self.config) > 0
451+
452+ def read_config_from_repository(self):
453+ rev_tree = self.branch.repository.revision_tree(self.revid)
454+ if self.use_snapshot:
455+ if rev_tree.is_versioned(SNAPSHOT_PATH):
456+ cfg_path = SHAPSHOT_PATH
457+ else:
458+ cfg_path = CONFIG_PATH
459+ if rev_tree.is_versioned(cfg_path):
460+ warning('warning: for this revision there is no snapshot of external branches!')
461+ else:
462+ cfg_path = None
463+ else:
464+ cfg_path = CONFIG_PATH
465+ if not rev_tree.is_versioned(cfg_path):
466+ cfg_path = None
467+ if not cfg_path:
468+ # there is no config or snapshot files in repository
469+ return False
470+ rev_tree.lock_read()
471+ try:
472+ text = rev_tree.get_file_text(cfg_path)
473+ self._set_config(text.decode('utf-8'))
474+ finally:
475+ rev_tree.unlock()
476+ return len(self.config) > 0
477+
478+ def _relpath(self, path):
479+ try:
480+ return relpath(self.cwd, path)
481+ except:
482+ pass
483+ return path
484+
485+ @staticmethod
486+ def _relurljoin(base, relative):
487+ if relative.startswith('//'):
488+ # urlutils.join not supports relative urls start with '//'
489+ scheme = base.partition('://')
490+ return scheme[0] + ':' + relative
491+ else:
492+ return join(base, relative)
493+
494+ @staticmethod
495+ def _report(cmd):
496+ if not is_quiet():
497+ note('External ' + ' '.join(cmd))
498+
499+ @staticmethod
500+ def adjust_verbosity(cmd):
501+ if is_quiet():
502+ cmd += ['-q']
503+ if is_verbose():
504+ cmd += ['-v']
505+
506+ def branch_iterator(self, target_root=None):
507+ if len(self.config) == 0:
508+ # branch not have externals configuration
509+ return
510+
511+ self.bound = True
512+ if not target_root:
513+ target_root = self.branch.get_bound_location()
514+ if not target_root:
515+ self.bound = False
516+ target_root = self.branch.get_parent()
517+ if not target_root:
518+ # support new braches with no parent yet
519+ target_root = self.branch.base
520+
521+ for arg in self.config: # url directory [revisionspec]
522+ location = self._relurljoin(target_root, arg[0])
523+ if target_root.startswith('file:///'):
524+ # try to pull externals from the parent for the feature branch
525+ path = pathjoin(local_path_from_url(target_root), arg[1])
526+ if isdir(path):
527+ location = self._relpath(path)
528+ else:
529+ # parent is local master branch
530+ if location.startswith('file:///'):
531+ location = self._relpath(local_path_from_url(location))
532+
533+ check_legal_path(arg[1])
534+ rel_path = self._relpath(pathjoin(self.root, arg[1]))
535+
536+ revision = None
537+ if len(arg) > 2:
538+ revision = arg[2]
539+ yield location, rel_path, revision
540+
541+ def pull(self):
542+ if disable_hooks:
543+ return
544+
545+ # need use merged config from repository and working tree
546+ # because new added external branch from repository or working tree
547+ # need pull/update for correct snapshot
548+ self.read_config()
549+ self.read_config_from_repository()
550+ if len(self.config) == 0:
551+ return
552+
553+ for location, path, revision in self.branch_iterator():
554+ if location == path:
555+ # not create feature branch for directory above the root
556+ continue
557+
558+ # select what do it
559+ if isdir(pathjoin(path, '.bzr')) or isdir(pathjoin(path, '.svn')):
560+ if self.bound:
561+ cmd = ['update', path]
562+ else:
563+ cmd = ['pull', location, '--directory', path]
564+ else:
565+ if self.bound:
566+ cmd = ['checkout', location, path]
567+ else:
568+ cmd = ['branch', location, path]
569+
570+ # command branch don't create recursive directory
571+ dirs = path.rpartition('/')
572+ if dirs[0] != '' and not isdir(dirs[0]):
573+ os.makedirs(dirs[0].encode(get_user_encoding()))
574+
575+ # if use revision options but not for 'update'
576+ if revision is not None and cmd[0] != 'update':
577+ cmd += ['--revision', revision]
578+
579+ self.adjust_verbosity(cmd)
580+ self._report(cmd)
581+ run_bzr_catch_user_errors(cmd)
582+
583+ def push(self, target):
584+ if disable_hooks or not self.read_config():
585+ return
586+
587+ for location, path, revision in self.branch_iterator(target):
588+ if location == path:
589+ # don't push into itself
590+ continue
591+
592+ # XXX: maybe he should rather decorate the push command
593+ # so that we can get the commandline args
594+ # alternatively the plugin infrastructure must provide it to us?!
595+ cmd = ['push', location, '--directory', path, '--no-strict']
596+
597+ if revision is not None:
598+ # not push if use revision
599+ continue
600+
601+ self.adjust_verbosity(cmd)
602+ self._report(cmd)
603+ run_bzr_catch_user_errors(cmd)
604+
605+ @staticmethod
606+ def _quoted_if_need(text):
607+ if text.find(' ') != -1:
608+ text = '"' + text + '"'
609+ return text
610+
611+ def commit(self, mutable_tree):
612+ # BUG: not run recursively if in above branch not have changes
613+ if disable_hooks or not self.read_config():
614+ return
615+
616+ from breezy.workingtree import WorkingTree
617+ snapshot = []
618+ for arg in self.config: # url directory [revisionspec]
619+ wt = WorkingTree.open(pathjoin(self.root, arg[1]))
620+ if wt.has_changes(wt.basis_tree()):
621+ cmd = ['ci']
622+ os.chdir(wt.basedir)
623+ try:
624+ run_bzr_catch_user_errors(cmd)
625+ finally:
626+ os.chdir(self.cwd)
627+
628+ if len(arg) < 3:
629+ arg.append('')
630+ arg[2] = 'revid:' + wt.last_revision()
631+ arg[1] = self._quoted_if_need(arg[1])
632+ snapshot.append(' '.join(arg))
633+
634+ path = pathjoin(self.root, SNAPSHOT_PATH)
635+ f = open(path, 'wt', encoding='utf-8')
636+ try:
637+ f.write('\n'.join(snapshot))
638+ finally:
639+ f.close()
640
641=== modified file 'setup.py'
642--- setup.py 2010-03-18 12:25:55 +0000
643+++ setup.py 2023-01-11 14:30:26 +0000
644@@ -22,7 +22,7 @@
645 bzr_plugin_name = __init__.plugin_name
646 bzr_commands = ['externals-add', 'externals-command']
647 bzr_plugin_version = __init__.version_info
648-bzr_minimum_version = (1, 4, 0)
649+bzr_minimum_version = (3, 0, 0)
650 bzr_maximum_version = None
651
652 if __name__ == '__main__':
653@@ -34,5 +34,5 @@
654 author_email="eugene.tarasenko@gmail.com",
655 license="GNU GPL v2",
656 url="https://launchpad.net/bzr-externals",
657- packages=['bzrlib.plugins.externals', ],
658- package_dir={'bzrlib.plugins.externals': '.'})
659+ packages=['breezy.plugins.externals', ],
660+ package_dir={'breezy.plugins.externals': '.'})

Subscribers

People subscribed via source and target branches