~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Aaron Bentley
  • Date: 2007-11-23 15:13:59 UTC
  • Revision ID: abentley@panoramicfeedback.com-20071123151359-yrjc6ta2fkbtu9ht
Remove switch (now in bzr itself)

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 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
                print ('Error: This tree has uncommitted changes or unknown'
 
290
                ' (?) files.  Use "bzr status" to list them.')
 
291
                sys.exit(1)
 
292
            final_exclusions = non_source[:]
 
293
        else:
 
294
            wt = tree
 
295
            final_exclusions = []
 
296
            for path, status, kind, file_id, entry in wt.list_files():
 
297
                final_exclusions.append(path)
 
298
 
 
299
        final_exclusions.extend(exclusions)
 
300
        if not overwrite:
 
301
            try:
 
302
                if not history_subset(push_location, tree.branch,
 
303
                                      _rsync=my_rsync):
 
304
                    raise bzrlib.errors.BzrCommandError(
 
305
                        "Local branch is not a newer version of remote"
 
306
                        " branch.")
 
307
            except RsyncNoFile:
 
308
                if not empty_or_absent(push_location):
 
309
                    raise bzrlib.errors.BzrCommandError(
 
310
                        "Remote location is not a bzr branch (or empty"
 
311
                        " directory)")
 
312
            except RsyncStreamIO:
 
313
                raise bzrlib.errors.BzrCommandError("Rsync could not use the"
 
314
                    " specified location.  Please ensure that"
 
315
                    ' "%s" is of the form "machine:/path".' % push_location)
 
316
        print "Pushing to %s" % push_location
 
317
        my_rsync(tree.basedir+'/', push_location, ssh=usessh,
 
318
              excludes=final_exclusions)
 
319
 
 
320
        set_push_data(tree, push_location)
 
321
    finally:
 
322
        tree.unlock()
295
323
 
296
324
 
297
325
def short_committer(committer):
305
333
    """Screen-scrape Apache listings"""
306
334
    apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
307
335
        ' <a href="'
308
 
    lines = t.get('.')
 
336
    t = t.clone()
 
337
    t._remote_path = lambda x: t.base
 
338
    lines = t.get('')
309
339
    expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
310
340
    for line in lines:
311
341
        match = expr.search(line)
351
381
    try:
352
382
        bzrdir = bzrdir_from_transport(t)
353
383
        yield bzrdir
 
384
    except (ConnectionError):
 
385
        raise
354
386
    except (NotBranchError, UnsupportedFormatError, TransportError,
355
387
            PermissionDenied):
356
388
        pass
367
399
    except (NoSuchFile, PermissionDenied, TransportError):
368
400
        pass
369
401
 
370
 
    
 
402
 
371
403
def bzrdir_from_transport(t):
372
404
    """Open a bzrdir from a transport (not a location)"""
373
405
    format = BzrDirFormat.find_format(t)
375
407
    return format.open(t)
376
408
 
377
409
 
 
410
def open_from_url(location):
 
411
    location = urlutils.normalize_url(location)
 
412
    dirname, basename = urlutils.split(location)
 
413
    return get_transport(dirname).get(basename)
 
414
 
 
415
 
378
416
def run_tests():
379
417
    import doctest
380
418
    result = doctest.testmod()