~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
8 by abentley
Added ability to run from the commandline
32
import sys
37 by Aaron Bentley
More log fixups for baz2bzr
33
import email.Utils
11 by abentley
refactored out progress.py
34
from progress import *
2 by abentley
Added baz2bzr file(doh!)
35
3 by abentley
Added add_file and add_dir functions
36
def add_id(files, id=None):
37
    """Adds an explicit id to a list of files.
38
39
    :param files: the name of the file to add an id to
40
    :type files: list of str
41
    :param id: tag one file using the specified id, instead of generating id
42
    :type id: str
43
    """
44
    args = ["add-id"]
45
    if id is not None:
46
        args.extend(["--id", id])
47
    args.extend(files)
48
    return null_cmd(args)
49
2 by abentley
Added baz2bzr file(doh!)
50
def test_environ():
51
    """
52
    >>> q = test_environ()
53
    >>> os.path.exists(q)
54
    True
55
    >>> os.path.exists(os.path.join(q, "home", ".arch-params"))
56
    True
57
    >>> teardown_environ(q)
58
    >>> os.path.exists(q)
59
    False
60
    """
61
    tdir = tempfile.mkdtemp(prefix="baz2bzr-")
62
    os.environ["HOME"] = os.path.join(tdir, "home")
63
    os.mkdir(os.environ["HOME"])
3 by abentley
Added add_file and add_dir functions
64
    arch_dir = os.path.join(tdir, "archive_dir")
65
    pybaz.make_archive("test@example.com", arch_dir)
2 by abentley
Added baz2bzr file(doh!)
66
    work_dir = os.path.join(tdir, "work_dir")
67
    os.mkdir(work_dir)
68
    os.chdir(work_dir)
3 by abentley
Added add_file and add_dir functions
69
    pybaz.init_tree(work_dir, "test@example.com/test--test--0")
2 by abentley
Added baz2bzr file(doh!)
70
    lib_dir = os.path.join(tdir, "lib_dir")
71
    os.mkdir(lib_dir)
72
    pybaz.register_revision_library(lib_dir)
6 by abentley
added commit, timport, commit_test_revisions
73
    pybaz.set_my_id("Test User<test@example.org>")
2 by abentley
Added baz2bzr file(doh!)
74
    return tdir
75
5 by abentley
changed add_file, add_dir interfaces
76
def add_file(path, text, id):
3 by abentley
Added add_file and add_dir functions
77
    """
78
    >>> q = test_environ()
5 by abentley
changed add_file, add_dir interfaces
79
    >>> add_file("path with space", "text", "lalala")
3 by abentley
Added add_file and add_dir functions
80
    >>> tree = pybaz.tree_root(".")
81
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
82
    >>> ("x_lalala", "path with space") in inv
83
    True
84
    >>> teardown_environ(q)
85
    """
86
    file(path, "wb").write(text)
5 by abentley
changed add_file, add_dir interfaces
87
    add_id([path], id)
88
89
90
def add_dir(path, id):
3 by abentley
Added add_file and add_dir functions
91
    """
92
    >>> q = test_environ()
5 by abentley
changed add_file, add_dir interfaces
93
    >>> add_dir("path with\(sp) space", "lalala")
3 by abentley
Added add_file and add_dir functions
94
    >>> tree = pybaz.tree_root(".")
95
    >>> inv = list(tree.iter_inventory_ids(source=True, both=True))
4 by abentley
made test case trickier, by using pika escaping sequence in filename.
96
    >>> ("x_lalala", "path with\(sp) space") in inv
3 by abentley
Added add_file and add_dir functions
97
    True
98
    >>> teardown_environ(q)
99
    """
100
    os.mkdir(path)
5 by abentley
changed add_file, add_dir interfaces
101
    add_id([path], id)
3 by abentley
Added add_file and add_dir functions
102
2 by abentley
Added baz2bzr file(doh!)
103
def teardown_environ(tdir):
104
    os.chdir("/")
105
    shutil.rmtree(tdir)
106
6 by abentley
added commit, timport, commit_test_revisions
107
def timport(tree, summary):
108
    msg = tree.log_message()
109
    msg["summary"] = summary
110
    tree.import_(msg)
111
112
def commit(tree, summary):
113
    """
114
    >>> q = test_environ()
115
    >>> tree = pybaz.tree_root(".")
116
    >>> timport(tree, "import")
117
    >>> commit(tree, "commit")
118
    >>> logs = [str(l.revision) for l in tree.iter_logs()]
119
    >>> len(logs)
120
    2
121
    >>> logs[0]
122
    'test@example.com/test--test--0--base-0'
123
    >>> logs[1]
124
    'test@example.com/test--test--0--patch-1'
125
    >>> teardown_environ(q)
126
    """
127
    msg = tree.log_message()
128
    msg["summary"] = summary
129
    tree.commit(msg)
