~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, 2006, 2007 Aaron Bentley <aaron@aaronbentley.com>
 
1
# Copyright (C) 2005-2009, 2011-2013 Aaron Bentley <aaron@aaronbentley.com>
2
2
# Copyright (C) 2007 John Arbash Meinel
3
3
#
4
4
#    This program is free software; you can redistribute it and/or modify
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
19
 
26
 
import bzrlib
27
 
from bzrlib import revision as _mod_revision, trace, urlutils
28
 
import bzrlib.errors
 
20
from bzrlib import urlutils
29
21
from bzrlib.errors import (
30
22
    BzrCommandError,
31
 
    BzrError,
32
 
    ConnectionError,
33
23
    NotBranchError,
34
24
    NoSuchFile,
35
 
    NoWorkingTree,
36
 
    PermissionDenied,
37
 
    UnsupportedFormatError,
38
 
    TransportError,
39
25
    )
40
 
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
26
from bzrlib.bzrdir import BzrDir
41
27
from bzrlib.transport import get_transport
42
28
 
43
 
def temp_tree():
44
 
    dirname = tempfile.mkdtemp("temp-branch")
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):
51
 
    """
52
 
    Return true if no files are modifed or unknown
53
 
    """
54
 
    old_tree = cur_tree.basis_tree()
55
 
    new_tree = cur_tree
56
 
    non_source = []
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()
65
 
    return not delta.has_changed(), non_source
66
 
 
67
 
def set_push_data(tree, location):
68
 
    tree.branch._transport.put_bytes("x-push-data", "%s\n" % location)
69
 
 
70
 
def get_push_data(tree):
71
 
    """
72
 
    >>> tree = temp_tree()
73
 
    >>> get_push_data(tree) is None
74
 
    True
75
 
    >>> set_push_data(tree, 'http://somewhere')
76
 
    >>> get_push_data(tree)
77
 
    u'http://somewhere'
78
 
    >>> rm_tree(tree)
79
 
    """
80
 
    try:
81
 
        location = tree.branch._transport.get('x-push-data').read()
82
 
    except NoSuchFile:
83
 
        return None
84
 
    location = location.decode('utf-8')
85
 
    return location.rstrip('\n')
86
 
 
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
 
 
105
 
class RsyncUnknownStatus(Exception):
106
 
    def __init__(self, status):
107
 
        Exception.__init__(self, "Unknown status: %d" % status)
108
 
 
109
 
class NoRsync(Exception):
110
 
    def __init__(self, rsync_name):
111
 
        Exception.__init__(self, "%s not found." % rsync_name)
112
 
 
113
 
 
114
 
def rsync(source, target, ssh=False, excludes=(), silent=False,
115
 
          rsync_name="rsync"):
116
 
    cmd = [rsync_name, "-av", "--delete"]
117
 
    if ssh:
118
 
        cmd.extend(('-e', 'ssh'))
119
 
    if len(excludes) > 0:
120
 
        cmd.extend(('--exclude-from', '-'))
121
 
    cmd.extend((source, target))
122
 
    if silent:
123
 
        stderr = PIPE
124
 
        stdout = PIPE
125
 
    else:
126
 
        stderr = None
127
 
        stdout = None
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)
133
 
 
134
 
    proc.stdin.write('\n'.join(excludes)+'\n')
135
 
    proc.stdin.close()
136
 
    if silent:
137
 
        proc.stderr.read()
138
 
        proc.stderr.close()
139
 
        proc.stdout.read()
140
 
        proc.stdout.close()
141
 
    proc.wait()
142
 
    if proc.returncode == 12:
143
 
        raise RsyncStreamIO()
144
 
    elif proc.returncode == 23:
145
 
        raise RsyncNoFile(source)
146
 
    elif proc.returncode != 0:
147
 
        raise RsyncUnknownStatus(proc.returncode)
148
 
    return cmd
149
 
 
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()
167
 
    if proc.returncode == 12:
168
 
        raise RsyncStreamIO()
169
 
    elif proc.returncode == 23:
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
 
 
175
 
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
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')
179
 
 
180
 
 
181
 
def read_revision_history(fname):
182
 
    return [l.rstrip('\r\n') for l in
183
 
            codecs.open(fname, 'rb', 'utf-8').readlines()]
184
 
 
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
 
 
194
 
class RsyncNoFile(Exception):
195
 
    def __init__(self, path):
196
 
        Exception.__init__(self, "No such file %s" % path)
197
 
 
198
 
class RsyncStreamIO(Exception):
199
 
    def __init__(self):
200
 
        Exception.__init__(self, "Error in rsync protocol data stream.")
201
 
 
202
 
 
203
 
class NotStandalone(BzrError):
204
 
 
205
 
    _fmt = '%(location)s is not a standalone tree.'
206
 
    _internal = False
207
 
 
208
 
    def __init__(self, location):
209
 
        BzrError.__init__(self, location=location)
210
 
 
211
 
 
212
 
def get_revision_history(location, _rsync):
213
 
    tempdir = tempfile.mkdtemp('push')
214
 
    my_rsync = _rsync
215
 
    if my_rsync is None:
216
 
        my_rsync = rsync
217
 
    try:
218
 
        history_fname = os.path.join(tempdir, 'revision-history')
219
 
        try:
220
 
            cmd = my_rsync(location+'.bzr/revision-history', history_fname,
221
 
                        silent=True)
222
 
        except RsyncNoFile:
223
 
            cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
224
 
                        silent=True)
225
 
        history = read_revision_history(history_fname)
226
 
    finally:
227
 
        shutil.rmtree(tempdir)
228
 
    return history
229
 
 
230
 
 
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
 
 
246
 
def history_subset(location, branch, _rsync=None):
247
 
    local_history = branch.revision_history()
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):
257
 
            return False
258
 
        for local, remote in zip(remote_history, local_history):
259
 
            if local != remote:
260
 
                return False
261
 
        return True
262
 
 
263
 
 
264
 
def empty_or_absent(location):
265
 
    try:
266
 
        files = rsync_ls(location)
267
 
        return files == ['.']
268
 
    except RsyncNoFile:
269
 
        return True
270
 
 
271
 
def rspush(tree, location=None, overwrite=False, working_tree=True,
272
 
    _rsync=None):
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:
306
 
                raise bzrlib.errors.BzrCommandError(
307
 
                    'This tree has uncommitted changes or unknown'
308
 
                    ' (?) files.  Use "bzr status" to list them.')
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)
334
 
        trace.note("Pushing to %s", push_location)
335
 
        my_rsync(tree.basedir+'/', push_location, ssh=usessh,
336
 
                 excludes=final_exclusions)
337
 
 
338
 
        set_push_data(tree, push_location)
339
 
    finally:
340
 
        tree.unlock()
 
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
 
36
    finally:
 
37
        lockable.unlock()
341
38
 
342
39
 
343
40
def short_committer(committer):
355
52
    t._remote_path = lambda x: t.base
356
53
    try:
357
54
        lines = t.get('')
358
 
    except bzrlib.errors.NoSuchFile:
 
55
    except NoSuchFile:
359
56
        return
360
57
    expr = re.compile('<a[^>]*href="([^>]*)\/"[^>]*>', flags=re.I)
361
58
    for line in lines: