~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Aaron Bentley
  • Date: 2011-09-25 01:56:48 UTC
  • mto: This revision was merged to the branch mainline in revision 779.
  • Revision ID: aaron@aaronbentley.com-20110925015648-41tbr3wonksf7efh
Add get_rev_info test.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Aaron Bentley <aaron.bentley@utoronto.ca>
 
1
# Copyright (C) 2005, 2006, 2007 Aaron Bentley <aaron@aaronbentley.com>
2
2
# Copyright (C) 2007 John Arbash Meinel
3
3
#
4
4
#    This program is free software; you can redistribute it and/or modify
24
24
import sys
25
25
 
26
26
import bzrlib
27
 
import bzrlib.errors
28
 
from bzrlib.errors import (BzrCommandError, NotBranchError, NoSuchFile,
29
 
                           UnsupportedFormatError, TransportError, 
30
 
                           NoWorkingTree, PermissionDenied)
31
 
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
27
from bzrlib import revision as _mod_revision, trace, urlutils
 
28
from bzrlib.errors import (
 
29
    BzrCommandError,
 
30
    BzrError,
 
31
    NotBranchError,
 
32
    NoSuchFile,
 
33
    )
 
34
from bzrlib.bzrdir import BzrDir
 
35
from bzrlib.transport import get_transport
32
36
 
33
37
def temp_tree():
34
38
    dirname = tempfile.mkdtemp("temp-branch")
55
59
    return not delta.has_changed(), non_source
56
60
 
57
61
def set_push_data(tree, location):
58
 
    tree.branch.control_files.put_utf8("x-push-data", "%s\n" % location)
 
62
    tree.branch._transport.put_bytes("x-push-data", "%s\n" % location)
59
63
 
60
64
def get_push_data(tree):
61
65
    """
68
72
    >>> rm_tree(tree)
69
73
    """
70
74
    try:
71
 
        location = tree.branch.control_files.get_utf8('x-push-data').read()
 
75
        location = tree.branch._transport.get('x-push-data').read()
72
76
    except NoSuchFile:
73
77
        return None
 
78
    location = location.decode('utf-8')
74
79
    return location.rstrip('\n')
75
80
 
76
81
"""
99
104
    def __init__(self, rsync_name):
100
105
        Exception.__init__(self, "%s not found." % rsync_name)
101
106
 
102
 
def rsync(source, target, ssh=False, excludes=(), silent=False, 
 
107
 
 
108
def rsync(source, target, ssh=False, excludes=(), silent=False,
103
109
          rsync_name="rsync"):
104
 
    """
105
 
    >>> new_dir = tempfile.mkdtemp()
106
 
    >>> old_dir = os.getcwd()
107
 
    >>> os.chdir(new_dir)
108
 
    >>> rsync("a", "b", silent=True)
109
 
    Traceback (most recent call last):
110
 
    RsyncNoFile: No such file...
111
 
    >>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
112
 
    Traceback (most recent call last):
113
 
    RsyncNoFile: No such file...
114
 
    >>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
115
 
    Traceback (most recent call last):
116
 
    NoRsync: rsyncc not found.
117
 
    >>> os.chdir(old_dir)
118
 
    >>> os.rmdir(new_dir)
