~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Aaron Bentley
  • Date: 2006-06-14 18:09:53 UTC
  • mto: This revision was merged to the branch mainline in revision 395.
  • Revision ID: abentley@panoramicfeedback.com-20060614180953-4671478534fd8823
Update NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2009, 2011-2012 Aaron Bentley <aaron@aaronbentley.com>
2
 
# Copyright (C) 2007 John Arbash Meinel
 
1
# Copyright (C) 2005 Aaron Bentley
 
2
# <aaron.bentley@utoronto.ca>
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
14
14
#    You should have received a copy of the GNU General Public License
15
15
#    along with this program; if not, write to the Free Software
16
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
import codecs
 
18
import errno
 
19
import os
17
20
import re
18
 
 
19
 
from bzrlib import urlutils
20
 
from bzrlib.errors import (
21
 
    BzrCommandError,
22
 
    NotBranchError,
23
 
    NoSuchFile,
24
 
    )
25
 
from bzrlib.bzrdir import BzrDir
26
 
from bzrlib.transport import get_transport
 
21
import tempfile
 
22
import shutil
 
23
from subprocess import Popen, PIPE
 
24
import sys
 
25
 
 
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
 
32
 
 
33
def temp_tree():
 
34
    dirname = tempfile.mkdtemp("temp-branch")
 
35
    return BzrDir.create_standalone_workingtree(dirname)
 
36
 
 
37
def rm_tree(tree):
 
38
    shutil.rmtree(tree.basedir)
 
39
 
 
40
def is_clean(cur_tree):
 
41
    """
 
42
    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")
 
56
    >>> is_clean(tree)
 
57
    (True, [])
 
58
    >>> rm_tree(tree)
 
59
    """
 
60
    from bzrlib.diff import compare_trees
 
61
    old_tree = cur_tree.basis_tree()
 
62
    new_tree = cur_tree
 
63
    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 = compare_trees(old_tree, new_tree, want_unchanged=False)
 
68
    return not delta.has_changed(), non_source
 
69
 
 
70
def set_push_data(tree, location):
 
71
    push_file = file (tree.branch.control_files.controlfilename("x-push-data"), "wb")
 
72
    push_file.write("%s\n" % location)
 
73
 
 
74
def get_push_data(tree):
 
75
    """
 
76
    >>> tree = temp_tree()
 
77
    >>> get_push_data(tree) is None
 
78
    True
 
79
    >>> set_push_data(tree, 'http://somewhere')
 
80
    >>> get_push_data(tree)
 
81
    'http://somewhere'
 
82
    >>> rm_tree(tree)
 
83
    """
 
84
    filename = tree.branch.control_files.controlfilename("x-push-data")
 
85
    if not os.path.exists(filename):
 
86
        return None
 
87
    push_file = file (filename, "rb")
 
88
    (location,) = [f.rstrip('\n') for f in push_file]
 
89
    return location
 
90
 
 
91
"""
 
92
>>> shell_escape('hello')
 
93
'\h\e\l\l\o'
 
94
"""
 
95
def shell_escape(arg):
 
96
    return "".join(['\\'+c for c in arg])
 
97
 
 
98
def safe_system(args):
 
99
    """
 
100
    >>> real_system = os.system
 
101
    >>> os.system = sys.stdout.write
 
102
    >>> safe_system(['a', 'b', 'cd'])
 
103
    \\a \\b \\c\\d
 
104
    >>> os.system = real_system
 
105
    """
 
106
    arg_str = " ".join([shell_escape(a) for a in args])
 
107
    return os.system(arg_str)
 
108
 
 
109
class RsyncUnknownStatus(Exception):
 
110
    def __init__(self, status):
 
111
        Exception.__init__(self, "Unknown status: %d" % status)
 
112
 
 
113
class NoRsync(Exception):
 
114
    def __init__(self, rsync_name):
 
115
        Exception.__init__(self, "%s not found." % rsync_name)
 
116
 
 
117
def rsync(source, target, ssh=False, excludes=(), silent=False, 
 
118
          rsync_name="rsync"):
 
119
    """
 
120
    >>> new_dir = tempfile.mkdtemp()
 
121
    >>> old_dir = os.getcwd()
 
122
    >>> os.chdir(new_dir)
 
123
    >>> rsync("a", "b", silent=True)
 
124
    Traceback (most recent call last):
 
125
    RsyncNoFile: No such file...
 
126
    >>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
 
127
    Traceback (most recent call last):
 
128
    RsyncNoFile: No such file...
 
129
    >>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
 
130
    Traceback (most recent call last):
 
131
    NoRsync: rsyncc not found.
 
132
    >>> os.chdir(old_dir)
 
133
    >>> os.rmdir(new_dir)
 
134
    """
 
135
    cmd = [rsync_name, "-av", "--delete"]
 
136
    if ssh:
 
137
        cmd.extend(('-e', 'ssh'))
 
138
    if len(excludes) > 0:
 
139
        cmd.extend(('--exclude-from', '-'))
 
140
    cmd.extend((source, target))
 
141
    if silent:
 
142
        stderr = PIPE
 
143
        stdout = PIPE
 
144
    else:
 
145
        stderr = None
 
146
        stdout = None
 
147
    try:
 
148
        proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
 
149
    except OSError, e:
 
150
        if e.errno == errno.ENOENT:
 
151
            raise NoRsync(rsync_name)
 
152
            
 
153
    proc.stdin.write('\n'.join(excludes)+'\n')
 
154
    proc.stdin.close()
 
155
    if silent:
 
156
        proc.stderr.read()
 
157
        proc.stderr.close()
 
158
        proc.stdout.read()
 
159
        proc.stdout.close()
 
160
    proc.wait()
 
161
    if proc.returncode == 12:
 
162
        raise RsyncStreamIO()
 
163
    elif proc.returncode == 23:
 
164
        raise RsyncNoFile(source)
 
165
    elif proc.returncode != 0:
 
166
        raise RsyncUnknownStatus(proc.returncode)
 
167
    return cmd
 
168
 
 
169
 
 
170
def rsync_ls(source, ssh=False, silent=True):
 
171
    cmd = ["rsync"]
 
172
    if ssh:
 
173
        cmd.extend(('-e', 'ssh'))
 
174
    cmd.append(source)
 
175
    if silent:
 
176
        stderr = PIPE
 
177
    else:
 
178
        stderr = None
 
179
    proc = Popen(cmd, stderr=stderr, stdout=PIPE)
 
180
    result = proc.stdout.read()
 
181
    proc.stdout.close()
 
182
    if silent:
 
183
        proc.stderr.read()
 
184
        proc.stderr.close()
 
185
    proc.wait()
 
186
    if proc.returncode == 12:
 
187
        raise RsyncStreamIO()
 
188
    elif proc.returncode == 23:
 
189
        raise RsyncNoFile(source)
 
190
    elif proc.returncode != 0:
 
191
        raise RsyncUnknownStatus(proc.returncode)
 
192
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
 
193
 
 
194
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent', 
 
195
              '.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
 
196
              '.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
 
197
              '.bzr/basis-inventory', '.bzr/inventory.backup.weave')
 
198
 
 
199
 
 
200
def read_revision_history(fname):
 
201
    return [l.rstrip('\r\n') for l in
 
202
            codecs.open(fname, 'rb', 'utf-8').readlines()]
 
203
 
 
204
class RsyncNoFile(Exception):
 
205
    def __init__(self, path):
 
206
        Exception.__init__(self, "No such file %s" % path)
 
207
 
 
208
class RsyncStreamIO(Exception):
 
209
    def __init__(self):
 
210
        Exception.__init__(self, "Error in rsync protocol data stream.")
 
