~abentley/bzrtools/bzrtools.dev

2 by abentley
Added baz2bzr file(doh!)
1
#!/usr/bin/env python
14 by abentley
GPLed the project, ignored files
2
3
# Copyright (C) 2005 Aaron Bentley
4
# <aaron.bentley@utoronto.ca>
5
#
6
#    This program is free software; you can redistribute it and/or modify
7
#    it under the terms of the GNU General Public License as published by
8
#    the Free Software Foundation; either version 2 of the License, or
9
#    (at your option) any later version.
10
#
11
#    This program is distributed in the hope that it will be useful,
12
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#    GNU General Public License for more details.
15
#
16
#    You should have received a copy of the GNU General Public License
17
#    along with this program; if not, write to the Free Software
18
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
2 by abentley
Added baz2bzr file(doh!)
20
try:
21
    import pybaz
22
except ImportError:
23
    print "This command requires PyBaz.  Please ensure that it is installed."
24
    import sys
25
    sys.exit(1)
3 by abentley
Added add_file and add_dir functions
26
from pybaz.backends.baz import null_cmd
2 by abentley
Added baz2bzr file(doh!)
27
import tempfile
28
import os
29
import os.path
30
import shutil
7 by abentley
Completed initial construction
31
import bzrlib
45 by Aaron Bentley
Silenced commits
32
import bzrlib.trace
8 by abentley
Added ability to run from the commandline
33
import sys
37 by Aaron Bentley
More log fixups for baz2bzr
34
import email.Utils
11 by abentley
refactored out progress.py
35
from progress import *
2 by abentley
Added baz2bzr file(doh!)
36
3 by abentley
Added add_file and add_dir functions
37
def add_id(files, id=None):
38
    """Adds an explicit id to a list of files.
39
40
    :param files: the name of the file to add an id to
41
    :type files: list of str
42
    :param id: tag one file using the specified id, instead of generating id
43
    :type id: str
44
    """
45
    args = ["add-id"]
46
    if id is not None:
47
        args.extend(["--id", id])
48
    args.extend(files)
49
    return null_cmd(args)
50
2 by abentley
Added baz2bzr file(doh!)
51
def test_environ():
52
    """
53
    >>> q = test_environ()
54
    >>> os.path.exists(q)
55
    True
56
    >>> os.path.exists(os.path.join(q, "home", ".arch-params"))
57
    True
58
    >>> teardown_environ(q)
59
    >>> os.path.exists(q)
60
    False
61
    """
62
    tdir = tempfile.mkdtemp(prefix="baz2bzr-")
63
    os.environ["HOME"] = os.path.join(tdir, "home")
64
    os.mkdir(os.environ["HOME"])
3 by abentley
Added add_file and add_dir functions
65
    arch_dir = os.path.join(tdir, "archive_dir")
66
    pybaz.make_archive("test@example.com", arch_dir)
2 by abentley
Added baz2bzr file(doh!)
67
    work_dir = os.path.join(tdir, "work_dir")
68
    os.mkdir(work_dir)
69
    os.chdir(work_dir)
3 by abentley
Added add_file and add_dir functions
70
    pybaz.init_tree(work_dir, "test@example.com/test--test--0")
2 by abentley
Added baz2bzr file(doh!)
71
    lib_dir = os.path.join(tdir, "lib_dir")
72
    os.mkdir(lib_dir)
73
    pybaz.register_revision_library(lib_dir)
6 by abentley
added commit, timport, commit_test_revisions
74
    pybaz.set_my_id("Test User<test@example.org>")
2 by abentley
Added baz2bzr file(doh!)
75
    return tdir
76
5 by abentley
changed add_file, add_dir interfaces
77
def add_file(path, text, id):
3 by abentley
Added add_file and add_dir functions
78
    """
79
    >>> q = test_environ()
5 by abentley
changed add_file, add_dir interfaces
80
    >>> add_file("path with space", "text", "lalala")
3 by abentley
Added add_file and add_dir functions
81
    >>> tree = pybaz.tree_root(".")
82
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
83
    >>> ("x_lalala", "path with space") in inv
84
    True
85
    >>> teardown_environ(q)
86
    """
87
    file(path, "wb").write(text)
