~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Aaron Bentley
  • Date: 2013-08-20 03:02:43 UTC
  • Revision ID: aaron@aaronbentley.com-20130820030243-r8v1xfbcnd8f10p4
Fix zap command for 2.6/7

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-2009, 2011-2013 Aaron Bentley <aaron@aaronbentley.com>
 
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
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
from contextlib import contextmanager
20
18
import re
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)
 
19
 
 
20
from bzrlib import urlutils
 
21
from bzrlib.errors import (
 
22
    BzrCommandError,
 
23
    NotBranchError,
 
24
    NoSuchFile,
 
25
    )
 
26
from bzrlib.bzrdir import BzrDir
 
27
from bzrlib.transport import get_transport
 
28
 
 
29
 
 
30
@contextmanager
 
31
def read_locked(lockable):
 
32
    """Read-lock a tree, branch or repository in this context."""
 
33
    lockable.lock_read()
 
34
    try:
 
35
        yield lockable
223
36
    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)
 
37
        lockable.unlock()
292
38
 
293
39
 
294
40
def short_committer(committer):
302
48
    """Screen-scrape Apache listings"""
303
49
    apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
304
50
        ' <a href="'
305
 
    lines = t.get('.')
306
 
    expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
 
51
    t = t.clone()
 
52
    t._remote_path = lambda x: t.base
 
53
    try:
 
54
        lines = t.get('')
 
55
    except NoSuchFile:
 
56
        return
 
57
    expr = re.compile('<a[^>]*href="([^>]*)\/"[^>]*>', flags=re.I)
307
58
    for line in lines:
308
59
        match = expr.search(line)
309
60
        if match is None:
316
67
        yield url.rstrip('/')
317
68
 
318
69
 
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
 
70
def list_branches(t):
 
71
    def is_inside(branch):
 
72
        return bool(branch.base.startswith(t.base))
 
73
 
 
74
    if t.base.startswith('http://'):
 
75
        def evaluate(bzrdir):
 
76
            try:
 
77
                branch = bzrdir.open_branch()
 
78
                if is_inside(branch):
 
79
                    return True, branch
 
80
                else:
 
81
                    return True, None
 
82
            except NotBranchError:
 
83
                return True, None
 
84
        return [b for b in BzrDir.find_bzrdirs(t, list_current=apache_ls,
 
85
                evaluate=evaluate) if b is not None]
 
86
    elif not t.listable():
 
87
        raise BzrCommandError("Can't list this type of location.")
 
88
    return [b for b in BzrDir.find_branches(t) if is_inside(b)]
 
89
 
 
90
 
 
91
def evaluate_branch_tree(bzrdir):
 
92
    try:
 
93
        tree, branch = bzrdir._get_tree_branch()
 
94
    except NotBranchError:
 
95
        return True, None
 
96
    else:
 
97
        return True, (branch, tree)
328
98
 
329
99
 
330
100
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:
336
 
            try:
337
 
                branch = bzrdir.open_branch()
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)
 
101
    return (x for x in BzrDir.find_bzrdirs(t, evaluate=evaluate_branch_tree,
 
102
            list_current=lister) if x is not None)
 
103
 
 
104
 
 
105
def open_from_url(location):
 
106
    location = urlutils.normalize_url(location)
 
107
    dirname, basename = urlutils.split(location)
 
108
    if location.endswith('/') and not basename.endswith('/'):
 
109
        basename += '/'
 
110
    return get_transport(dirname).get(basename)
373
111
 
374
112
 
375
113
def run_tests():