~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
19
 
26
 
import bzrlib
27
 
import bzrlib.errors
 
20
from bzrlib import urlutils
 
21
from bzrlib.errors import (
 
22
    BzrCommandError,
 
23
    NotBranchError,
 
24
    NoSuchFile,
 
25
    )
28
26
from bzrlib.bzrdir import BzrDir
29
 
 
30
 
def temp_tree():
31
 
    dirname = tempfile.mkdtemp("temp-branch")
32
 
    return BzrDir.create_standalone_workingtree(dirname)
33
 
 
34
 
def rm_tree(tree):
35
 
    shutil.rmtree(tree.basedir)
36
 
 
37
 
def is_clean(cur_tree):
38
 
    """
39
 
    Return true if no files are modifed or unknown
40
 
    >>> import bzrlib.add
41
 
    >>> tree = temp_tree()
42
 
    >>> is_clean(tree)
43
 
    (True, [])
44
 
    >>> fooname = os.path.join(tree.basedir, "foo")
45
 
    >>> file(fooname, "wb").write("bar")
46
 
    >>> is_clean(tree)
47
 
    (True, [u'foo'])
48
 
    >>> bzrlib.add.smart_add_tree(tree, [tree.basedir])
49
 
    ([u'foo'], {})
50
 
    >>> is_clean(tree)
51
 
    (False, [])
52
 
    >>> tree.commit("added file")
53
 
    >>> is_clean(tree)
54
 
    (True, [])
55
 
    >>> rm_tree(tree)
56
 
    """
57
 
    from bzrlib.diff import compare_trees
58
 
    old_tree = cur_tree.basis_tree()
59
 
    new_tree = cur_tree
60
 
    non_source = []
61
 
    for path, file_class, kind, file_id, entry in new_tree.list_files():
62
 
        if file_class in ('?', 'I'):
63
 
            non_source.append(path)
64
 
    delta = compare_trees(old_tree, new_tree, want_unchanged=False)
65
 
    return not delta.has_changed(), non_source
66
 
 
67
 
def set_push_data(tree, location):
68
 
    push_file = file (tree._control_files.controlfilename("x-push-data"), "wb")
69
 
    push_file.write("%s\n" % location)
70
 
 
71
 
def get_push_data(tree):
72
 
    """
73
 
    >>> tree = temp_tree()
74
 
    >>> get_push_data(tree) is None
75
 
    True
76
 
    >>> set_push_data(tree, 'http://somewhere')
77
 
    >>> get_push_data(tree)
78
 
    'http://somewhere'
79
 
    >>> rm_tree(tree)
80
 
    """
81
 
    filename = tree._control_files.controlfilename("x-push-data")
82
 
    if not os.path.exists(filename):
83
 
        return None
84
 
    push_file = file (filename, "rb")
85
 
    (location,) = [f.rstrip('\n') for f in push_file]
86
 
    return location
87
 
 
88
 
"""
89
 
>>> shell_escape('hello')
90
 
'\h\e\l\l\o'
91
 
"""
92
 
def shell_escape(arg):
93
 
    return "".join(['\\'+c for c in arg])
94
 
 
95
 
def safe_system(args):
96
 
    """
97
 
    >>> real_system = os.system
98
 
    >>> os.system = sys.stdout.write
99
 
    >>> safe_system(['a', 'b', 'cd'])
100
 
    \\a \\b \\c\\d
101
 
    >>> os.system = real_system
102
 
    """
103
 
    arg_str = " ".join([shell_escape(a) for a in args])
104
 
    return os.system(arg_str)
105
 
 
106
 
class RsyncUnknownStatus(Exception):
107
 
    def __init__(self, status):
108
 
        Exception.__init__(self, "Unknown status: %d" % status)
109
 
 
110
 
class NoRsync(Exception):
111
 
    def __init__(self, rsync_name):
112
 
        Exception.__init__(self, "%s not found." % rsync_name)
113
 
 
114
 
def rsync(source, target, ssh=False, excludes=(), silent=False, 
115
 
          rsync_name="rsync"):
