~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Aaron Bentley
  • Date: 2007-12-27 16:30:52 UTC
  • Revision ID: abentley@panoramicfeedback.com-20071227163052-l4wmn2vsl91nbfjh
Remove test due to intentional behavior change

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Aaron Bentley
2
 
# <aaron.bentley@utoronto.ca>
 
1
# Copyright (C) 2005, 2006, 2007 Aaron Bentley <aaron.bentley@utoronto.ca>
 
2
# Copyright (C) 2007 John Arbash Meinel
3
3
#
4
4
#    This program is free software; you can redistribute it and/or modify
5
5
#    it under the terms of the GNU General Public License as published by
24
24
import sys
25
25
 
26
26
import bzrlib
 
27
from bzrlib import trace, urlutils
27
28
import bzrlib.errors
28
 
from bzrlib.errors import (BzrCommandError, NotBranchError, NoSuchFile,
29
 
                           UnsupportedFormatError, TransportError, 
30
 
                           NoWorkingTree, PermissionDenied)
 
29
from bzrlib.errors import (
 
30
    BzrCommandError,
 
31
    BzrError,
 
32
    ConnectionError,
 
33
    NotBranchError,
 
34
    NoSuchFile,
 
35
    NoWorkingTree,
 
36
    PermissionDenied,
 
37
    UnsupportedFormatError,
 
38
    TransportError,
 
39
    )
31
40
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
41
from bzrlib.transport import get_transport
32
42
 
33
43
def temp_tree():
34
44
    dirname = tempfile.mkdtemp("temp-branch")
40
50
def is_clean(cur_tree):
41
51
    """
42
52
    Return true if no files are modifed or unknown
43
 
    >>> import bzrlib.add
44
 
    >>> tree = temp_tree()
45
 
    >>> is_clean(tree)
46
 
    (True, [])
47
 
    >>> fooname = os.path.join(tree.basedir, "foo")
48
 
    >>> file(fooname, "wb").write("bar")
49
 
    >>> is_clean(tree)
50
 
    (True, [u'foo'])
51
 
    >>> bzrlib.add.smart_add_tree(tree, [tree.basedir])
52
 
    ([u'foo'], {})
53
 
    >>> is_clean(tree)
54
 
    (False, [])
55
 
    >>> tree.commit("added file", rev_id='commit-id')
56
 
    'commit-id'
57
 
    >>> is_clean(tree)
58
 
    (True, [])
59
 
    >>> rm_tree(tree)
60
53
    """
61
54
    old_tree = cur_tree.basis_tree()
62
55
    new_tree = cur_tree
63
56
    non_source = []
64
 
    for path, file_class, kind, file_id, entry in new_tree.list_files():
65
 
        if file_class in ('?', 'I'):
66
 
            non_source.append(path)
67
 
    delta = new_tree.changes_from(old_tree, want_unchanged=False)
 
57
    new_tree.lock_read()
 
58
    try:
 
59
        for path, file_class, kind, file_id, entry in new_tree.list_files():
 
60
            if file_class in ('?', 'I'):
 
61
                non_source.append(path)
 
62
        delta = new_tree.changes_from(old_tree, want_unchanged=False)
 
63
    finally:
 
64
        new_tree.unlock()
68
65
    return not delta.has_changed(), non_source
69
66
 
70
67
def set_push_data(tree, location):
112
109
    def __init__(self, rsync_name):
113
110
        Exception.__init__(self, "%s not found." % rsync_name)
114
111
 
115
 
