~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Michael Ellerman
  • Date: 2005-11-29 01:41:52 UTC
  • mto: (0.3.1 shelf-dev) (325.1.2 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 334.
  • Revision ID: michael@ellerman.id.au-20051129014152-f5ede8888bcebc48
HunkSelector was broken if you did a "done" followed by "status/invert" etc.
Fixup to make pychecker happy.

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
19
 
import os
20
 
import re
21
 
import tempfile
22
 
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
29
 
from bzrlib.bzrdir import BzrDir
30
 
 
31
 
def temp_tree():
32
 
    dirname = tempfile.mkdtemp("temp-branch")
33
 
    return BzrDir.create_standalone_workingtree(dirname)
34
 
 
35
 
def rm_tree(tree):
36
 
    shutil.rmtree(tree.basedir)
37
 
 
38
 
def is_clean(cur_tree):
39
 
    """
40
 
    Return true if no files are modifed or unknown
41
 
    >>> import bzrlib.add
42
 
    >>> tree = temp_tree()
43
 
    >>> is_clean(tree)
44
 
    (True, [])
45
 
    >>> fooname = os.path.join(tree.basedir, "foo")
46
 
    >>> file(fooname, "wb").write("bar")
47
 
    >>> is_clean(tree)
48
 
    (True, [u'foo'])
49
 
    >>> bzrlib.add.smart_add_tree(tree, [tree.basedir])
50
 
    ([u'foo'], {})
51
 
    >>> is_clean(tree)
52
 
    (False, [])
53
 
    >>> tree.commit("added file")
54
 
    >>> is_clean(tree)
55
 
    (True, [])
56
 
    >>> rm_tree(tree)
57
 
    """
58
 
    from bzrlib.diff import compare_trees
59
 
    old_tree = cur_tree.basis_tree()
60
 
    new_tree = cur_tree
61
 
    non_source = []
62
 
    for path, file_class, kind, file_id, entry in new_tree.list_files():
63
 
        if file_class in ('?', 'I'):
64
 
            non_source.append(path)
65
 
    delta = compare_trees(old_tree, new_tree, want_unchanged=False)
66
 
    return not delta.has_changed(), non_source
67
 
 
68
 
def set_push_data(tree, location):
69
 
    push_file = file (tree._control_files.controlfilename("x-push-data"), "wb")
70
 
    push_file.write("%s\n" % location)
71
 
 
72
 
def get_push_data(tree):
73
 
    """
74
 
    >>> tree = temp_tree()
75
 
    >>> get_push_data(tree) is None
76
 
    True
77
 
    >>> set_push_data(tree, 'http://somewhere')
78
 
    >>> get_push_data(tree)
79
 
    'http://somewhere'
80
 
    >>> rm_tree(tree)
81
 
    """
82
 
    filename = tree._control_files.controlfilename("x-push-data")
83
 
    if not os.path.exists(filename):
84
 
        return None
85
 
    push_file = file (filename, "rb")
86
 
    (location,) = [f.rstrip('\n') for f in push_file]
87
 
    return location
88
 
 
89
 
"""
90
 
>>> shell_escape('hello')
91
 
'\h\e\l\l\o'
92
 
"""
93
 
def shell_escape(arg):
94
 
    return "".join(['\\'+c for c in arg])
95
 
 
96
 
def safe_system(args):
97
 
    """
98
 
    >>> real_system = os.system
99
 
    >>> os.system = sys.stdout.write
100
 
    >>> safe_system(['a', 'b', 'cd'])
101
 
    \\a \\b \\c\\d
102
 
    >>> os.system = real_system
103
 
    """
104
 
    arg_str = " ".join([shell_escape(a) for a in args])
105
 
    return os.system(arg_str)
106
 
 
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"]
134
 
    if ssh:
135
 
        cmd.extend(('-e', 'ssh'))
136
 
    if len(excludes) > 0:
137
 
        cmd.extend(('--exclude-from', '-'))
138
 
    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/parent', '.bzr/x-pull-data', 
193
 
              '.bzr/x-pull', '.bzr/pull', '.bzr/stat-cache',
194
 
              '.bzr/x-rsync-data', '.bzr/basis-inventory', 
195
 
              '.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
 
        cmd = rsync(location+'.bzr/revision-history', history_fname,
215
 
                    silent=True)
216
 
        history = read_revision_history(history_fname)
217
 
    finally:
218
 
        shutil.rmtree(tempdir)
219
 
    return history
220
 
 
221
 
def history_subset(location, branch):
222
 
    remote_history = get_revision_history(location)
223
 
    local_history = branch.revision_history()
224
 
    if len(remote_history) > len(local_history):
225
 
        return False
226
 
    for local, remote in zip(remote_history, local_history):
227
 
        if local != remote:
228
 
            return False 
229
 
    return True
230
 
 
231
 
def empty_or_absent(location):
232
 
    try:
233
 
        files = rsync_ls(location)
234
 
        return files == ['.']
235
 
    except RsyncNoFile:
236
 
        return True
237
 
 
238
 
def push(tree, location=None, overwrite=False, working_tree=True):
239
 
    push_location = get_push_data(tree)
240
 
    if location is not None:
241
 
        if not location.endswith('/'):
242
 
            location += '/'
243
 
        push_location = location
244
 
    
245
 
    if push_location is None:
246
 
        if tree.branch.get_push_location() is None:
247
 
            raise BzrCommandError("No push location known or specified.")
248
 
        else:
249
 
            raise bzrlib.errors.MustUseDecorated
250
 
 
251
 
    if push_location.find('://') != -1:
252
 
        raise bzrlib.errors.MustUseDecorated
253
 
 
254
 
    if push_location.find(':') == -1:
255
 
        raise bzrlib.errors.MustUseDecorated
256
 
 
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
 
    if working_tree:
263
 
        final_exclusions = non_source[:]
264
 
    else:
265
 
        wt = tree
266
 
        final_exclusions = []
267
 
        for path, status, kind, file_id, entry in wt.list_files():
268
 
            final_exclusions.append(path)
269
 
 
270
 
    final_exclusions.extend(exclusions)
271
 
    if not overwrite:
272
 
        try:
273
 
            if not history_subset(push_location, tree.branch):
274
 
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
275
 
                                                    " newer version of remote"
276
 
                                                    " branch.")
277
 
        except RsyncNoFile:
278
 
            if not empty_or_absent(push_location):
279
 
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
280
 
                                                    " bzr branch (or empty"
281
 
                                                    " directory)")
282
 
        except RsyncStreamIO:
283
 
            raise bzrlib.errors.BzrCommandError("Rsync could not use the"
284
 
                " specified location.  Please ensure that"
285
 
                ' "%s" is of the form "machine:/path".' % push_location)
286
 
    print "Pushing to %s" % push_location
287
 
    rsync(tree.basedir+'/', push_location, ssh=True, 
288
 
          excludes=final_exclusions)
289
 
 
290
 
    set_push_data(tree, push_location)
291
 
 
292
 
 
293
 
def short_committer(committer):
294
 
    new_committer = re.sub('<.*>', '', committer).strip(' ')
295
 
    if len(new_committer) < 2:
296
 
        return committer
297
 
    return new_committer
298
 
 
299
 
 
300
 
def run_tests():
301
 
    import doctest
302
 
    result = doctest.testmod()
303
 
    if result[1] > 0:
304
 
        if result[0] == 0:
305
 
            print "All tests passed"
306
 
    else:
307
 
        print "No tests to run"
308
 
if __name__ == "__main__":
309
 
    run_tests()