~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Eirik Nygaard
  • Date: 2005-09-27 11:21:32 UTC
  • mto: This revision was merged to the branch mainline in revision 201.
  • Revision ID: eirikald@pvv.ntnu.no-20050927112132-8e462198091bc0d9
Add check for rsync return code 12, error in rsync protocol data stream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
18
import bzrlib.errors
19
19
import os
20
 
import re
 
20
import os.path
 
21
import sys
21
22
import tempfile
22
23
import shutil
 
24
import errno
23
25
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():
 
26
import codecs
 
27
 
 
28
def temp_branch():
34
29
    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):
 
30
    return bzrlib.branch.Branch.initialize(dirname)
 
31
 
 
32
def rm_branch(br):
 
33
    shutil.rmtree(br.base)
 
34
 
 
35
def is_clean(cur_branch):
41
36
    """
42
37
    Return true if no files are modifed or unknown
43
38
    >>> import bzrlib.add
44
 
    >>> tree = temp_tree()
45
 
    >>> is_clean(tree)
 
39
    >>> br = temp_branch()
 
40
    >>> is_clean(br)
46
41
    (True, [])
47
 
    >>> fooname = os.path.join(tree.basedir, "foo")
 
42
    >>> fooname = os.path.join(br.base, "foo")
48
43
    >>> 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)
 
44
    >>> is_clean(br)
 
45
    (True, ['foo'])
 
46
    >>> bzrlib.add.smart_add_branch(br, [br.base])
 
47
    1
 
48
    >>> is_clean(br)
54
49
    (False, [])
55
 
    >>> tree.commit("added file", rev_id='commit-id')
56
 
    'commit-id'
57
 
    >>> is_clean(tree)
 
50
    >>> br.commit("added file")
 
51
    added foo
 
52
    >>> is_clean(br)
58
53
    (True, [])
59
 
    >>> rm_tree(tree)
 
54
    >>> rm_branch(br)
60
55
    """
61
 
    old_tree = cur_tree.basis_tree()
62
 
    new_tree = cur_tree
 
56
    from bzrlib.diff import compare_trees
 
57
    old_tree = cur_branch.basis_tree()
 
58
    new_tree = cur_branch.working_tree()
63
59
    non_source = []
64
 
    for path, file_class, kind, file_id, entry in new_tree.list_files():
 
60
    for path, file_class, kind, file_id in new_tree.list_files():
65
61
        if file_class in ('?', 'I'):
66
62
            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
 
63
    delta = compare_trees(old_tree, new_tree, want_unchanged=False)
 
64
    if len(delta.added) > 0 or len(delta.removed) > 0 or \
 
65
        len(delta.modified) > 0:
 
66
        return False, non_source
 
67
    return True, non_source 
 
68
 
 
69
def set_pull_data(br, location, rev_id):
 
70
    pull_file = file (br.controlfilename("x-pull-data"), "wb")
 
71
    pull_file.write("%s\n%s\n" % (location, rev_id))
 
72
 
 
73
def get_pull_data(br):
 
74
    """
 
75
    >>> br = temp_branch()
 
76
    >>> get_pull_data(br)
 
77
    (None, None)
 
78
    >>> set_pull_data(br, 'http://somewhere', '888-777')
 
79
    >>> get_pull_data(br)
 
80
    ('http://somewhere', '888-777')
 
81
    >>> rm_branch(br)
 
82
    """
 
83
    filename = br.controlfilename("x-pull-data")
 
84
    if not os.path.exists(filename):
 
85
        return (None, None)
 
86
    pull_file = file (filename, "rb")
 
87
    location, rev_id = [f.rstrip('\n') for f in pull_file]
 
88
    return location, rev_id
 
89
 
 
90
def set_push_data(br, location):
 
91
    push_file = file (br.controlfilename("x-push-data"), "wb")
 
92
    push_file.write("%s\n" % location)
 
93
 
 
94
def get_push_data(br):
 
95
    """
 
96
    >>> br = temp_branch()
 
97
    >>> get_push_data(br) is None
77
98
    True
78
 
    >>> set_push_data(tree, 'http://somewhere')
79
 
    >>> get_push_data(tree)
80
 
    u'http://somewhere'
81
 
    >>> rm_tree(tree)
 
99
    >>> set_push_data(br, 'http://somewhere')
 
100
    >>> get_push_data(br)
 
101
    'http://somewhere'
 
102
    >>> rm_branch(br)
82
103
    """
83
 
    try:
84
 
        location = tree.branch.control_files.get_utf8('x-push-data').read()
85
 
    except NoSuchFile:
 
104
    filename = br.controlfilename("x-push-data")
 
105
    if not os.path.exists(filename):
86
106
        return None
