~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Robert Collins
  • Date: 2005-09-28 05:43:19 UTC
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050928054319-2c2e9e3048bbc215
find_branch -> open_containing change

Show diffs side-by-side

added added

removed removed

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