130
131
def commit_test_revisions():
132
    """
133
    >>> q = test_environ()
134
    >>> commit_test_revisions()
135
    >>> a = pybaz.Archive("test@example.com")
136
    >>> revisions = list(a.iter_revisions("test--test--0"))
137
    >>> len(revisions)
7 by abentley
Completed initial construction
138
    3
139
    >>> str(revisions[2])
140
    'test@example.com/test--test--0--base-0'
6 by abentley
added commit, timport, commit_test_revisions
141
    >>> str(revisions[1])
7 by abentley
Completed initial construction
142
    'test@example.com/test--test--0--patch-1'
6 by abentley
added commit, timport, commit_test_revisions
143
    >>> str(revisions[0])
7 by abentley
Completed initial construction
144
    'test@example.com/test--test--0--patch-2'
6 by abentley
added commit, timport, commit_test_revisions
145
    >>> teardown_environ(q)
146
    """
147
    tree = pybaz.tree_root(".")
148
    add_file("mainfile", "void main(void){}", "mainfile by aaron")
149
    timport(tree, "Created mainfile")
150
    file("mainfile", "wb").write("or something like that")
151
    commit(tree, "altered mainfile")
7 by abentley
Completed initial construction
152
    add_file("ofile", "this is another file", "ofile by aaron")
153
    commit(tree, "altered mainfile")
154
155
def version_ancestry(version):
156
    """
157
    >>> q = test_environ()
158
    >>> commit_test_revisions()
159
    >>> version = pybaz.Version("test@example.com/test--test--0")
160
    >>> ancestors = version_ancestry(version)
161
    >>> str(ancestors[0])
162
    'test@example.com/test--test--0--base-0'
163
    >>> str(ancestors[1])
164
    'test@example.com/test--test--0--patch-1'
165
    >>> teardown_environ(q)
166
    """
35 by Aaron Bentley
Fixed, and got test cases passing
167
    revision = version.iter_revisions(reverse=True).next()
31 by Aaron Bentley
Further updates
168
    ancestors = list(revision.iter_ancestors(metoo=True))
169
    ancestors.reverse()
7 by abentley
Completed initial construction
170
    return ancestors
171
10 by abentley
Refactored, made progress bar
172
35 by Aaron Bentley
Fixed, and got test cases passing
173
def import_version(output_dir, version, fancy=True):
7 by abentley
Completed initial construction
174
    """
175
    >>> q = test_environ()
176
    >>> result_path = os.path.join(q, "result")
177
    >>> commit_test_revisions()
178
    >>> version = pybaz.Version("test@example.com/test--test--0")
35 by Aaron Bentley
Fixed, and got test cases passing
179
    >>> import_version(result_path, version, fancy=False)
180
    not fancy
181
    ....
182
    Import complete.
7 by abentley
Completed initial construction
183
    >>> teardown_environ(q)
184
    """
185
    tempdir = tempfile.mkdtemp(prefix="baz2bzr")
186
    try:
35 by Aaron Bentley
Fixed, and got test cases passing
187
        if not fancy:
188
            print "not fancy"
10 by abentley
Refactored, made progress bar
189
        for result in iter_import_version(output_dir, version, tempdir):
35 by Aaron Bentley
Fixed, and got test cases passing
190
            if fancy:
191
                progress_bar(result)
192
            else:
193
                sys.stdout.write('.')
29 by Aaron Bentley
Nicer handling of unsupported file types
194
    finally:
35 by Aaron Bentley
Fixed, and got test cases passing
195
        if fancy:
196
            clear_progress_bar()
197
        else:
198
            sys.stdout.write('\n')
7 by abentley
Completed initial construction
199
        shutil.rmtree(tempdir)
29 by Aaron Bentley
Nicer handling of unsupported file types
200
    print "Import complete."
10 by abentley
Refactored, made progress bar
201
            
12 by abentley
Error early when the user specifies an output directory that already exists
202
class UserError(Exception):
203
    def __init__(self, message):
204
        """Exception to throw when a user makes an impossible request
205
        :param message: The message to emit when printing this exception
206
        :type message: string
207
        """
208
        Exception.__init__(self, message)
209
10 by abentley
Refactored, made progress bar
210
def iter_import_version(output_dir, version, tempdir):
211
    revdir = None
212
    ancestors = version_ancestry(version)
213
    for i in range(len(ancestors)):
214
        revision = ancestors[i]
215
        yield Progress("revisions", i, len(ancestors))
216
        if revdir is None:
217
            revdir = os.path.join(tempdir, "rd")
218
            baz_inv, log = get_revision(revdir, revision)
219
            branch = bzrlib.Branch(revdir, init=True)
220
        else:
221
            old = os.path.join(revdir, ".bzr")
222
            new = os.path.join(tempdir, ".bzr")
223
            os.rename(old, new)