87
 
    return location.rstrip('\n')
 
107
    push_file = file (filename, "rb")
 
108
    (location,) = [f.rstrip('\n') for f in push_file]
 
109
    return location
88
110
 
89
111
"""
90
112
>>> shell_escape('hello')
115
137
def rsync(source, target, ssh=False, excludes=(), silent=False, 
116
138
          rsync_name="rsync"):
117
139
    """
118
 
    >>> new_dir = tempfile.mkdtemp()
119
 
    >>> old_dir = os.getcwd()
120
 
    >>> os.chdir(new_dir)
121
140
    >>> rsync("a", "b", silent=True)
122
141
    Traceback (most recent call last):
123
 
    RsyncNoFile: No such file...
124
 
    >>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
 
142
    RsyncNoFile: No such file a
 
143
    >>> rsync("a", "b", excludes=("*.py",), silent=True)
125
144
    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")
 
145
    RsyncNoFile: No such file a
 
146
    >>> rsync("a", "b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
128
147
    Traceback (most recent call last):
129
148
    NoRsync: rsyncc not found.
130
 
    >>> os.chdir(old_dir)
131
 
    >>> os.rmdir(new_dir)
132
149
    """
133
150
    cmd = [rsync_name, "-av", "--delete"]
134
151
    if ssh:
157
174
        proc.stdout.close()
158
175
    proc.wait()
159
176
    if proc.returncode == 12:
160
 
        raise RsyncStreamIO()
 
177
        raise RsyncStreamIO(target)
161
178
    elif proc.returncode == 23:
162
179
        raise RsyncNoFile(source)
163
180
    elif proc.returncode != 0:
182
199
        proc.stderr.close()
183
200
    proc.wait()
184
201
    if proc.returncode == 12:
185
 
        raise RsyncStreamIO()
 
202
         raise RsyncStreamIO(target)
186
203
    elif proc.returncode == 23:
187
204
        raise RsyncNoFile(source)
188
205
    elif proc.returncode != 0:
189
206
        raise RsyncUnknownStatus(proc.returncode)
190
207
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
191
208
 
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')
 
209
exclusions = ('.bzr/x-push-data', '.bzr/parent', '.bzr/x-pull-data', 
 
210
              '.bzr/x-pull', '.bzr/pull', '.bzr/stat-cache',
 
211
              '.bzr/x-rsync-data')
196
212
 
197
213
 
198
214
def read_revision_history(fname):
202
218
class RsyncNoFile(Exception):
203
219
    def __init__(self, path):
204
220
        Exception.__init__(self, "No such file %s" % path)
205
 
 
206
221
class RsyncStreamIO(Exception):
207
 
    def __init__(self):
208
 
        Exception.__init__(self, "Error in rsync protocol data stream.")
 
222
    def __init__(self, url):
 
223
        Exception.__init__(self, "Error in rsync protocol data stream %s\nTarget should be: machine:/path/to/repo" % url)
209
224
 
210
225
def get_revision_history(location):
211
226
    tempdir = tempfile.mkdtemp('push')
212
227
    try:
213
228
        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)
 
229
        cmd = rsync(location+'.bzr/revision-history', history_fname,
 
230
                    silent=True)
220
231
        history = read_revision_history(history_fname)
221
232
    finally:
222
233
        shutil.rmtree(tempdir)
239
250
    except RsyncNoFile:
240
251
        return True
241
252
 
242
 
def rspush(tree, location=None, overwrite=False, working_tree=True):
243
 
    push_location = get_push_data(tree)
 
253
def push(cur_branch, location=None, overwrite=False):
 
254
    push_location = get_push_data(cur_branch)
244
255
    if location is not None:
245
256
        if not location.endswith('/'):
246
257
            location += '/'
247
258
        push_location = location
248
259
    
249
260
    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)
 
261
        print "No push location saved.  Please specify one on the command line."
 
262
        sys.exit(1)
 
263
 
 
264
    clean, non_source = is_clean(cur_branch)
 
265
    if not clean:
 
266
        print """Error: This tree has uncommitted changes or unknown (?) files.
 
267
Use "bzr status" to list them."""
 
268
        sys.exit(1)
 
269
    non_source.extend(exclusions)
270
270
    if not overwrite:
271
271
        try:
272
 
            if not history_subset(push_location, tree.branch):
 
272
            if not history_subset(push_location, cur_branch):
273
273
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
274
274
                                                    " newer version of remote"
275
275
                                                    " branch.")
278
278
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
279
279
                                                    " bzr branch (or empty"
280
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)
285
281
    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
 
 
 
282
    rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
 
283
 
 
284
    set_push_data(cur_branch, push_location)
372
285
 
373
286
def run_tests():
374
287
    import doctest