211
 
 
212
def get_revision_history(location):
 
213
    tempdir = tempfile.mkdtemp('push')
 
214
    try:
 
215
        history_fname = os.path.join(tempdir, 'revision-history')
 
216
        try:
 
217
            cmd = rsync(location+'.bzr/revision-history', history_fname,
 
218
                        silent=True)
 
219
        except RsyncNoFile:
 
220
            cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
 
221
                        silent=True)
 
222
        history = read_revision_history(history_fname)
 
223
    finally:
 
224
        shutil.rmtree(tempdir)
 
225
    return history
 
226
 
 
227
def history_subset(location, branch):
 
228
    remote_history = get_revision_history(location)
 
229
    local_history = branch.revision_history()
 
230
    if len(remote_history) > len(local_history):
 
231
        return False
 
232
    for local, remote in zip(remote_history, local_history):
 
233
        if local != remote:
 
234
            return False 
 
235
    return True
 
236
 
 
237
def empty_or_absent(location):
 
238
    try:
 
239
        files = rsync_ls(location)
 
240
        return files == ['.']
 
241
    except RsyncNoFile:
 
242
        return True
 
243
 
 
244
def rspush(tree, location=None, overwrite=False, working_tree=True):
 
245
    push_location = get_push_data(tree)
 
246
    if location is not None:
 
247
        if not location.endswith('/'):
 
248
            location += '/'
 
249
        push_location = location
 
250
    
 
251
    if push_location is None:
 
252
        raise BzrCommandError("No rspush location known or specified.")
 
253
 
 
254
    if (push_location.find('://') != -1 or
 
255
        push_location.find(':') == -1):
 
256
        raise BzrCommandError("Invalid rsync path %r." % push_location)
 
257
 
 
258
    if working_tree:
 
259
        clean, non_source = is_clean(tree)
 
260
        if not clean:
 
261
            print """Error: This tree has uncommitted changes or unknown (?) files.
 
262
    Use "bzr status" to list them."""
 
263
            sys.exit(1)
 
264
        final_exclusions = non_source[:]
 
265
    else:
 
266
        wt = tree
 
267
        final_exclusions = []
 
268
        for path, status, kind, file_id, entry in wt.list_files():
 
269
            final_exclusions.append(path)
 
270
 
 
271
    final_exclusions.extend(exclusions)
 
272
    if not overwrite:
 
273
        try:
 
274
            if not history_subset(push_location, tree.branch):
 
275
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
 
276
                                                    " newer version of remote"
 
277
                                                    " branch.")
 
278
        except RsyncNoFile:
 
279
            if not empty_or_absent(push_location):
 
280
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
 
281
                                                    " bzr branch (or empty"
 
282
                                                    " directory)")
 
283
        except RsyncStreamIO:
 
284
            raise bzrlib.errors.BzrCommandError("Rsync could not use the"
 
285
                " specified location.  Please ensure that"
 
286
                ' "%s" is of the form "machine:/path".' % push_location)
 
287
    print "Pushing to %s" % push_location
 
288
    rsync(tree.basedir+'/', push_location, ssh=True, 
 
289
          excludes=final_exclusions)
 
290
 
 
291
    set_push_data(tree, push_location)
27
292
 
28
293
 
29
294
def short_committer(committer):
37
302
    """Screen-scrape Apache listings"""
38
303
    apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
39
304
        ' <a href="'
40
 
    t = t.clone()
41
 
    t._remote_path = lambda x: t.base
42
 
    try:
43
 
        lines = t.get('')
44
 
    except NoSuchFile:
45
 
        return
46
 
    expr = re.compile('<a[^>]*href="([^>]*)\/"[^>]*>', flags=re.I)
 
305
    lines = t.get('.')
 
306
    expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
47
307
    for line in lines:
48
308
        match = expr.search(line)
49
309
        if match is None:
56
316
        yield url.rstrip('/')