119
 
    """
120
110
    cmd = [rsync_name, "-av", "--delete"]
121
111
    if ssh:
122
112
        cmd.extend(('-e', 'ssh'))
134
124
    except OSError, e:
135
125
        if e.errno == errno.ENOENT:
136
126
            raise NoRsync(rsync_name)
137
 
            
 
127
 
138
128
    proc.stdin.write('\n'.join(excludes)+'\n')
139
129
    proc.stdin.close()
140
130
    if silent:
176
166
        raise RsyncUnknownStatus(proc.returncode)
177
167
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
178
168
 
179
 
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent', 
 
169
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
180
170
              '.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
181
171
              '.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
182
172
              '.bzr/basis-inventory', '.bzr/inventory.backup.weave')
186
176
    return [l.rstrip('\r\n') for l in
187
177
            codecs.open(fname, 'rb', 'utf-8').readlines()]
188
178
 
 
179
 
 
180
def read_revision_info(path):
 
181
    """Parse a last_revision file to determine revision_info"""
 
182
    line = open(path, 'rb').readlines()[0].strip('\n')
 
183
    revno, revision_id = line.split(' ', 1)
 
184
    revno = int(revno)
 
185
    return revno, revision_id
 
186
 
 
187
 
189
188
class RsyncNoFile(Exception):
190
189
    def __init__(self, path):
191
190
        Exception.__init__(self, "No such file %s" % path)
194
193
    def __init__(self):
195
194
        Exception.__init__(self, "Error in rsync protocol data stream.")
196
195
 
197
 
def get_revision_history(location):
 
196
 
 
197
class NotStandalone(BzrError):
 
198
 
 
199
    _fmt = '%(location)s is not a standalone tree.'
 
200
    _internal = False
 
201
 
 
202
    def __init__(self, location):
 
203
        BzrError.__init__(self, location=location)
 
204
 
 
205
 
 
206
def get_revision_history(location, _rsync):
198
207
    tempdir = tempfile.mkdtemp('push')
 
208
    my_rsync = _rsync
 
209
    if my_rsync is None:
 
210
        my_rsync = rsync
199
211
    try:
200
212
        history_fname = os.path.join(tempdir, 'revision-history')
201
213
        try:
202
 
            cmd = rsync(location+'.bzr/revision-history', history_fname,
 
214
            cmd = my_rsync(location+'.bzr/revision-history', history_fname,
203
215
                        silent=True)
204
216
        except RsyncNoFile:
205
217
            cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
209
221
        shutil.rmtree(tempdir)
210
222
    return history
211
223
 
212
 
def history_subset(location, branch):
213
 
    remote_history = get_revision_history(location)
 
224
 
 
225
def get_revision_info(location, _rsync):
 
226
    """Get the revsision_info for an rsync-able branch"""
 
227
    tempdir = tempfile.mkdtemp('push')
 
228
    my_rsync = _rsync
 
229
    if my_rsync is None:
 
230
        my_rsync = rsync
 
231
    try:
 
232
        info_fname = os.path.join(tempdir, 'last-revision')
 
233
        cmd = rsync(location+'.bzr/branch/last-revision', info_fname,
 
234
                    silent=True)
 
235
        return read_revision_info(info_fname)
 
236
    finally:
 
237
        shutil.rmtree(tempdir)
 
238
 
 
239
 
 
240
def history_subset(location, branch, _rsync=None):
214
241
    local_history = branch.revision_history()
215
 
    if len(remote_history) > len(local_history):
216
 
        return False
217
 
    for local, remote in zip(remote_history, local_history):
218
 
        if local != remote:
219
 
            return False 
220
 
    return True
 
242
    try:
 
243
        remote_history = get_revision_history(location, _rsync)
 
244
    except RsyncNoFile:
 
245
        revno, revision_id = get_revision_info(location, _rsync)
 
246
        if revision_id == _mod_revision.NULL_REVISION:
 
247
            return True
 
248
        return bool(revision_id.decode('utf-8') in local_history)
 
249
    else:
 
250
        if len(remote_history) > len(local_history):
 
251
            return False
 
252
        for local, remote in zip(remote_history, local_history):
 
253
            if local != remote:
 
254
                return False
 
255
        return True
 
256
 
221
257
 
222
258
def empty_or_absent(location):
223
259
    try:
226
262
    except RsyncNoFile:
227
263
        return True
228
264
 
229
 
def rspush(tree, location=None, overwrite=False, working_tree=True):
230
 
    push_location = get_push_data(tree)
231
 
    if location is not None:
232
 
        if not location.endswith('/'):
233
 
            location += '/'
234
 
        push_location = location
235
 
    
236
 
    if push_location is None:
237
 
        raise BzrCommandError("No rspush location known or specified.")
238
 
 
239
 
    if (push_location.find('::') != -1):
240
 
        usessh=False
241
 
    else:
242
 
        usessh=True
243
 
 
244
 
    if (push_location.find('://') != -1 or
245
 
        push_location.find(':') == -1):
246
 
        raise BzrCommandError("Invalid rsync path %r." % push_location)
247
 
 
248
 
    if working_tree:
249
 
        clean, non_source = is_clean(tree)
250
 
        if not clean:
251
 
            print """Error: This tree has uncommitted changes or unknown (?) files.