116
 
    """
117
 
    >>> new_dir = tempfile.mkdtemp()
118
 
    >>> old_dir = os.getcwd()
119
 
    >>> os.chdir(new_dir)
120
 
    >>> rsync("a", "b", silent=True)
121
 
    Traceback (most recent call last):
122
 
    RsyncNoFile: No such file a
123
 
    >>> rsync("a", "b", excludes=("*.py",), silent=True)
124
 
    Traceback (most recent call last):
125
 
    RsyncNoFile: No such file a
126
 
    >>> rsync("a", "b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
127
 
    Traceback (most recent call last):
128
 
    NoRsync: rsyncc not found.
129
 
    >>> os.chdir(old_dir)
130
 
    >>> os.rmdir(new_dir)
131
 
    """
132
 
    cmd = [rsync_name, "-av", "--delete"]
133
 
    if ssh:
134
 
        cmd.extend(('-e', 'ssh'))
135
 
    if len(excludes) > 0:
136
 
        cmd.extend(('--exclude-from', '-'))
137
 
    cmd.extend((source, target))
138
 
    if silent:
139
 
        stderr = PIPE
140
 
        stdout = PIPE
141
 
    else:
142
 
        stderr = None
143
 
        stdout = None
144
 
    try:
145
 
        proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
146
 
    except OSError, e:
147
 
        if e.errno == errno.ENOENT:
148
 
            raise NoRsync(rsync_name)
149
 
            
150
 
    proc.stdin.write('\n'.join(excludes)+'\n')
151
 
    proc.stdin.close()
152
 
    if silent:
153
 
        proc.stderr.read()
154
 
        proc.stderr.close()
155
 
        proc.stdout.read()
156
 
        proc.stdout.close()
157
 
    proc.wait()
158
 
    if proc.returncode == 12:
159
 
        raise RsyncStreamIO()
160
 
    elif proc.returncode == 23:
161
 
        raise RsyncNoFile(source)
162
 
    elif proc.returncode != 0:
163
 
        raise RsyncUnknownStatus(proc.returncode)
164
 
    return cmd
165
 
 
166
 
 
167
 
def rsync_ls(source, ssh=False, silent=True):
168
 
    cmd = ["rsync"]
169
 
    if ssh:
170
 
        cmd.extend(('-e', 'ssh'))
171
 
    cmd.append(source)
172
 
    if silent:
173
 
        stderr = PIPE
174
 
    else:
175
 
        stderr = None
176
 
    proc = Popen(cmd, stderr=stderr, stdout=PIPE)
177
 
    result = proc.stdout.read()
178
 
    proc.stdout.close()
179
 
    if silent:
180
 
        proc.stderr.read()
181
 
        proc.stderr.close()
182
 
    proc.wait()
183
 
    if proc.returncode == 12:
184
 
        raise RsyncStreamIO()
185
 
    elif proc.returncode == 23:
186
 
        raise RsyncNoFile(source)
187
 
    elif proc.returncode != 0:
188
 
        raise RsyncUnknownStatus(proc.returncode)
189
 
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
190
 
 
191
 
exclusions = ('.bzr/x-push-data', '.bzr/parent', '.bzr/x-pull-data', 
192
 
              '.bzr/x-pull', '.bzr/pull', '.bzr/stat-cache',
193
 
              '.bzr/x-rsync-data')
194
 
 
195
 
 
196
 
def read_revision_history(fname):
197
 
    return [l.rstrip('\r\n') for l in
198
 
            codecs.open(fname, 'rb', 'utf-8').readlines()]
199
 
 
200
 
class RsyncNoFile(Exception):
201
 
    def __init__(self, path):
202
 
        Exception.__init__(self, "No such file %s" % path)
203
 
 
204
 
class RsyncStreamIO(Exception):
205
 
    def __init__(self):
206
 
        Exception.__init__(self, "Error in rsync protocol data stream.")
207
 
 
208
 
def get_revision_history(location):
209
 
    tempdir = tempfile.mkdtemp('push')