def rsync(source, target, ssh=False, excludes=(), silent=False, 
 
112
 
 
113
def rsync(source, target, ssh=False, excludes=(), silent=False,
116
114
          rsync_name="rsync"):
117
115
    """
118
116
    >>> new_dir = tempfile.mkdtemp()
147
145
    except OSError, e:
148
146
        if e.errno == errno.ENOENT:
149
147
            raise NoRsync(rsync_name)
150
 
            
 
148
 
151
149
    proc.stdin.write('\n'.join(excludes)+'\n')
152
150
    proc.stdin.close()
153
151
    if silent:
189
187
        raise RsyncUnknownStatus(proc.returncode)
190
188
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
191
189
 
192
 
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent', 
 
190
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
193
191
              '.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
194
192
              '.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
195
193
              '.bzr/basis-inventory', '.bzr/inventory.backup.weave')
207
205
    def __init__(self):
208
206
        Exception.__init__(self, "Error in rsync protocol data stream.")
209
207
 
210
 
def get_revision_history(location):
 
208
 
 
209
class NotStandalone(BzrError):
 
210
 
 
211
    _format = '%(location) is not a standalone tree.'
 
212
    _internal = False
 
213
 
 
214
    def __init__(self, location):
 
215
        BzrError.__init__(self, location=location)
 
216
 
 
217
 
 
218
def get_revision_history(location, _rsync):
211
219
    tempdir = tempfile.mkdtemp('push')
 
220
    my_rsync = _rsync
 
221
    if my_rsync is None:
 
222
        my_rsync = rsync
212
223
    try:
213
224
        history_fname = os.path.join(tempdir, 'revision-history')
214
225
        try:
215
 
            cmd = rsync(location+'.bzr/revision-history', history_fname,
 
226
            cmd = my_rsync(location+'.bzr/revision-history', history_fname,
216
227
                        silent=True)
217
228
        except RsyncNoFile:
218
229
            cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
222
233
        shutil.rmtree(tempdir)
223
234
    return history
224
235
 
225
 
def history_subset(location, branch):
226
 
    remote_history = get_revision_history(location)
 
236
 
 
237
def history_subset(location, branch, _rsync=None):
 
238
    remote_history = get_revision_history(location, _rsync)
227
239
    local_history = branch.revision_history()
228
240
    if len(remote_history) > len(local_history):
229
241
        return False
230
242
    for local, remote in zip(remote_history, local_history):
231
243
        if local != remote:
232
 
            return False 
 
244
            return False
233
245
    return True
234
246
 
235
247
def empty_or_absent(location):
239
251
    except RsyncNoFile:
240
252
        return True
241
253
 
242
 
def rspush(tree, location=None, overwrite=False, working_tree=True):
243
 
    push_location = get_push_data(tree)
244
 
    if location is not None:
245
 
        if not location.endswith('/'):
246
 
            location += '/'
247
 
        push_location = location
248
 
    
249
 
    if push_location is None:
250
 
        raise BzrCommandError("No rspush location known or specified.")
251
 
 
252
 
    if (push_location.find('::') != -1):
253
 
        usessh=False
254
 
    else:
255
 
        usessh=True
256
 
 
257
 
    if (push_location.find('://') != -1 or
258
 
        push_location.find(':') == -1):
259
 
        raise BzrCommandError("Invalid rsync path %r." % push_location)
260
 
 
261
 
    if working_tree:
262
 
        clean, non_source = is_clean(tree)
263
 
        if not clean:
264
 
            print """Error: This tree has uncommitted changes or unknown (?) files.
265
 
    Use "bzr status" to list them."""
266
 
            sys.exit(1)
267
 
        final_exclusions = non_source[:]
268
 
    else:
269
 
        wt = tree
270
 
        final_exclusions = []
271
 
        for path, status, kind, file_id, entry in wt.list_files():
272
 
            final_exclusions.append(path)
273
 
 
274
 
    final_exclusions.extend(exclusions)
275
 
    if not overwrite:
276
 
        try:
277
 
            if not history_subset(push_location, tree.branch):
278
 
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
279
 
                                                    " newer version of remote"
280
 
                                                    " branch.")
281
 
        except RsyncNoFile:
282
 
            if not empty_or_absent(push_location):
283
 
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
284
 
                                                    " bzr branch (or empty"
285
 
                                                    " directory)")
286
 
        except RsyncStreamIO:
287
 
            raise bzrlib.errors.BzrCommandError("Rsync could not use the"
288
 
                " specified location.  Please ensure that"
289
 
                ' "%s" is of the form "machine:/path".' % push_location)
290
 
    print "Pushing to %s" % push_location
291
 
    rsync(tree.basedir+'/', push_location, ssh=usessh, 
292
 
          excludes=final_exclusions)
293
 
 
294
 
    set_push_data(tree, push_location)
 
254
def rspush(tree, location=None, overwrite=False, working_tree=True,
 
255
    _rsync=None):
 
256
    tree.lock_write()
 
257
    try:
 
258
        my_rsync = _rsync
 
259
        if my_rsync is None:
 
260
            my_rsync = rsync
 
261
        if (tree.bzrdir.root_transport.base !=
 
262
            tree.branch.bzrdir.root_transport.base):
 
263
            raise NotStandalone(tree.bzrdir.root_transport.base)
 
264
        if (tree.branch.get_bound_location() is not None):
 
265
            raise NotStandalone(tree.bzrdir.root_transport.base)
 
266
        if (tree.branch.repository.is_shared()):
 
267
            raise NotStandalone(tree.bzrdir.root_transport.base)
 
268
        push_location = get_push_data(tree)
 
269
        if location is not None:
 
270
            if not location.endswith('/'):
 
271
                location += '/'
 
272
            push_location = location
 
273
 
 
274
        if push_location is None:
 
275
            raise BzrCommandError("No rspush location known or specified.")
 
276
 
 
277
        if (push_location.find('::') != -1):
 
278
            usessh=False
 
279
        else:
 
280
            usessh=True
 
281
 
 
282
        if (push_location.find('://') != -1 or
 
283
            push_location.find(':') == -1):
 
284
            raise BzrCommandError("Invalid rsync path %r." % push_location)
 
285
 
 
286
        if working_tree:
 
287
            clean, non_source = is_clean(tree)
 
288
            if not clean:
 
289
                raise bzrlib.errors.BzrCommandError(
 
290
                    'This tree has uncommitted changes or unknown'
 
291
                    ' (?) files.  Use "bzr status" to list them.')
 
292
                sys.exit(1)
 
293
            final_exclusions = non_source[:]
 
294
        else:
 
295
            wt = tree
 
296
            final_exclusions = []
 
297
            for path, status, kind, file_id, entry in wt.list_files():
 
298
                final_exclusions.append(path)
 
299
 
 
300
        final_exclusions.extend(exclusions)
 
301
        if not overwrite:
 
302
            try:
 
303
                if not history_subset(push_location, tree.branch,
 
304
                                      _rsync=my_rsync):
 
305
                    raise bzrlib.errors.BzrCommandError(
 
306
                        "Local branch is not a newer version of remote"
 
307
                        " branch.")
 
308
            except RsyncNoFile:
 
309
                if not empty_or_absent(push_location):
 
310
                    raise bzrlib.errors.BzrCommandError(
 
311
                        "Remote location is not a bzr branch (or empty"
 
312
                        " directory)")
 
313
            except RsyncStreamIO:
 
314
                raise bzrlib.errors.BzrCommandError("Rsync could not use the"
 
315
                    " specified location.  Please ensure that"
 
316
                    ' "%s" is of the form "machine:/path".' % push_location)
 
317
        trace.note("Pushing to %s", push_location)
 
318
        my_rsync(tree.basedir+'/', push_location, ssh=usessh,
 
319
              excludes=final_exclusions)
 
320
 
 
321
        set_push_data(tree, push_location)
 
322
    finally:
 
323
        tree.unlock()
295
324
 
296
325
 
297
326
def short_committer(committer):
305
334
    """Screen-scrape Apache listings"""
306
335
    apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
307
336
        ' <a href="'
308
 
    lines = t.get('.')
309
 
    expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
 
337
    t = t.clone()
 
338
    t._remote_path = lambda x: t.base
 
339
    try:
 
340
        lines = t.get('')
 
341
    except bzrlib.errors.NoSuchFile:
 
342
        return
 
343
    expr = re.compile('<a[^>]*href="([^>]*)\/"[^>]*>', flags=re.I)
310
344
    for line in lines:
311
345
        match = expr.search(line)
312
346
        if match is None:
319
353
        yield url.rstrip('/')
320
354
 
321
355
 
322
 
def iter_branches(t, lister=None):
323
 
    """Iterate through all the branches under a transport"""
324
 
    for bzrdir in iter_bzrdirs(t, lister):
325
 
        try:
326
 
            branch = bzrdir.open_branch()
327
 
            if branch.bzrdir is bzrdir:
328
 
                yield branch
329
 
        except (NotBranchError, UnsupportedFormatError):
330
 
            pass
 
356
def list_branches(t):
 
357
    def is_inside(branch):
 
358
        return bool(branch.base.startswith(t.base))
 
359
 
 
360
    if t.base.startswith('http://'):
 
361
        def evaluate(bzrdir):
 
362
            try:
 
363
                branch = bzrdir.open_branch()
 
364
                if is_inside(branch):
 
365
                    return True, branch
 
366
                else:
 
367
                    return True, None
 
368
            except NotBranchError:
 
369
                return True, None
 
370
        return [b for b in BzrDir.find_bzrdirs(t, list_current=apache_ls,
 
371
                evaluate=evaluate) if b is not None]
 
372
    elif not t.listable():
 
373
        raise BzrCommandError("Can't list this type of location.")
 
374
    return [b for b in BzrDir.find_branches(t) if is_inside(b)]
 
375
 
 
376
 
 
377
def evaluate_branch_tree(bzrdir):
 
378
    try:
 
379
        tree, branch = bzrdir._get_tree_branch()
 
380
    except NotBranchError:
 
381
        return True, None
 
382
    else:
 
383
        return True, (branch, tree)
331
384
 
332
385
 
333
386
def iter_branch_tree(t, lister=None):
334
 
    for bzrdir in iter_bzrdirs(t, lister):
335
 
        try:
336
 
            wt = bzrdir.open_workingtree()
337
 
            yield wt.branch, wt
338
 
        except NoWorkingTree, UnsupportedFormatError:
339
 
            try:
340
 
                branch = bzrdir.open_branch()
341
 
                if branch.bzrdir is bzrdir:
342
 
                    yield branch, None
343
 
            except (NotBranchError, UnsupportedFormatError):
344
 
                continue
345
 
 
346
 
 
347
 
def iter_bzrdirs(t, lister=None):
348
 
    if lister is None:
349
 
        def lister(t):
350
 
            return t.list_dir('.')
351
 
    try:
352
 
        bzrdir = bzrdir_from_transport(t)
353
 
        yield bzrdir
354
 
    except (NotBranchError, UnsupportedFormatError, TransportError,
355
 
            PermissionDenied):
356
 
        pass
357
 
    try:
358
 
        for directory in lister(t):
359
 
            if directory == ".bzr":
360
 
                continue
361
 
            try:
362
 
                subt = t.clone(directory)
363
 
            except UnicodeDecodeError:
364
 
                continue
365
 
            for bzrdir in iter_bzrdirs(subt, lister):
366
 
                yield bzrdir
367
 
    except (NoSuchFile, PermissionDenied, TransportError):
368
 
        pass
369
 
 
370
 
    
371
 
def bzrdir_from_transport(t):
372
 
    """Open a bzrdir from a transport (not a location)"""
373
 
    format = BzrDirFormat.find_format(t)
374
 
    BzrDir._check_supported(format, False)
375
 
    return format.open(t)
 
387
    return (x for x in BzrDir.find_bzrdirs(t, evaluate=evaluate_branch_tree,
 
388
            list_current=lister) if x is not None)
 
389
 
 
390
 
 
391
def open_from_url(location):
 
392
    location = urlutils.normalize_url(location)
 
393
    dirname, basename = urlutils.split(location)
 
394
    return get_transport(dirname).get(basename)
376
395
 
377
396
 
378
397
def run_tests():