5 by abentley
changed add_file, add_dir interfaces
88
    add_id([path], id)
89
90
91
def add_dir(path, id):
3 by abentley
Added add_file and add_dir functions
92
    """
93
    >>> q = test_environ()
5 by abentley
changed add_file, add_dir interfaces
94
    >>> add_dir("path with\(sp) space", "lalala")
3 by abentley
Added add_file and add_dir functions
95
    >>> tree = pybaz.tree_root(".")
96
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
4 by abentley
made test case trickier, by using pika escaping sequence in filename.
97
    >>> ("x_lalala", "path with\(sp) space") in inv
3 by abentley
Added add_file and add_dir functions
98
    True
99
    >>> teardown_environ(q)
100
    """
101
    os.mkdir(path)
5 by abentley
changed add_file, add_dir interfaces
102
    add_id([path], id)
3 by abentley
Added add_file and add_dir functions
103
2 by abentley
Added baz2bzr file(doh!)
104
def teardown_environ(tdir):
105
    os.chdir("/")
106
    shutil.rmtree(tdir)
107
6 by abentley
added commit, timport, commit_test_revisions
108
def timport(tree, summary):
109
    msg = tree.log_message()
110
    msg["summary"] = summary
111
    tree.import_(msg)
112
113
def commit(tree, summary):
114
    """
115
    >>> q = test_environ()
116
    >>> tree = pybaz.tree_root(".")
117
    >>> timport(tree, "import")
118
    >>> commit(tree, "commit")
119
    >>> logs = [str(l.revision) for l in tree.iter_logs()]
120
    >>> len(logs)
121
    2
122
    >>> logs[0]
123
    'test@example.com/test--test--0--base-0'
124
    >>> logs[1]
125
    'test@example.com/test--test--0--patch-1'
126
    >>> teardown_environ(q)
127
    """
128
    msg = tree.log_message()
129
    msg["summary"] = summary
130
    tree.commit(msg)
131
132
def commit_test_revisions():
133
    """
134
    >>> q = test_environ()
135
    >>> commit_test_revisions()
136
    >>> a = pybaz.Archive("test@example.com")
137
    >>> revisions = list(a.iter_revisions("test--test--0"))
138
    >>> len(revisions)
7 by abentley
Completed initial construction
139
    3
140
    >>> str(revisions[2])
141
    'test@example.com/test--test--0--base-0'
6 by abentley
added commit, timport, commit_test_revisions
142
    >>> str(revisions[1])
7 by abentley
Completed initial construction
143
    'test@example.com/test--test--0--patch-1'
6 by abentley
added commit, timport, commit_test_revisions
144
    >>> str(revisions[0])
7 by abentley
Completed initial construction
145
    'test@example.com/test--test--0--patch-2'
6 by abentley
added commit, timport, commit_test_revisions
146
    >>> teardown_environ(q)
147
    """
148
    tree = pybaz.tree_root(".")
149
    add_file("mainfile", "void main(void){}", "mainfile by aaron")
150
    timport(tree, "Created mainfile")
151
    file("mainfile", "wb").write("or something like that")
152
    commit(tree, "altered mainfile")
7 by abentley
Completed initial construction
153
    add_file("ofile", "this is another file", "ofile by aaron")
154
    commit(tree, "altered mainfile")
155
156
def version_ancestry(version):
157
    """
158
    >>> q = test_environ()
159
    >>> commit_test_revisions()
160
    >>> version = pybaz.Version("test@example.com/test--test--0")
161
    >>> ancestors = version_ancestry(version)
162
    >>> str(ancestors[0])
163
    'test@example.com/test--test--0--base-0'
164
    >>> str(ancestors[1])
165
    'test@example.com/test--test--0--patch-1'
166
    >>> teardown_environ(q)
167
    """
35 by Aaron Bentley
Fixed, and got test cases passing
168
    revision = version.iter_revisions(reverse=True).next()
31 by Aaron Bentley
Further updates
169
    ancestors = list(revision.iter_ancestors(metoo=True))
170
    ancestors.reverse()
7 by abentley
Completed initial construction
171
    return ancestors