210
 
    try:
211
 
        history_fname = os.path.join(tempdir, 'revision-history')
212
 
        cmd = rsync(location+'.bzr/revision-history', history_fname,
213
 
                    silent=True)
214
 
        history = read_revision_history(history_fname)
 
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
215
36
    finally:
216
 
        shutil.rmtree(tempdir)
217
 
    return history
218
 
 
219
 
def history_subset(location, branch):
220
 
    remote_history = get_revision_history(location)
221
 
    local_history = branch.revision_history()
222
 
    if len(remote_history) > len(local_history):
223
 
        return False
224
 
    for local, remote in zip(remote_history, local_history):
225
 
        if local != remote:
226
 
            return False 
227
 
    return True
228
 
 
229
 
def empty_or_absent(location):
230
 
    try:
231
 
        files = rsync_ls(location)
232
 
        return files == ['.']
233
 
    except RsyncNoFile:
234
 
        return True
235
 
 
236
 
def push(tree, location=None, overwrite=False, working_tree=True):
237
 
    push_location = get_push_data(tree)
238
 
    if location is not None:
239
 
        if not location.endswith('/'):
240
 
            location += '/'
241
 
        push_location = location
242
 
    
243
 
    if push_location is None:
244
 
        raise bzrlib.errors.MustUseDecorated
245
 
 
246
 
    if push_location.find('://') != -1:
247
 
        raise bzrlib.errors.MustUseDecorated
248
 
 
249
 
    if push_location.find(':') == -1:
250
 
        raise bzrlib.errors.MustUseDecorated
251
 
 
252
 
    clean, non_source = is_clean(tree)
253
 
    if not clean:
254
 
        print """Error: This tree has uncommitted changes or unknown (?) files.
255
 
Use "bzr status" to list them."""
256
 
        sys.exit(1)
257
 
    if working_tree:
258
 
        final_exclusions = non_source[:]
259
 
    else:
260
 
        wt = tree
261
 
        final_exclusions = []
262
 
        for path, status, kind, file_id, entry in wt.list_files():
263
 
            final_exclusions.append(path)
264
 
 
265
 
    final_exclusions.extend(exclusions)
266
 
    if not overwrite:
267
 
        try:
268
 
            if not history_subset(push_location, tree.branch):
269
 
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
270
 
                                                    " newer version of remote"
271
 
                                                    " branch.")
272
 
        except RsyncNoFile:
273
 
            if not empty_or_absent(push_location):
274
 
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
275
 
                                                    " bzr branch (or empty"
276
 
                                                    " directory)")
277
 
        except RsyncStreamIO:
278
 
            raise bzrlib.errors.BzrCommandError("Rsync could not use the"
279
 
                " specified location.  Please ensure that"
280
 
                ' "%s" is of the form "machine:/path".' % push_location)
281
 
    print "Pushing to %s" % push_location
282
 
    rsync(tree.basedir+'/', push_location, ssh=True, 
283
 
          excludes=final_exclusions)
284
 
 
285
 
    set_push_data(tree, push_location)
 
37
        lockable.unlock()
 
38
 
286
39
 
287
40
def short_committer(committer):
288
41
    new_committer = re.sub('<.*>', '', committer).strip(' ')
291
44
    return new_committer
292
45
 
293
46
 
 
47
def apache_ls(t):
 
48
    """Screen-scrape Apache listings"""
 
49
    apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
 
50
        ' <a href="'
 
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)
 
58
    for line in lines:
 
59
        match = expr.search(line)
 
60
        if match is None:
 
61
            continue
 
62
        url = match.group(1)
 
63
        if url.startswith('http://') or url.startswith('/') or '../' in url:
 
64
            continue
 
65
        if '?' in url:
 
66
            continue
 
67
        yield url.rstrip('/')
 
68
 
 
69
 
 
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)
 
98
 
 
99
 
 
100
def iter_branch_tree(t, lister=None):
 
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)
 
111
 
 
112
 
294
113
def run_tests():
295
114
    import doctest
296
115
    result = doctest.testmod()