1
# Copyright (C) 2005, 2006 by Aaron Bentley
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from bzrlib.bzrdir import BzrDir
20
import bzrlib.bzrdir as bzrdir
21
from bzrlib.errors import (BzrError,
28
from bzrlib.branch import Branch
29
from bzrlib.commit import Commit, NullCommitReporter
30
from bzrlib.commands import Command
31
from bzrlib.option import _global_option, Option
32
from bzrlib.merge import merge_inner
33
from bzrlib.revision import NULL_REVISION
34
from bzrlib.tree import EmptyTree
36
from bzrlib.workingtree import WorkingTree
37
from errors import NoPyBaz
41
from pybaz import NameParser as NameParser
42
from pybaz.backends.baz import null_cmd
45
from fai import iter_new_merges, direct_merges
53
import bzrlib.inventory
57
from progress import *
59
class ImportCommitReporter(NullCommitReporter):
60
def __init__(self, pb):
63
def escaped(self, escape_count, message):
65
bzrlib.trace.warning("replaced %d control characters in message" %
68
def add_id(files, id=None):
69
"""Adds an explicit id to a list of files.
71
:param files: the name of the file to add an id to
72
:type files: list of str
73
:param id: tag one file using the specified id, instead of generating id
78
args.extend(["--id", id])
84
def make_archive(name, location):
85
pb_location = pybaz.ArchiveLocation(location)
86
pb_location.create_master(pybaz.Archive(name),
87
pybaz.ArchiveLocationParams())
91
>>> q = test_environ()
94
>>> os.path.exists(os.path.join(q, "home", ".arch-params"))
96
>>> teardown_environ(q)
101
saved_dir = os.getcwdu()
102
tdir = tempfile.mkdtemp(prefix="testdir-")
103
os.environ["HOME"] = os.path.join(tdir, "home")
104
os.mkdir(os.environ["HOME"])
105
arch_dir = os.path.join(tdir, "archive_dir")
106
make_archive("test@example.com", arch_dir)
107
work_dir = os.path.join(tdir, "work_dir")
110
pybaz.init_tree(work_dir, "test@example.com/test--test--0")
111
lib_dir = os.path.join(tdir, "lib_dir")
113
pybaz.register_revision_library(lib_dir)
114
pybaz.set_my_id("Test User<test@example.org>")
117
def add_file(path, text, id):
119
>>> q = test_environ()
120
>>> add_file("path with space", "text", "lalala")
121
>>> tree = pybaz.tree_root(".")
122
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
123
>>> ("x_lalala", "path with space") in inv
125
>>> teardown_environ(q)
127
file(path, "wb").write(text)
131
def add_dir(path, id):
133
>>> q = test_environ()
134
>>> add_dir("path with\(sp) space", "lalala")
135
>>> tree = pybaz.tree_root(".")
136
>>> inv = list(tree.iter_inventory_ids(source=True, both=True))
137
>>> ("x_lalala", "path with\(sp) space") in inv
139
>>> teardown_environ(q)
144
def teardown_environ(tdir):
148
def timport(tree, summary):
149
msg = tree.log_message()
150
msg["summary"] = summary
153
def commit(tree, summary):
155
>>> q = test_environ()
156
>>> tree = pybaz.tree_root(".")
157
>>> timport(tree, "import")
158
>>> commit(tree, "commit")
159
>>> logs = [str(l.revision) for l in tree.iter_logs()]
163
'test@example.com/test--test--0--base-0'
165
'test@example.com/test--test--0--patch-1'
166
>>> teardown_environ(q)
168
msg = tree.log_message()
169
msg["summary"] = summary
172
def commit_test_revisions():
174
>>> q = test_environ()
175
>>> commit_test_revisions()
176
>>> a = pybaz.Archive("test@example.com")
177
>>> revisions = list(a.iter_revisions("test--test--0"))
180
>>> str(revisions[2])
181
'test@example.com/test--test--0--base-0'
182
>>> str(revisions[1])
183
'test@example.com/test--test--0--patch-1'
184
>>> str(revisions[0])
185
'test@example.com/test--test--0--patch-2'
186
>>> teardown_environ(q)
188
tree = pybaz.tree_root(".")
189
add_file("mainfile", "void main(void){}", "mainfile by aaron")
190
timport(tree, "Created mainfile")
191
file("mainfile", "wb").write("or something like that")
192
commit(tree, "altered mainfile")
193
add_file("ofile", "this is another file", "ofile by aaron")
194
commit(tree, "altered mainfile")
197
def commit_more_test_revisions():
199
>>> q = test_environ()
200
>>> commit_test_revisions()
201
>>> commit_more_test_revisions()
202
>>> a = pybaz.Archive("test@example.com")
203
>>> revisions = list(a.iter_revisions("test--test--0"))
206
>>> str(revisions[0])
207
'test@example.com/test--test--0--patch-3'
208
>>> teardown_environ(q)
210
tree = pybaz.tree_root(".")
211
add_file("trainfile", "void train(void){}", "trainfile by aaron")
212
commit(tree, "altered trainfile")
214
class NoSuchVersion(Exception):
215
def __init__(self, version):
216
Exception.__init__(self, "The version %s does not exist." % version)
217
self.version = version
219
def version_ancestry(version):
221
>>> q = test_environ()
222
>>> commit_test_revisions()
223
>>> version = pybaz.Version("test@example.com/test--test--0")
224
>>> ancestors = version_ancestry(version)
225
>>> str(ancestors[0])
226
'test@example.com/test--test--0--base-0'
227
>>> str(ancestors[1])
228
'test@example.com/test--test--0--patch-1'
229
>>> version = pybaz.Version("test@example.com/test--test--0.5")
230
>>> ancestors = version_ancestry(version)
231
Traceback (most recent call last):
232
NoSuchVersion: The version test@example.com/test--test--0.5 does not exist.
233
>>> teardown_environ(q)
236
revision = version.iter_revisions(reverse=True).next()
237
except StopIteration:
241
if not version.exists():
242
raise NoSuchVersion(version)
245
ancestors = list(revision.iter_ancestors(metoo=True))
249
def get_last_revision(branch):
250
last_patch = branch.last_revision()
252
return arch_revision(last_patch)
253
except NotArchRevision:
255
"Directory \"%s\" already exists, and the last revision is not"
256
" an Arch revision (%s)" % (branch.base, last_patch))
258
def do_branch(br_from, to_location, revision_id):
259
"""Derived from branch in builtins."""
263
os.mkdir(to_location)
265
if e.errno == errno.EEXIST:
266
raise UserError('Target directory "%s" already'
267
' exists.' % to_location)
268
if e.errno == errno.ENOENT:
269
raise UserError('Parent of "%s" does not exist.' %
274
br_from.bzrdir.clone(to_location, revision_id)
275
except NoSuchRevision:
277
msg = "The branch %s has no revision %s." % (from_location,
283
def get_remaining_revisions(output_dir, version, reuse_history_from=[]):
286
output_exists = os.path.exists(output_dir)
288
# We are starting from an existing directory, figure out what
289
# the current version is
290
branch = Branch.open(output_dir)
291
last_patch = get_last_revision(branch)
292
if last_patch is None:
293
raise NotPreviousImport(branch.base)
295
version = last_patch.version
296
elif version is None:
297
raise UserError("No version specified, and directory does not exist.")
300
ancestors = version_ancestry(version)
301
if not output_exists and reuse_history_from != []:
302
for ancestor in reversed(ancestors):
303
if last_patch is not None:
304
# found something to copy
306
# try to grab a copy of ancestor
307
# note that is not optimised: we could look for namespace
308
# transitions and only look for the past after the
310
for history_root in reuse_history_from:
311
possible_source = os.path.join(history_root,
312
map_namespace(ancestor.version))
314
source = Branch.open(possible_source)
315
rev_id = revision_id(ancestor)
316
if rev_id in source.revision_history():
317
do_branch(source, output_dir, rev_id)
318
last_patch = ancestor
320
except NotBranchError:
322
except NoSuchVersion, e:
323
raise UserError(str(e))
326
for i in range(len(ancestors)):
327
if ancestors[i] == last_patch:
330
raise UserError("Directory \"%s\" already exists, and the last "
331
"revision (%s) is not in the ancestry of %s" %
332
(output_dir, last_patch, version))
333
# Strip off all of the ancestors which are already present
334
# And get a directory starting with the latest ancestor
335
latest_ancestor = ancestors[i]
336
old_revno = Branch.open(output_dir).revno()
337
ancestors = ancestors[i+1:]
338
return ancestors, old_revno
341
###class Importer(object):
344
### Currently this is used as a parameter object, though more behaviour is
348
### def __init__(self, output_dir, version, printer, fancy=True, fast=False,
349
### verbose=False, dry_run=False, max_count=None,
350
### reuse_history_from=[]):
351
### self.output_dir = output_dir
352
### self.version = version
356
def import_version(output_dir, version, printer, fancy=True, fast=False,
357
verbose=False, dry_run=False, max_count=None,
358
reuse_history_from=[], standalone=True):
360
>>> q = test_environ()
361
>>> result_path = os.path.join(q, "result")
362
>>> commit_test_revisions()
363
>>> version = pybaz.Version("test@example.com/test--test--0.1")
364
>>> def printer(message): print message
365
>>> import_version('/', version, printer, fancy=False, dry_run=True)
366
Traceback (most recent call last):
367
NotPreviousImport: / is not the location of a previous import.
368
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
369
Traceback (most recent call last):
370
UserError: The version test@example.com/test--test--0.1 does not exist.
371
>>> version = pybaz.Version("test@example.com/test--test--0")
372
>>> import_version(result_path, version, printer, fancy=False, dry_run=True)
375
Dry run, not modifying output_dir
377
>>> import_version(result_path, version, printer, fancy=False)
382
>>> import_version(result_path, version, printer, fancy=False)
383
Tree is up-to-date with test@example.com/test--test--0--patch-2
384
>>> commit_more_test_revisions()
385
>>> import_version(result_path, version, printer, fancy=False)
390
>>> teardown_environ(q)
393
ancestors, old_revno = get_remaining_revisions(output_dir, version,
395
except NotBranchError, e:
396
raise NotPreviousImport(e.path)
397
if old_revno is None and len(ancestors) == 0:
398
print 'Version %s has no revisions.' % version
400
if len(ancestors) == 0:
401
last_revision = get_last_revision(Branch.open(output_dir))
402
print 'Tree is up-to-date with %s' % last_revision
405
progress_bar = bzrlib.ui.ui_factory.progress_bar()
406
tempdir = tempfile.mkdtemp(prefix="baz2bzr-",
407
dir=os.path.dirname(output_dir))
409
wt = WorkingTree.open(output_dir)
410
except (NotBranchError, NoWorkingTree):
413
old_basis = EmptyTree()
415
old_basis = wt.basis_tree()
420
for result in iter_import_version(output_dir, ancestors, tempdir,
421
progress_bar, fast=fast, verbose=verbose, dry_run=dry_run,
422
max_count=max_count, standalone=standalone):
424
show_progress(progress_bar, result)
426
sys.stdout.write('.')
431
sys.stdout.write('\n')
434
print 'Dry run, not modifying output_dir'
437
# Update the working tree of the branch
439
wt = WorkingTree.open(output_dir)
440
except NoWorkingTree:
443
wt.set_last_revision(wt.branch.last_revision())
444
merge_inner(wt.branch, wt.basis_tree(), old_basis,
445
ignore_zero=True, this_tree=wt)
449
printer('Cleaning up')
450
shutil.rmtree(tempdir)
451
printer("Import complete.")
453
class UserError(BzrCommandError):
454
def __init__(self, message):
455
"""Exception to throw when a user makes an impossible request
456
:param message: The message to emit when printing this exception
457
:type message: string
459
BzrCommandError.__init__(self, message)
461
class NotPreviousImport(UserError):
462
def __init__(self, path):
463
UserError.__init__(self, "%s is not the location of a previous import."
467
def revision_id(arch_revision):
469
Generate a Bzr revision id from an Arch revision id. 'x' in the id
470
designates a revision imported with an experimental algorithm. A number
471
would indicate a particular standardized version.
473
:param arch_revision: The Arch revision to generate an ID for.
475
>>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
476
'Arch-1:you@example.com%cat--br--0--base-0'
478
return "Arch-1:%s" % str(arch_revision).replace('/', '%')
480
class NotArchRevision(Exception):
481
def __init__(self, revision_id):
482
msg = "The revision id %s does not look like it came from Arch."\
484
Exception.__init__(self, msg)
486
def arch_revision(revision_id):
488
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0"))
489
Traceback (most recent call last):
490
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0 does not look like it came from Arch.
491
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--base-5"))
492
Traceback (most recent call last):
493
NotArchRevision: The revision id Arch-1:jrandom@example.com%test--test--0--base-5 does not look like it came from Arch.
494
>>> str(arch_revision("Arch-1:jrandom@example.com%test--test--0--patch-5"))
495
'jrandom@example.com/test--test--0--patch-5'
497
if revision_id is None:
499
if revision_id[:7] != 'Arch-1:':
500
raise NotArchRevision(revision_id)
503
return pybaz.Revision(revision_id[7:].replace('%', '/'))
504
except pybaz.errors.NamespaceError, e:
505
raise NotArchRevision(revision_id)
508
def create_shared_repository(output_dir):
509
bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
510
bd.create_repository(shared=True)
512
def create_branch(output_dir):
514
bd = bzrdir.BzrDirMetaFormat1().initialize(output_dir)
515
return bd.create_branch()
518
def create_checkout(source, to_location, revision_id=None):
519
checkout = bzrdir.BzrDirMetaFormat1().initialize(to_location)
520
bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
521
return checkout.create_workingtree(revision_id)
524
def create_checkout_metadata(source, to_location, revision_id=None):
525
if revision_id is None:
526
revision_id = source.last_revision()
527
wt = create_checkout(source, to_location, NULL_REVISION)
528
wt.set_last_revision(revision_id)
529
wt._write_inventory(wt.basis_tree().inventory)
533
def iter_import_version(output_dir, ancestors, tempdir, pb, fast=False,
534
verbose=False, dry_run=False, max_count=None,
538
# Uncomment this for testing, it basically just has baz2bzr only update
539
# 5 patches at a time
541
ancestors = ancestors[:max_count]
543
# Not sure if I want this output. basically it tells you ahead of time
544
# what it is going to do, but then later it tells you as it is doing it.
545
# what probably would be best would be to collapse it into ranges, so that
546
# this gives the simple view, and then later it gives the blow by blow.
548
# print 'Adding the following revisions:'
549
# for a in ancestors:
552
previous_version=None
553
missing_ancestor = None
555
dry_output_dir = os.path.join(tempdir, 'od')
556
if os.path.exists(output_dir):
557
shutil.copytree(output_dir, dry_output_dir)
558
output_dir = dry_output_dir
560
if os.path.exists(output_dir):
561
target_branch = Branch.open(output_dir)
564
wt = BzrDir.create_standalone_workingtree(output_dir)
565
target_branch = wt.branch
567
target_branch = create_branch(output_dir)
569
for i in range(len(ancestors)):
570
revision = ancestors[i]
571
rev_id = revision_id(revision)
574
version = str(revision.version)
575
if version != previous_version:
577
print '\rOn version: %s' % version
578
yield Progress(str(revision.patchlevel), i, len(ancestors))
579
previous_version = version
581
yield Progress("revisions", i, len(ancestors))
583
if target_branch.repository.has_revision(rev_id):
584
target_branch.append_revision(rev_id)
587
revdir = os.path.join(tempdir, "rd")
589
tree, baz_inv, log = get_revision(revdir, revision)
590
except pybaz.errors.ExecProblem, e:
591
if ("%s" % e.args).find('could not connect') == -1:
593
missing_ancestor = revision
595
print ("unable to access ancestor %s, making into a merge."
598
target_tree = create_checkout_metadata(target_branch, revdir)
599
branch = target_tree.branch
601
old = os.path.join(revdir, ".bzr")
602
new = os.path.join(tempdir, ".bzr")
604
baz_inv, log = apply_revision(tree, revision)
606
target_tree = WorkingTree.open(revdir)
607
branch = target_tree.branch
608
# cached so we can delete the log
610
log_summary = log.summary
611
log_description = log.description
612
is_continuation = log.continuation_of is not None
613
log_creator = log.creator
614
direct_merges = get_direct_merges(revdir, revision, log)
616
timestamp = email.Utils.mktime_tz(log_date + (0,))
617
if log_summary is None:
619
# log_descriptions of None and "" are ignored.
620
if not is_continuation and log_description:
621
log_message = "\n".join((log_summary, log_description))
623
log_message = log_summary
624
target_tree.lock_write()
628
# if we want it to be in revision-history, do that here.
629
target_tree.add_pending_merge(revision_id(missing_ancestor))
630
missing_ancestor = None
631
for merged_rev in direct_merges:
632
target_tree.add_pending_merge(revision_id(merged_rev))
633
target_tree.set_inventory(baz_inv)
634
commitobj = Commit(reporter=ImportCommitReporter(pb))
635
commitobj.commit(working_tree=target_tree,
636
message=log_message.decode('ascii', 'replace'),
637
verbose=False, committer=log_creator,
638
timestamp=timestamp, timezone=0, rev_id=rev_id,
643
yield Progress("revisions", len(ancestors), len(ancestors))
645
def get_direct_merges(revdir, revision, log):
646
continuation = log.continuation_of
647
previous_version = revision.version
648
if pybaz.WorkingTree(revdir).tree_version != previous_version:
649
pybaz.WorkingTree(revdir).set_tree_version(previous_version)
650
log_path = "%s/{arch}/%s/%s/%s/%s/patch-log/%s" % (revdir,
651
revision.category.nonarch, revision.branch.nonarch,
652
revision.version.nonarch, revision.archive, revision.patchlevel)
653
temp_path = tempfile.mktemp(dir=os.path.dirname(revdir))
654
os.rename(log_path, temp_path)
655
merges = list(iter_new_merges(revdir, revision.version))
656
direct = direct_merges(merges, [continuation])
657
os.rename(temp_path, log_path)
660
def unlink_unversioned(wt):
661
for unversioned in wt.extras():
662
path = wt.abspath(unversioned)
663
if os.path.isdir(path):
668
def get_log(tree, revision):
669
log = pybaz.Patchlog(revision, tree=tree)
670
assert str(log.revision) == str(revision), (log.revision, revision)
673
def get_revision(revdir, revision):
674
tree = revision.get(revdir)
675
log = get_log(tree, revision)
677
return tree, bzr_inventory_data(tree), log
678
except BadFileKind, e:
679
raise UserError("Cannot convert %s because %s is a %s" %
680
(revision,e.path, e.kind))
683
def apply_revision(tree, revision):
685
log = get_log(tree, revision)
687
return bzr_inventory_data(tree), log
688
except BadFileKind, e:
689
raise UserError("Cannot convert %s because %s is a %s" %
690
(revision,e.path, e.kind))
693
class BadFileKind(Exception):
694
"""The file kind is not permitted in bzr inventories"""
695
def __init__(self, tree_root, path, kind):
696
self.tree_root = tree_root
699
Exception.__init__(self, "File %s is of forbidden type %s" %
700
(os.path.join(tree_root, path), kind))
703
def bzr_inventory_data(tree):
704
inv_iter = tree.iter_inventory_ids(source=True, both=True)
706
for arch_id, path in inv_iter:
707
bzr_file_id = map_file_id(arch_id)
708
inv_map[path] = bzr_file_id
711
for path, file_id in inv_map.iteritems():
712
full_path = os.path.join(tree, path)
713
kind = bzrlib.osutils.file_kind(full_path)
714
if kind not in ("file", "directory", "symlink"):
715
raise BadFileKind(tree, path, kind)
716
parent_dir = os.path.dirname(path)
718
parent_id = inv_map[parent_dir]
720
parent_id = bzrlib.inventory.ROOT_ID
721
bzr_inv.append((path, file_id, parent_id, kind))
725
_global_option('max-count', type = int)
726
class cmd_baz_import_branch(Command):
727
"""Import an Arch or Baz branch into a bzr branch"""
728
takes_args = ['to_location', 'from_branch?', 'reuse_history*']
729
takes_options = ['verbose', 'max-count']
731
def printer(self, name):
734
def run(self, to_location, from_branch=None, fast=False, max_count=None,
735
verbose=False, dry_run=False, reuse_history_list=[]):
736
to_location = os.path.realpath(str(to_location))
737
if from_branch is not None:
739
from_branch = pybaz.Version(from_branch)
740
except pybaz.errors.NamespaceError:
741
print "%s is not a valid Arch branch." % from_branch
743
if reuse_history_list is None:
744
reuse_history_list = []
745
import_version(to_location, from_branch, self.printer,
747
reuse_history_from=reuse_history_list)
750
class NotInABranch(Exception):
751
def __init__(self, path):
752
Exception.__init__(self, "%s is not in a branch." % path)
756
class cmd_baz_import(Command):
757
"""Import an Arch or Baz archive into bzr branches.
759
This command should be used on local archives (or mirrors) only. It is
760
quite slow on remote archives.
762
reuse_history allows you to specify any previous imports you
763
have done of different archives, which this archive has branches
764
tagged from. This will dramatically reduce the time to convert
765
the archive as it will not have to convert the history already
766
converted in that other branch.
768
If you specify prefixes, only branches whose names start with that prefix
769
will be imported. Skipped branches will be listed, so you can import any
770
branches you missed by accident. Here's an example of doing a partial
771
import from thelove@canonical.com:
772
bzr baz-import thelove thelove@canonical.com --prefixes dists:talloc-except
774
takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
775
takes_options = ['verbose', Option('prefixes', type=str,
776
help="Prefixes of branches to import, colon-separated")]
778
def printer(self, name):
781
def run(self, to_root_dir, from_archive, verbose=False,
782
reuse_history_list=[], prefixes=None):
783
if reuse_history_list is None:
784
reuse_history_list = []
785
to_root = str(os.path.realpath(to_root_dir))
786
if not os.path.exists(to_root):
788
if prefixes is not None:
789
prefixes = prefixes.split(':')
790
import_archive(to_root, from_archive, verbose, self.printer,
791
reuse_history_list, prefixes=prefixes)
794
def import_archive(to_root, from_archive, verbose, printer,
795
reuse_history_from=[], standalone=False,
797
def selected(version):
801
for prefix in prefixes:
802
if version.nonarch.startswith(prefix):
805
real_to = os.path.realpath(to_root)
806
history_locations = [real_to] + reuse_history_from
807
if standalone is False:
809
bd = BzrDir.open(to_root)
811
except NotBranchError:
812
create_shared_repository(to_root)
813
except NoRepositoryPresent:
814
raise BzrCommandError("Can't create repository at existing branch.")
815
for version in pybaz.Archive(str(from_archive)).iter_versions():
816
if not selected(version):
817
print "Skipping %s" % version
819
target = os.path.join(to_root, map_namespace(version))
820
printer("importing %s into %s" % (version, target))
821
if not os.path.exists(os.path.dirname(target)):
822
os.makedirs(os.path.dirname(target))
824
import_version(target, version, printer,
825
reuse_history_from=reuse_history_from,
826
standalone=standalone)
827
except pybaz.errors.ExecProblem,e:
828
if str(e).find('The requested revision cannot be built.') != -1:
829
printer("Skipping version %s as it cannot be built due"
830
" to a missing parent archive." % version)
834
if str(e).find('already exists, and the last revision ') != -1:
835
printer("Skipping version %s as it has had commits made"
836
" since it was converted to bzr." % version)
841
def map_namespace(a_version):
842
a_version = pybaz.Version("%s" % a_version)
843
parser = NameParser(a_version)
844
version = parser.get_version()
845
branch = parser.get_branch()
846
category = parser.get_category()
847
if branch is None or branch == '':
850
return "%s/%s" % (category, branch)
851
return "%s/%s/%s" % (category, version, branch)
853
def map_file_id(file_id):
854
"""Convert a baz file id to a bzr one."""
855
return file_id.replace('%', '%25').replace('/', '%2f')