13 by abentley
Used replay to get next revision
224
            baz_inv, log = apply_revision(revdir, revision)
10 by abentley
Refactored, made progress bar
225
            os.rename(new, old)
226
            branch = bzrlib.Branch(revdir)
227
        branch.set_inventory(baz_inv)
37 by Aaron Bentley
More log fixups for baz2bzr
228
        timestamp = email.Utils.mktime_tz(log.date + (0,))
36 by Aaron Bentley
Improved converted log output
229
            
37 by Aaron Bentley
More log fixups for baz2bzr
230
        branch.commit(log.summary, verbose=False,
231
                      committer=log.creator, timestamp=timestamp, timezone=0)
10 by abentley
Refactored, made progress bar
232
    yield Progress("revisions", len(ancestors), len(ancestors))
233
    unlink_unversioned(branch, revdir)
234
    os.rename(revdir, output_dir)   
235
236
def unlink_unversioned(branch, revdir):
237
    for unversioned in branch.working_tree().extras():
238
        path = os.path.join(revdir, unversioned)
239
        if os.path.isdir(path):
240
            shutil.rmtree(path)
241
        else:
242
            os.unlink(path)
7 by abentley
Completed initial construction
243
37 by Aaron Bentley
More log fixups for baz2bzr
244
def get_log(tree, revision):
245
    log = tree.iter_logs(version=revision.version, reverse=True).next()
246
    assert log.revision == revision
247
    return log
248
7 by abentley
Completed initial construction
249
def get_revision(revdir, revision):
250
    revision.get(revdir)
251
    tree = pybaz.tree_root(revdir)
37 by Aaron Bentley
More log fixups for baz2bzr
252
    log = get_log(tree, revision)
29 by Aaron Bentley
Nicer handling of unsupported file types
253
    try:
254
        return bzr_inventory_data(tree), log 
255
    except BadFileKind, e:
256
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
257
7 by abentley
Completed initial construction
258
13 by abentley
Used replay to get next revision
259
def apply_revision(revdir, revision):
260
    tree = pybaz.tree_root(revdir)
261
    revision.apply(tree)
37 by Aaron Bentley
More log fixups for baz2bzr
262
    log = get_log(tree, revision)
29 by Aaron Bentley
Nicer handling of unsupported file types
263
    try:
264
        return bzr_inventory_data(tree), log
265
    except BadFileKind, e:
266
        raise UserError("Cannot convert %s because %s is a %s" % (revision,e.path, e.kind) )
267
268
269
270
271
class BadFileKind(Exception):
272
    """The file kind is not permitted in bzr inventories"""
273
    def __init__(self, tree_root, path, kind):
274
        self.tree_root = tree_root
275
        self.path = path
276
        self.kind = kind
277
        Exception.__init__(self, "File %s is of forbidden type %s" %
278
                           (os.path.join(tree_root, path), kind))
13 by abentley
Used replay to get next revision
279
7 by abentley
Completed initial construction
280
def bzr_inventory_data(tree):
281
    inv_iter = tree.iter_inventory_ids(source=True, both=True)
282
    inv_map = {}
283
    for file_id, path in inv_iter:
8 by abentley
Added ability to run from the commandline
284
        inv_map[path] = file_id 
7 by abentley
Completed initial construction
285
286
    bzr_inv = []
8 by abentley
Added ability to run from the commandline
287
    for path, file_id in inv_map.iteritems():
29 by Aaron Bentley
Nicer handling of unsupported file types
288
        full_path = os.path.join(tree, path)
289
        kind = bzrlib.osutils.file_kind(full_path)
290
        if kind not in ("file", "directory"):
291
            raise BadFileKind(tree, path, kind)
7 by abentley
Completed initial construction
292
        parent_dir = os.path.dirname(path)
293
        if parent_dir != "":
294
            parent_id = inv_map[parent_dir]
295
        else:
296
            parent_id = bzrlib.inventory.ROOT_ID
297
        bzr_inv.append((path, file_id, parent_id, kind))
298
    bzr_inv.sort()
299
    return bzr_inv
6 by abentley
added commit, timport, commit_test_revisions
300
8 by abentley
Added ability to run from the commandline
301
if len(sys.argv) == 2 and sys.argv[1] == "test":
302
    print "Running tests"
303
    import doctest
304
    doctest.testmod()
305
elif len(sys.argv) == 3:
12 by abentley
Error early when the user specifies an output directory that already exists
306
    try:
307
        output_dir = sys.argv[2]
308
        if os.path.exists(output_dir):
309
            raise UserError("Directory \"%s\" already exists" % output_dir)
310
        import_version(output_dir, pybaz.Version(sys.argv[1]))
311
    except UserError, e:
312
        print e
8 by abentley
Added ability to run from the commandline
313
else:
314
    print "usage: %s VERSION OUTDIR" % os.path.basename(sys.argv[0])