~bzr/ubuntu/lucid/bzrtools/beta-ppa

0.4.612 by Aaron Bentley
Update email address
1
# Copyright (C) 2005, 2006, 2007 Aaron Bentley <aaron@aaronbentley.com>
0.4.513 by Aaron Bentley
Fix change dectection for dirstate
2
# Copyright (C) 2007 John Arbash Meinel
0.4.89 by Aaron Bentley
Added copyright/GPL notices
3
#
4
#    This program is free software; you can redistribute it and/or modify
5
#    it under the terms of the GNU General Public License as published by
6
#    the Free Software Foundation; either version 2 of the License, or
7
#    (at your option) any later version.
8
#
9
#    This program is distributed in the hope that it will be useful,
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
#    GNU General Public License for more details.
13
#
14
#    You should have received a copy of the GNU General Public License
15
#    along with this program; if not, write to the Free Software
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
17
import codecs
18
import errno
0.4.19 by abentley
librified most of the pull script
19
import os
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
20
import re
0.4.16 by abentley
Got is_clean under test, added setters/getters for pull data
21
import tempfile
22
import shutil
0.4.117 by aaron.bentley at utoronto
Excluded non-source files
23
from subprocess import Popen, PIPE
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
24
import sys
25
26
import bzrlib
0.4.606 by Aaron Bentley
Support branch6 formats in rspush
27
from bzrlib import revision as _mod_revision, trace, urlutils
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
28
import bzrlib.errors
0.4.583 by Aaron Bentley
rspush requires standalone trees
29
from bzrlib.errors import (
30
    BzrCommandError,
31
    BzrError,
32
    ConnectionError,
33
    NotBranchError,
34
    NoSuchFile,
35
    NoWorkingTree,
36
    PermissionDenied,
37
    UnsupportedFormatError,
38
    TransportError,
39
    )
0.4.352 by Aaron Bentley
Added branches subcommand
40
from bzrlib.bzrdir import BzrDir, BzrDirFormat
0.4.563 by Aaron Bentley
Allow importing directly from a URL
41
from bzrlib.transport import get_transport
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
42
43
def temp_tree():
0.4.16 by abentley
Got is_clean under test, added setters/getters for pull data
44
    dirname = tempfile.mkdtemp("temp-branch")
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
45
    return BzrDir.create_standalone_workingtree(dirname)
46
47
def rm_tree(tree):
48
    shutil.rmtree(tree.basedir)
49
50
def is_clean(cur_tree):
0.4.16 by abentley
Got is_clean under test, added setters/getters for pull data
51
    """
52
    Return true if no files are modifed or unknown
53
    """
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
54
    old_tree = cur_tree.basis_tree()
55
    new_tree = cur_tree
0.4.117 by aaron.bentley at utoronto
Excluded non-source files
56
    non_source = []
0.4.513 by Aaron Bentley
Fix change dectection for dirstate
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()
0.12.3 by Aaron Bentley
Switched to TreeDelta.has_changed
65
    return not delta.has_changed(), non_source
0.4.16 by abentley
Got is_clean under test, added setters/getters for pull data
66
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
67
def set_push_data(tree, location):
0.4.648 by Aaron Bentley
Fix deprecations
68
    tree.branch._transport.put_bytes("x-push-data", "%s\n" % location)
0.4.20 by abentley
added bzr-push command
69
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
70
def get_push_data(tree):
0.4.20 by abentley
added bzr-push command
71
    """
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
72
    >>> tree = temp_tree()
73
    >>> get_push_data(tree) is None
0.4.20 by abentley
added bzr-push command
74
    True
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
75
    >>> set_push_data(tree, 'http://somewhere')
76
    >>> get_push_data(tree)
0.4.394 by Aaron Bentley
Use get_utf8 and put_utf8 instead of controlfilename
77
    u'http://somewhere'
0.20.59 by Aaron Bentley
Reverted bzrtools.py to mainline version.
78
    >>> rm_tree(tree)
0.4.20 by abentley
added bzr-push command
79
    """
0.4.394 by Aaron Bentley
Use get_utf8 and put_utf8 instead of controlfilename
80
    try:
0.4.648 by Aaron Bentley
Fix deprecations
81
        location = tree.branch._transport.get('x-push-data').read()
0.4.394 by Aaron Bentley
Use get_utf8 and put_utf8 instead of controlfilename
82
    except NoSuchFile:
0.4.20 by abentley
added bzr-push command
83
        return None
0.4.648 by Aaron Bentley
Fix deprecations
84
    location = location.decode('utf-8')
0.4.394 by Aaron Bentley
Use get_utf8 and put_utf8 instead of controlfilename
85
    return location.rstrip('\n')
0.4.20 by abentley
added bzr-push command
86
0.4.19 by abentley
librified most of the pull script
87
"""
88
>>> shell_escape('hello')
89
'\h\e\l\l\o'
90
"""
91
def shell_escape(arg):
92
    return "".join(['\\'+c for c in arg])
93
94
def safe_system(args):
95
    """
96
    >>> real_system = os.system
97
    >>> os.system = sys.stdout.write
98
    >>> safe_system(['a', 'b', 'cd'])
99
    \\a \\b \\c\\d
100
    >>> os.system = real_system
101
    """
102
    arg_str = " ".join([shell_escape(a) for a in args])
103
    return os.system(arg_str)
104
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
105
class RsyncUnknownStatus(Exception):
106
    def __init__(self, status):
107
        Exception.__init__(self, "Unknown status: %d" % status)
108
0.4.199 by Aaron Bentley
Updated doctests
109
class NoRsync(Exception):
110
    def __init__(self, rsync_name):
111
        Exception.__init__(self, "%s not found." % rsync_name)
112
0.4.583 by Aaron Bentley
rspush requires standalone trees
113
0.51.2 by Charlie Shepherd
Remove all trailing whitespace
114
def rsync(source, target, ssh=False, excludes=(), silent=False,
0.4.199 by Aaron Bentley
Updated doctests
115
          rsync_name="rsync"):
116
    cmd = [rsync_name, "-av", "--delete"]
0.4.20 by abentley
added bzr-push command
117
    if ssh:
118
        cmd.extend(('-e', 'ssh'))
0.4.117 by aaron.bentley at utoronto
Excluded non-source files
119
    if len(excludes) > 0:
120
        cmd.extend(('--exclude-from', '-'))
0.4.20 by abentley
added bzr-push command
121
    cmd.extend((source, target))
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
122
    if silent:
123
        stderr = PIPE
124
        stdout = PIPE
125
    else:
126
        stderr = None
127
        stdout = None
0.4.199 by Aaron Bentley
Updated doctests
128
    try:
129
        proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
130
    except OSError, e:
131
        if e.errno == errno.ENOENT:
132
            raise NoRsync(rsync_name)
0.51.2 by Charlie Shepherd
Remove all trailing whitespace
133
0.4.117 by aaron.bentley at utoronto
Excluded non-source files
134
    proc.stdin.write('\n'.join(excludes)+'\n')
135
    proc.stdin.close()
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
136
    if silent:
137
        proc.stderr.read()
138
        proc.stderr.close()
139
        proc.stdout.read()
140
        proc.stdout.close()
0.4.147 by Robert Collins
make bzr selftest run the plugins tests, and fix them
141
    proc.wait()
0.8.1 by Eirik Nygaard
Add check for rsync return code 12, error in rsync protocol data stream.
142
    if proc.returncode == 12:
0.4.201 by Aaron Bentley
Merged error handling for bad rsync locations. (Eirik Nygaard)
143
        raise RsyncStreamIO()
0.8.1 by Eirik Nygaard
Add check for rsync return code 12, error in rsync protocol data stream.
144
    elif proc.returncode == 23:
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
145
        raise RsyncNoFile(source)
146
    elif proc.returncode != 0:
147
        raise RsyncUnknownStatus(proc.returncode)
0.4.147 by Robert Collins
make bzr selftest run the plugins tests, and fix them
148
    return cmd
0.4.117 by aaron.bentley at utoronto
Excluded non-source files
149
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
150
151
def rsync_ls(source, ssh=False, silent=True):
152
    cmd = ["rsync"]
153
    if ssh:
154
        cmd.extend(('-e', 'ssh'))
155
    cmd.append(source)
156
    if silent:
157
        stderr = PIPE
158
    else:
159
        stderr = None
160
    proc = Popen(cmd, stderr=stderr, stdout=PIPE)
161
    result = proc.stdout.read()
162
    proc.stdout.close()
163
    if silent:
164
        proc.stderr.read()
165
        proc.stderr.close()
166
    proc.wait()
0.8.1 by Eirik Nygaard
Add check for rsync return code 12, error in rsync protocol data stream.
167
    if proc.returncode == 12:
0.4.201 by Aaron Bentley
Merged error handling for bad rsync locations. (Eirik Nygaard)
168
        raise RsyncStreamIO()
0.8.1 by Eirik Nygaard
Add check for rsync return code 12, error in rsync protocol data stream.
169
    elif proc.returncode == 23:
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
170
        raise RsyncNoFile(source)
171
    elif proc.returncode != 0:
172
        raise RsyncUnknownStatus(proc.returncode)
173
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
174
0.51.2 by Charlie Shepherd
Remove all trailing whitespace
175
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
0.4.332 by Aaron Bentley
Added exclusions for x-push and parent in format6 branches
176
              '.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
177
              '.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
178
              '.bzr/basis-inventory', '.bzr/inventory.backup.weave')
0.4.19 by abentley
librified most of the pull script
179
0.4.20 by abentley
added bzr-push command
180
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
181
def read_revision_history(fname):
182
    return [l.rstrip('\r\n') for l in
183
            codecs.open(fname, 'rb', 'utf-8').readlines()]
184
0.4.606 by Aaron Bentley
Support branch6 formats in rspush
185
186
def read_revision_info(path):
187
    """Parse a last_revision file to determine revision_info"""
188
    line = open(path, 'rb').readlines()[0].strip('\n')
189
    revno, revision_id = line.split(' ', 1)
190
    revno = int(revno)
191
    return revno, revision_id
192
193
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
194
class RsyncNoFile(Exception):
195
    def __init__(self, path):
196
        Exception.__init__(self, "No such file %s" % path)
0.4.201 by Aaron Bentley
Merged error handling for bad rsync locations. (Eirik Nygaard)
197
0.8.1 by Eirik Nygaard
Add check for rsync return code 12, error in rsync protocol data stream.
198
class RsyncStreamIO(Exception):
0.4.201 by Aaron Bentley
Merged error handling for bad rsync locations. (Eirik Nygaard)
199
    def __init__(self):
200
        Exception.__init__(self, "Error in rsync protocol data stream.")
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
201
0.4.583 by Aaron Bentley
rspush requires standalone trees
202
203
class NotStandalone(BzrError):
204
0.69.4 by Aaron Bentley
Fix NotStandalone (#277652)
205
    _fmt = '%(location)s is not a standalone tree.'
0.4.583 by Aaron Bentley
rspush requires standalone trees
206
    _internal = False
207
208
    def __init__(self, location):
209
        BzrError.__init__(self, location=location)
210
211
212
def get_revision_history(location, _rsync):
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
213
    tempdir = tempfile.mkdtemp('push')
0.4.583 by Aaron Bentley
rspush requires standalone trees
214
    my_rsync = _rsync
215
    if my_rsync is None:
216
        my_rsync = rsync
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
217
    try:
218
        history_fname = os.path.join(tempdir, 'revision-history')
0.4.330 by Aaron Bentley
Got push working with new-format branches
219
        try:
0.4.583 by Aaron Bentley
rspush requires standalone trees
220
            cmd = my_rsync(location+'.bzr/revision-history', history_fname,
0.4.330 by Aaron Bentley
Got push working with new-format branches
221
                        silent=True)
222
        except RsyncNoFile:
223
            cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
224
                        silent=True)
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
225
        history = read_revision_history(history_fname)
226
    finally:
227
        shutil.rmtree(tempdir)
228
    return history
229
0.4.583 by Aaron Bentley
rspush requires standalone trees
230
0.4.606 by Aaron Bentley
Support branch6 formats in rspush
231
def get_revision_info(location, _rsync):
232
    """Get the revsision_info for an rsync-able branch"""
233
    tempdir = tempfile.mkdtemp('push')
234
    my_rsync = _rsync
235
    if my_rsync is None:
236
        my_rsync = rsync
237
    try:
238
        info_fname = os.path.join(tempdir, 'last-revision')
239
        cmd = rsync(location+'.bzr/branch/last-revision', info_fname,
240
                    silent=True)
241
        return read_revision_info(info_fname)
242
    finally:
243
        shutil.rmtree(tempdir)
244
245
0.4.583 by Aaron Bentley
rspush requires standalone trees
246
def history_subset(location, branch, _rsync=None):
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
247
    local_history = branch.revision_history()
0.4.606 by Aaron Bentley
Support branch6 formats in rspush
248
    try:
249
        remote_history = get_revision_history(location, _rsync)
250
    except RsyncNoFile:
251
        revno, revision_id = get_revision_info(location, _rsync)
252
        if revision_id == _mod_revision.NULL_REVISION:
253
            return True
254
        return bool(revision_id.decode('utf-8') in local_history)
255
    else:
256
        if len(remote_history) > len(local_history):
0.51.2 by Charlie Shepherd
Remove all trailing whitespace
257
            return False
0.4.606 by Aaron Bentley
Support branch6 formats in rspush
258
        for local, remote in zip(remote_history, local_history):
259
            if local != remote:
260
                return False
261
        return True
262
0.4.195 by Aaron Bentley
prevented accidental overwrites from push
263
264
def empty_or_absent(location):
265
    try:
266
        files = rsync_ls(location)
267
        return files == ['.']
268
    except RsyncNoFile:
269
        return True
270
0.4.583 by Aaron Bentley
rspush requires standalone trees
271
def rspush(tree, location=None, overwrite=False, working_tree=True,
272
    _rsync=None):
0.4.584 by Aaron Bentley
Fix rspush lock error when excluding working tree
273
    tree.lock_write()
274
    try:
275
        my_rsync = _rsync
276
        if my_rsync is None:
277
            my_rsync = rsync
278
        if (tree.bzrdir.root_transport.base !=
279
            tree.branch.bzrdir.root_transport.base):
280
            raise NotStandalone(tree.bzrdir.root_transport.base)
281
        if (tree.branch.get_bound_location() is not None):
282
            raise NotStandalone(tree.bzrdir.root_transport.base)
283
        if (tree.branch.repository.is_shared()):
284
            raise NotStandalone(tree.bzrdir.root_transport.base)
285
        push_location = get_push_data(tree)
286
        if location is not None:
287
            if not location.endswith('/'):
288
                location += '/'
289
            push_location = location
290
291
        if push_location is None:
292
            raise BzrCommandError("No rspush location known or specified.")
293
294
        if (push_location.find('::') != -1):
295
            usessh=False
296
        else:
297
            usessh=True
298
299
        if (push_location.find('://') != -1 or
300
            push_location.find(':') == -1):
301
            raise BzrCommandError("Invalid rsync path %r." % push_location)
302
303
        if working_tree:
304
            clean, non_source = is_clean(tree)
305
            if not clean:
0.4.593 by Aaron Bentley
Clean up test kipple
306
                raise bzrlib.errors.BzrCommandError(
307
                    'This tree has uncommitted changes or unknown'
308
                    ' (?) files.  Use "bzr status" to list them.')
0.4.584 by Aaron Bentley
Fix rspush lock error when excluding working tree
309
                sys.exit(1)
310
            final_exclusions = non_source[:]
311
        else:
312
            wt = tree
313
            final_exclusions = []
314
            for path, status, kind, file_id, entry in wt.list_files():
315
                final_exclusions.append(path)
316
317
        final_exclusions.extend(exclusions)
318
        if not overwrite:
319
            try:
320
                if not history_subset(push_location, tree.branch,
321
                                      _rsync=my_rsync):
322
                    raise bzrlib.errors.BzrCommandError(
323
                        "Local branch is not a newer version of remote"
324
                        " branch.")
325
            except RsyncNoFile:
326
                if not empty_or_absent(push_location):
327
                    raise bzrlib.errors.BzrCommandError(
328
                        "Remote location is not a bzr branch (or empty"
329
                        " directory)")
330
            except RsyncStreamIO:
331
                raise bzrlib.errors.BzrCommandError("Rsync could not use the"
332
                    " specified location.  Please ensure that"
333
                    ' "%s" is of the form "machine:/path".' % push_location)
0.4.593 by Aaron Bentley
Clean up test kipple
334
        trace.note("Pushing to %s", push_location)
0.4.584 by Aaron Bentley
Fix rspush lock error when excluding working tree
335
        my_rsync(tree.basedir+'/', push_location, ssh=usessh,
0.4.606 by Aaron Bentley
Support branch6 formats in rspush
336
                 excludes=final_exclusions)
0.4.584 by Aaron Bentley
Fix rspush lock error when excluding working tree
337
338
        set_push_data(tree, push_location)
339
    finally:
340
        tree.unlock()
0.4.20 by abentley
added bzr-push command
341
0.19.2 by Aaron Bentley
Applied Robert's random fixes as non-merges
342
0.4.292 by Aaron Bentley
Introduced branch-history command
343
def short_committer(committer):
344
    new_committer = re.sub('<.*>', '', committer).strip(' ')
345
    if len(new_committer) < 2:
346
        return committer
347
    return new_committer
348
349
0.4.354 by Aaron Bentley
Added apache index scraping to the branches command
350
def apache_ls(t):
351
    """Screen-scrape Apache listings"""
0.4.355 by Aaron Bentley
Handled more hrefs
352
    apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
353
        ' <a href="'
0.4.571 by Aaron Bentley
Branches works with Apache again
354
    t = t.clone()
355
    t._remote_path = lambda x: t.base
0.4.602 by Aaron Bentley
Update branches to new find_branches API
356
    try:
357
        lines = t.get('')
358
    except bzrlib.errors.NoSuchFile:
359
        return
360
    expr = re.compile('<a[^>]*href="([^>]*)\/"[^>]*>', flags=re.I)
0.4.354 by Aaron Bentley
Added apache index scraping to the branches command
361
    for line in lines:
0.4.355 by Aaron Bentley
Handled more hrefs
362
        match = expr.search(line)
363
        if match is None:
364
            continue
365
        url = match.group(1)
366
        if url.startswith('http://') or url.startswith('/') or '../' in url:
367
            continue
0.4.357 by Aaron Bentley
Tweakage of the apache scraper
368
        if '?' in url:
369
            continue
0.4.355 by Aaron Bentley
Handled more hrefs
370
        yield url.rstrip('/')
0.4.354 by Aaron Bentley
Added apache index scraping to the branches command
371
372
0.4.603 by Aaron Bentley
Update branches, multi-pull to new APIs, create trees
373
def list_branches(t):
374
    def is_inside(branch):
375
        return bool(branch.base.startswith(t.base))
376
377
    if t.base.startswith('http://'):
378
        def evaluate(bzrdir):
379
            try:
380
                branch = bzrdir.open_branch()
381
                if is_inside(branch):
382
                    return True, branch
383
                else:
384
                    return True, None
0.4.604 by Aaron Bentley
Fix error handling
385
            except NotBranchError:
0.4.603 by Aaron Bentley
Update branches, multi-pull to new APIs, create trees
386
                return True, None
387
        return [b for b in BzrDir.find_bzrdirs(t, list_current=apache_ls,
388
                evaluate=evaluate) if b is not None]
389
    elif not t.listable():
390
        raise BzrCommandError("Can't list this type of location.")
391
    return [b for b in BzrDir.find_branches(t) if is_inside(b)]
392
393
394
def evaluate_branch_tree(bzrdir):
395
    try:
396
        tree, branch = bzrdir._get_tree_branch()
397
    except NotBranchError:
398
        return True, None
399
    else:
400
        return True, (branch, tree)
0.4.353 by Aaron Bentley
Added multi-pull, working on branches and checkouts
401
0.4.354 by Aaron Bentley
Added apache index scraping to the branches command
402
403
def iter_branch_tree(t, lister=None):
0.4.603 by Aaron Bentley
Update branches, multi-pull to new APIs, create trees
404
    return (x for x in BzrDir.find_bzrdirs(t, evaluate=evaluate_branch_tree,
405
            list_current=lister) if x is not None)
0.4.352 by Aaron Bentley
Added branches subcommand
406
407
0.4.563 by Aaron Bentley
Allow importing directly from a URL
408
def open_from_url(location):
409
    location = urlutils.normalize_url(location)
410
    dirname, basename = urlutils.split(location)
0.69.36 by Aaron Bentley
bzr patch handles URLs with trailing slashes
411
    if location.endswith('/') and not basename.endswith('/'):
412
        basename += '/'
0.4.563 by Aaron Bentley
Allow importing directly from a URL
413
    return get_transport(dirname).get(basename)
414
415
0.4.19 by abentley
librified most of the pull script
416
def run_tests():
0.4.16 by abentley
Got is_clean under test, added setters/getters for pull data
417
    import doctest
0.4.18 by abentley
Finished implementing bzr-pull
418
    result = doctest.testmod()
0.4.19 by abentley
librified most of the pull script
419
    if result[1] > 0:
420
        if result[0] == 0:
421
            print "All tests passed"
422
    else:
423
        print "No tests to run"
424
if __name__ == "__main__":
425
    run_tests()