252
 
    Use "bzr status" to list them."""
253
 
            sys.exit(1)
254
 
        final_exclusions = non_source[:]
255
 
    else:
256
 
        wt = tree
257
 
        final_exclusions = []
258
 
        for path, status, kind, file_id, entry in wt.list_files():
259
 
            final_exclusions.append(path)
260
 
 
261
 
    final_exclusions.extend(exclusions)
262
 
    if not overwrite:
263
 
        try:
264
 
            if not history_subset(push_location, tree.branch):
265
 
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
266
 
                                                    " newer version of remote"
267
 
                                                    " branch.")
268
 
        except RsyncNoFile:
269
 
            if not empty_or_absent(push_location):
270
 
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
271
 
                                                    " bzr branch (or empty"
272
 
                                                    " directory)")
273
 
        except RsyncStreamIO:
274
 
            raise bzrlib.errors.BzrCommandError("Rsync could not use the"
275
 
                " specified location.  Please ensure that"
276
 
                ' "%s" is of the form "machine:/path".' % push_location)
277
 
    print "Pushing to %s" % push_location
278
 
    rsync(tree.basedir+'/', push_location, ssh=usessh, 
279
 
          excludes=final_exclusions)
280
 
 
281
 
    set_push_data(tree, push_location)
 
265
def rspush(tree, location=None, overwrite=False, working_tree=True,
 
266
    _rsync=None):
 
267
    tree.lock_write()
 
268
    try:
 
269
        my_rsync = _rsync
 
270
        if my_rsync is None:
 
271
            my_rsync = rsync
 
272
        if (tree.bzrdir.root_transport.base !=
 
273
            tree.branch.bzrdir.root_transport.base):
 
274
            raise NotStandalone(tree.bzrdir.root_transport.base)
 
275
        if (tree.branch.get_bound_location() is not None):
 
276
            raise NotStandalone(tree.bzrdir.root_transport.base)
 
277
        if (tree.branch.repository.is_shared()):
 
278
            raise NotStandalone(tree.bzrdir.root_transport.base)
 
279
        push_location = get_push_data(tree)
 
280
        if location is not None:
 
281
            if not location.endswith('/'):
 
282
                location += '/'
 
283
            push_location = location
 
284
 
 
285
        if push_location is None:
 
286
            raise BzrCommandError("No rspush location known or specified.")
 
287
 
 
288
        if (push_location.find('::') != -1):
 
289
            usessh=False
 
290
        else:
 
291
            usessh=True
 
292
 
 
293
        if (push_location.find('://') != -1 or
 
294
            push_location.find(':') == -1):
 
295
            raise BzrCommandError("Invalid rsync path %r." % push_location)
 
296
 
 
297
        if working_tree:
 
298
            clean, non_source = is_clean(tree)
 
299
            if not clean:
 
300
                raise BzrCommandError(
 
301
                    'This tree has uncommitted changes or unknown'
 
302
                    ' (?) files.  Use "bzr status" to list them.')
 
303
                sys.exit(1)
 
304
            final_exclusions = non_source[:]
 
305
        else:
 
306
            wt = tree
 
307
            final_exclusions = []
 
308
            for path, status, kind, file_id, entry in wt.list_files():
 
309
                final_exclusions.append(path)
 
310
 
 
311
        final_exclusions.extend(exclusions)
 
312
        if not overwrite:
 
313
            try:
 
314
                if not history_subset(push_location, tree.branch,
 
315
                                      _rsync=my_rsync):
 
316
                    raise BzrCommandError(
 
317
                        "Local branch is not a newer version of remote"
 
318
                        " branch.")
 
319
            except RsyncNoFile:
 
320
                if not empty_or_absent(push_location):
 
321
                    raise BzrCommandError(
 
322
                        "Remote location is not a bzr branch (or empty"
 
323
                        " directory)")
 
324
            except RsyncStreamIO:
 
325
                raise BzrCommandError("Rsync could not use the"
 
326
                    " specified location.  Please ensure that"
 
327
                    ' "%s" is of the form "machine:/path".' % push_location)
 
328
        trace.note("Pushing to %s", push_location)
 
329
        my_rsync(tree.basedir+'/', push_location, ssh=usessh,
 
330
                 excludes=final_exclusions)
 
331
 
 
332
        set_push_data(tree, push_location)
 
333
    finally:
 
334
        tree.unlock()
282
335
 
283
336
 
284
337
def short_committer(committer):
292
345
    """Screen-scrape Apache listings"""
293
346
    apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
294
347
        ' <a href="'
295
 
    lines = t.get('.')
296
 
    expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
 
348
    t = t.clone()
 
349
    t._remote_path = lambda x: t.base
 
350
    try:
 
351
        lines = t.get('')
 
352
    except NoSuchFile:
 
353
        return
 
354
    expr = re.compile('<a[^>]*href="([^>]*)\/"[^>]*>', flags=re.I)
297
355
    for line in lines:
298
356
        match = expr.search(line)
299
357
        if match is None:
306
364
        yield url.rstrip('/')
307
365
 
308
366
 
309
 
def iter_branches(t, lister=None):
310
 
    """Iterate through all the branches under a transport"""
311
 
    for bzrdir in iter_bzrdirs(t, lister):
312
 
        try:
313
 
            branch = bzrdir.open_branch()
314
 
            if branch.bzrdir is bzrdir:
315
 
                yield branch
316
 
        except (NotBranchError, UnsupportedFormatError):
317
 
            pass
 
367
def list_branches(t):
 
368
    def is_inside(branch):
 
369
        return bool(branch.base.startswith(t.base))
 
370
 
 
371
    if t.base.startswith('http://'):
 
372
        def evaluate(bzrdir):
 
373
            try:
 
374
                branch = bzrdir.open_branch()
 
375
                if is_inside(branch):
 
376
                    return True, branch
 
377
                else:
 
378
                    return True, None
 
379
            except NotBranchError:
 
380
                return True, None
 
381
        return [b for b in BzrDir.find_bzrdirs(t, list_current=apache_ls,
 
382
                evaluate=evaluate) if b is not None]
 
383
    elif not t.listable():
 
384
        raise BzrCommandError("Can't list this type of location.")
 
385
    return [b for b in BzrDir.find_branches(t) if is_inside(b)]
 
386
 
 
387
 
 
388
def evaluate_branch_tree(bzrdir):
 
389
    try:
 
390
        tree, branch = bzrdir._get_tree_branch()
 
391
    except NotBranchError:
 
392
        return True, None
 
393
    else:
 
394
        return True, (branch, tree)
318
395
 
319
396
 
320
397
def iter_branch_tree(t, lister=None):
321
 
    for bzrdir in iter_bzrdirs(t, lister):
322
 
        try:
323
 
            wt = bzrdir.open_workingtree()
324
 
            yield wt.branch, wt
325
 
        except NoWorkingTree, UnsupportedFormatError:
326
 
            try:
327
 
                branch = bzrdir.open_branch()
328
 
                if branch.bzrdir is bzrdir:
329
 
                    yield branch, None
330
 
            except (NotBranchError, UnsupportedFormatError):
331
 
                continue
332
 
 
333
 
 
334
 
def iter_bzrdirs(t, lister=None):
335
 
    if lister is None:
336
 
        def lister(t):
337
 
            return t.list_dir('.')
338
 
    try:
339
 
        bzrdir = bzrdir_from_transport(t)
340
 
        yield bzrdir
341
 
    except (NotBranchError, UnsupportedFormatError, TransportError,
342
 
            PermissionDenied):
343
 
        pass
344
 
    try:
345
 
        for directory in lister(t):
346
 
            if directory == ".bzr":
347
 
                continue
348
 
            try:
349
 
                subt = t.clone(directory)
350
 
            except UnicodeDecodeError:
351
 
                continue
352
 
            for bzrdir in iter_bzrdirs(subt, lister):
353
 
                yield bzrdir
354
 
    except (NoSuchFile, PermissionDenied, TransportError):
355
 
        pass
356
 
 
357
 
    
358
 
def bzrdir_from_transport(t):
359
 
    """Open a bzrdir from a transport (not a location)"""
360
 
    format = BzrDirFormat.find_format(t)
361
 
    BzrDir._check_supported(format, False)
362
 
    return format.open(t)
 
398
    return (x for x in BzrDir.find_bzrdirs(t, evaluate=evaluate_branch_tree,
 
399
            list_current=lister) if x is not None)
 
400
 
 
401
 
 
402
def open_from_url(location):
 
403
    location = urlutils.normalize_url(location)
 
404
    dirname, basename = urlutils.split(location)
 
405
    if location.endswith('/') and not basename.endswith('/'):
 
406
        basename += '/'
 
407
    return get_transport(dirname).get(basename)
363
408
 
364
409
 
365
410
def run_tests():