57
317
 
58
318
 
59
 
def list_branches(t):
60
 
    def is_inside(branch):
61
 
        return bool(branch.base.startswith(t.base))
62
 
 
63
 
    if t.base.startswith('http://'):
64
 
        def evaluate(bzrdir):
 
319
def iter_branches(t, lister=None):
 
320
    """Iterate through all the branches under a transport"""
 
321
    for bzrdir in iter_bzrdirs(t, lister):
 
322
        try:
 
323
            branch = bzrdir.open_branch()
 
324
            if branch.bzrdir is bzrdir:
 
325
                yield branch
 
326
        except (NotBranchError, UnsupportedFormatError):
 
327
            pass
 
328
 
 
329
 
 
330
def iter_branch_tree(t, lister=None):
 
331
    for bzrdir in iter_bzrdirs(t, lister):
 
332
        try:
 
333
            wt = bzrdir.open_workingtree()
 
334
            yield wt.branch, wt
 
335
        except NoWorkingTree, UnsupportedFormatError:
65
336
            try:
66
337
                branch = bzrdir.open_branch()
67
 
                if is_inside(branch):
68
 
                    return True, branch
69
 
                else:
70
 
                    return True, None
71
 
            except NotBranchError:
72
 
                return True, None
73
 
        return [b for b in BzrDir.find_bzrdirs(t, list_current=apache_ls,
74
 
                evaluate=evaluate) if b is not None]
75
 
    elif not t.listable():
76
 
        raise BzrCommandError("Can't list this type of location.")
77
 
    return [b for b in BzrDir.find_branches(t) if is_inside(b)]
78
 
 
79
 
 
80
 
def evaluate_branch_tree(bzrdir):
81
 
    try:
82
 
        tree, branch = bzrdir._get_tree_branch()
83
 
    except NotBranchError:
84
 
        return True, None
85
 
    else:
86
 
        return True, (branch, tree)
87
 
 
88
 
 
89
 
def iter_branch_tree(t, lister=None):
90
 
    return (x for x in BzrDir.find_bzrdirs(t, evaluate=evaluate_branch_tree,
91
 
            list_current=lister) if x is not None)
92
 
 
93
 
 
94
 
def open_from_url(location):
95
 
    location = urlutils.normalize_url(location)
96
 
    dirname, basename = urlutils.split(location)
97
 
    if location.endswith('/') and not basename.endswith('/'):
98
 
        basename += '/'
99
 
    return get_transport(dirname).get(basename)
 
338
                if branch.bzrdir is bzrdir:
 
339
                    yield branch, None
 
340
            except (NotBranchError, UnsupportedFormatError):
 
341
                continue
 
342
 
 
343
 
 
344
def iter_bzrdirs(t, lister=None):
 
345
    if lister is None:
 
346
        def lister(t):
 
347
            return t.list_dir('.')
 
348
    try:
 
349
        bzrdir = bzrdir_from_transport(t)
 
350
        yield bzrdir
 
351
    except (NotBranchError, UnsupportedFormatError, TransportError,
 
352
            PermissionDenied):
 
353
        pass
 
354
    try:
 
355
        for directory in lister(t):
 
356
            if directory == ".bzr":
 
357
                continue
 
358
            try:
 
359
                subt = t.clone(directory)
 
360
            except UnicodeDecodeError:
 
361
                continue
 
362
            for bzrdir in iter_bzrdirs(subt, lister):
 
363
                yield bzrdir
 
364
    except (NoSuchFile, PermissionDenied, TransportError):
 
365
        pass
 
366
 
 
367
    
 
368
def bzrdir_from_transport(t):
 
369
    """Open a bzrdir from a transport (not a location)"""
 
370
    format = BzrDirFormat.find_format(t)
 
371
    BzrDir._check_supported(format, False)
 
372
    return format.open(t)
100
373
 
101
374
 
102
375
def run_tests():