~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to scriptlib.py

  • Committer: abentley
  • Date: 2005-05-03 01:57:46 UTC
  • Revision ID: abentley@lappy-20050503015746-5204915e56808a57
Added README

Show diffs side-by-side

added added

removed removed

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