172
10 by abentley
Refactored, made progress bar
173
35 by Aaron Bentley
Fixed, and got test cases passing
174
def import_version(output_dir, version, fancy=True):
7 by abentley
Completed initial construction
175
    """
176
    >>> q = test_environ()
177
    >>> result_path = os.path.join(q, "result")
178
    >>> commit_test_revisions()
179
    >>> version = pybaz.Version("test@example.com/test--test--0")
35 by Aaron Bentley
Fixed, and got test cases passing
180
    >>> import_version(result_path, version, fancy=False)
181
    not fancy
182
    ....
183
    Import complete.
7 by abentley
Completed initial construction
184
    >>> teardown_environ(q)
185
    """
47 by Aaron Bentley
merged ETA changes
186
    progress_bar = ProgressBar()
7 by abentley
Completed initial construction
187
    tempdir = tempfile.mkdtemp(prefix="baz2bzr")
188
    try:
35 by Aaron Bentley
Fixed, and got test cases passing
189
        if not fancy:
190
            print "not fancy"
10 by abentley
Refactored, made progress bar
191
        for result in iter_import_version(output_dir, version, tempdir):
35 by Aaron Bentley
Fixed, and got test cases passing
192
            if fancy:
193
                progress_bar(result)
194
            else:
195
                sys.stdout.write('.')
29 by Aaron Bentley
Nicer handling of unsupported file types
196
    finally:
35 by Aaron Bentley
Fixed, and got test cases passing
197
        if fancy:
198
            clear_progress_bar()
199
        else:
200
            sys.stdout.write('\n')
7 by abentley
Completed initial construction
201
        shutil.rmtree(tempdir)
29 by Aaron Bentley
Nicer handling of unsupported file types
202
    print "Import complete."
10 by abentley
Refactored, made progress bar
203
            
12 by abentley
Error early when the user specifies an output directory that already exists
204
class UserError(Exception):
205
    def __init__(self, message):
206
        """Exception to throw when a user makes an impossible request
207
        :param message: The message to emit when printing this exception
208
        :type message: string
209
        """
210
        Exception.__init__(self, message)
211
46 by Aaron Bentley
Made bzr revision ids predictable
212
def revision_id(arch_revision):
213
    """
214
    Generate a Bzr revision id from an Arch revision id.  'x' in the id
215
    designates a revision imported with an experimental algorithm.  A number
216
    would indicate a particular standardized version.
217
218
    :param arch_revision: The Arch revision to generate an ID for.
219
220
    >>> revision_id(pybaz.Revision("you@example.com/cat--br--0--base-0"))
221
    'Arch-x:you@example.com%cat--br--0--base-0'
222
    """
223
    return "Arch-x:%s" % str(arch_revision).replace('/', '%')
224
10 by abentley
Refactored, made progress bar
225
def iter_import_version(output_dir, version, tempdir):
226
    revdir = None
227
    ancestors = version_ancestry(version)
228
    for i in range(len(ancestors)):
229
        revision = ancestors[i]
230
        yield Progress("revisions", i, len(ancestors))
231
        if revdir is None:
232
            revdir = os.path.join(tempdir, "rd")
233
            baz_inv, log = get_revision(revdir, revision)
234
            branch = bzrlib.Branch(revdir, init=True)
235
        else:
236
            old = os.path.join(revdir, ".bzr")
237
            new = os.path.join(tempdir, ".bzr")
238
            os.rename(old, new)
13 by abentley
Used replay to get next revision
239
            baz_inv, log = apply_revision(revdir, revision)
10 by abentley
Refactored, made progress bar
240
            os.rename(new, old)
241
            branch = bzrlib.Branch(revdir)
242
        branch.set_inventory(baz_inv)
37 by Aaron Bentley
More log fixups for baz2bzr
243
        timestamp = email.Utils.mktime_tz(log.date + (0,))
46 by Aaron Bentley
Made bzr revision ids predictable
244
        rev_id = revision_id(revision)
45 by Aaron Bentley
Silenced commits
245
        bzrlib.trace.silent = True
246
        try:
247
            branch.commit(log.summary, verbose=False, committer=log.creator,
46 by Aaron Bentley
Made bzr revision ids predictable
248
                          timestamp=timestamp, timezone=0, rev_id=rev_id)
45 by Aaron Bentley
Silenced commits
249
        finally:
250
            bzrlib.trace.silent = False   
10 by abentley
Refactored, made progress bar
251
    yield Progress("revisions", len(ancestors), len(ancestors))
252
    unlink_unversioned(branch, revdir)
253
    os.rename(revdir, output_dir)   
254
255
def unlink_unversioned(branch, revdir):
256
    for unversioned in branch.working_tree().extras():
257
        path = os.path.join(revdir, unversioned)
258
        if os.path.isdir(path):
259
            shutil.rmtree(path)
260
        else:
261
            os.unlink(path)
7 by abentley
Completed initial construction
262
37 by Aaron Bentley
More log fixups for baz2bzr
263
def get_log(tree, revision):
264
    log = tree.iter_logs(version=revision.version, reverse=True).next()
265
    assert log.revision == revision
266
    return log
267
7 by abentley
Completed initial construction
268
def get_revision(revdir, revision):
269
    revision.get(revdir)
270
    tree = pybaz.tree_root(revdir)
37 by Aaron Bentley
More log fixups for baz2bzr
271
    log = get_log(tree, revision)
29 by Aaron Bentley
Nicer handling of unsupported file types
272
    try:
273
        return bzr_inventory_data(tree), log 
274
    except BadFileKind, e:
275
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
276
7 by abentley
Completed initial construction
277
13 by abentley
Used replay to get next revision
278
def apply_revision(revdir, revision):
279
    tree = pybaz.tree_root(revdir)
280
    revision.apply(tree)
37 by Aaron Bentley
More log fixups for baz2bzr
281
    log = get_log(tree, revision)
29 by Aaron Bentley
Nicer handling of unsupported file types
282
    try:
283
        return bzr_inventory_data(tree), log
284
    except BadFileKind, e:
285
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
286
287
288
289
290
class BadFileKind(Exception):
291
    """The file kind is not permitted in bzr inventories"""
292
    def __init__(self, tree_root, path, kind):
293
        self.tree_root = tree_root
294
        self.path = path
295
        self.kind = kind
296
        Exception.__init__(self, "File %s is of forbidden type %s" %
297
                           (os.path.join(tree_root, path), kind))
13 by abentley
Used replay to get next revision
298
7 by abentley
Completed initial construction
299
def bzr_inventory_data(tree):
300
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
301
    inv_map = {}
302
    for file_id, path in inv_iter:
8 by abentley
Added ability to run from the commandline
303
        inv_map[path] = file_id 
7 by abentley
Completed initial construction
304
305
    bzr_inv = []
8 by abentley
Added ability to run from the commandline
306
    for path, file_id in inv_map.iteritems():
29 by Aaron Bentley
Nicer handling of unsupported file types
307
        full_path = os.path.join(tree, path)
308
        kind = bzrlib.osutils.file_kind(full_path)
309
        if kind not in ("file", "directory"):
310
            raise BadFileKind(tree, path, kind)
7 by abentley
Completed initial construction
311
        parent_dir = os.path.dirname(path)
312
        if parent_dir != "":
313
            parent_id = inv_map[parent_dir]
314
        else:
315
            parent_id = bzrlib.inventory.ROOT_ID
316
        bzr_inv.append((path, file_id, parent_id, kind))
317
    bzr_inv.sort()
318
    return bzr_inv
6 by abentley
added commit, timport, commit_test_revisions
319
8 by abentley
Added ability to run from the commandline
320
if len(sys.argv) == 2 and sys.argv[1] == "test":
321
    print "Running tests"
322
    import doctest
323
    doctest.testmod()
324
elif len(sys.argv) == 3:
12 by abentley
Error early when the user specifies an output directory that already exists
325
    try:
326
        output_dir = sys.argv[2]
327
        if os.path.exists(output_dir):
328
            raise UserError("Directory \"%s\" already exists" % output_dir)
329
        import_version(output_dir, pybaz.Version(sys.argv[1]))
330
    except UserError, e:
331
        print e
43 by Aaron Bentley
Handled ^C nicely
332
    except KeyboardInterrupt:
333
        print "Aborted."
8 by abentley
Added ability to run from the commandline
334
else:
335
    print "usage: %s VERSION OUTDIR" % os.path.basename(sys.argv[0])