~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Michael Ellerman
  • Date: 2005-10-25 14:00:28 UTC
  • mto: (0.3.1 shelf-dev) (325.1.2 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 246.
  • Revision ID: michael@ellerman.id.au-20051025140028-78a0a8a4cf86cf97
Remove x bit from diffstat.py

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 bzrlib
18
 
import bzrlib.errors
19
 
import os
20
 
import os.path
21
 
import sys
22
 
import tempfile
23
 
import shutil
24
 
import errno
25
 
from subprocess import Popen, PIPE
26
 
import codecs
27
 
 
28
 
def temp_branch():
29
 
    dirname = tempfile.mkdtemp("temp-branch")
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):
36
 
    """
37
 
    Return true if no files are modifed or unknown
38
 
    >>> import bzrlib.add
39
 
    >>> br = temp_branch()
40
 
    >>> is_clean(br)
41
 
    (True, [])
42
 
    >>> fooname = os.path.join(br.base, "foo")
43
 
    >>> file(fooname, "wb").write("bar")
44
 
    >>> is_clean(br)
45
 
    (True, [u'foo'])
46
 
    >>> br.working_tree().add(["foo"])
47
 
    >>> is_clean(br)
48
 
    (False, [])
49
 
    >>> br.working_tree().commit("added file")
50
 
    >>> is_clean(br)
51
 
    (True, [])
52
 
    >>> rm_branch(br)
53
 
    """
54
 
    from bzrlib.diff import compare_trees
55
 
    old_tree = cur_branch.basis_tree()
56
 
    new_tree = cur_branch.working_tree()
57
 
    non_source = []
58
 
    for path, file_class, kind, file_id, entry in new_tree.list_files():
59
 
        if file_class in ('?', 'I'):
60
 
            non_source.append(path)
61
 
    delta = compare_trees(old_tree, new_tree, want_unchanged=False)
62
 
    return not delta.has_changed(), non_source
63
 
 
64
 
def set_pull_data(br, location, rev_id):
65
 
    pull_file = file (br.controlfilename("x-pull-data"), "wb")
66
 
    pull_file.write("%s\n%s\n" % (location, rev_id))
67
 
 
68
 
def get_pull_data(br):
69
 
    """
70
 
    >>> br = temp_branch()
71
 
    >>> get_pull_data(br)
72
 
    (None, None)
73
 
    >>> set_pull_data(br, 'http://somewhere', '888-777')
74
 
    >>> get_pull_data(br)
75
 
    ('http://somewhere', '888-777')
76
 
    >>> rm_branch(br)
77
 
    """
78
 
    filename = br.controlfilename("x-pull-data")
79
 
    if not os.path.exists(filename):
80
 
        return (None, None)
81
 
    pull_file = file (filename, "rb")
82
 
    location, rev_id = [f.rstrip('\n') for f in pull_file]
83
 
    return location, rev_id
84
 
 
85
 
def set_push_data(br, location):
86
 
    push_file = file (br.controlfilename("x-push-data"), "wb")
87
 
    push_file.write("%s\n" % location)
88
 
 
89
 
def get_push_data(br):
90
 
    """
91
 
    >>> br = temp_branch()
92
 
    >>> get_push_data(br) is None
93
 
    True
94
 
    >>> set_push_data(br, 'http://somewhere')
95
 
    >>> get_push_data(br)
96
 
    'http://somewhere'
97
 
    >>> rm_branch(br)
98
 
    """
99
 
    filename = br.controlfilename("x-push-data")
100
 
    if not os.path.exists(filename):
101
 
        return None
102
 
    push_file = file (filename, "rb")
103
 
    (location,) = [f.rstrip('\n') for f in push_file]
104
 
    return location
105
 
 
106
 
"""
107
 
>>> shell_escape('hello')
108
 
'\h\e\l\l\o'
109
 
"""
110
 
def shell_escape(arg):
111
 
    return "".join(['\\'+c for c in arg])
112
 
 
113
 
def safe_system(args):
114
 
    """
115
 
    >>> real_system = os.system
116
 
    >>> os.system = sys.stdout.write
117
 
    >>> safe_system(['a', 'b', 'cd'])
118
 
    \\a \\b \\c\\d
119
 
    >>> os.system = real_system
120
 
    """
121
 
    arg_str = " ".join([shell_escape(a) for a in args])
122
 
    return os.system(arg_str)
123
 
 
124
 
class RsyncUnknownStatus(Exception):
125
 
    def __init__(self, status):
126
 
        Exception.__init__(self, "Unknown status: %d" % status)
127
 
 
128
 
class NoRsync(Exception):
129
 
    def __init__(self, rsync_name):
130
 
        Exception.__init__(self, "%s not found." % rsync_name)
131
 
 
132
 
def rsync(source, target, ssh=False, excludes=(), silent=False, 
133
 
          rsync_name="rsync"):
134
 
    """
135
 
    >>> rsync("a", "b", silent=True)
136
 
    Traceback (most recent call last):
137
 
    RsyncNoFile: No such file a
138
 
    >>> rsync("a", "b", excludes=("*.py",), silent=True)
139
 
    Traceback (most recent call last):
140
 
    RsyncNoFile: No such file a
141
 
    >>> rsync("a", "b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
142
 
    Traceback (most recent call last):
143
 
    NoRsync: rsyncc not found.
144
 
    """
145
 
    cmd = [rsync_name, "-av", "--delete"]
146
 
    if ssh:
147
 
        cmd.extend(('-e', 'ssh'))
148
 
    if len(excludes) > 0:
149
 
        cmd.extend(('--exclude-from', '-'))
150
 
    cmd.extend((source, target))
151
 
    if silent:
152
 
        stderr = PIPE
153
 
        stdout = PIPE
154
 
    else:
155
 
        stderr = None
156
 
        stdout = None
157
 
    try:
158
 
        proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
159
 
    except OSError, e:
160
 
        if e.errno == errno.ENOENT:
161
 
            raise NoRsync(rsync_name)
162
 
            
163
 
    proc.stdin.write('\n'.join(excludes)+'\n')
164
 
    proc.stdin.close()
165
 
    if silent:
166
 
        proc.stderr.read()
167
 
        proc.stderr.close()
168
 
        proc.stdout.read()
169
 
        proc.stdout.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 cmd
178
 
 
179
 
 
180
 
def rsync_ls(source, ssh=False, silent=True):
181
 
    cmd = ["rsync"]
182
 
    if ssh:
183
 
        cmd.extend(('-e', 'ssh'))
184
 
    cmd.append(source)
185
 
    if silent:
186
 
        stderr = PIPE
187
 
    else:
188
 
        stderr = None
189
 
    proc = Popen(cmd, stderr=stderr, stdout=PIPE)
190
 
    result = proc.stdout.read()
191
 
    proc.stdout.close()
192
 
    if silent:
193
 
        proc.stderr.read()
194
 
        proc.stderr.close()
195
 
    proc.wait()
196
 
    if proc.returncode == 12:
197
 
        raise RsyncStreamIO()
198
 
    elif proc.returncode == 23:
199
 
        raise RsyncNoFile(source)
200
 
    elif proc.returncode != 0:
201
 
        raise RsyncUnknownStatus(proc.returncode)
202
 
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
203
 
 
204
 
exclusions = ('.bzr/x-push-data', '.bzr/parent', '.bzr/x-pull-data', 
205
 
              '.bzr/x-pull', '.bzr/pull', '.bzr/stat-cache',
206
 
              '.bzr/x-rsync-data')
207
 
 
208
 
 
209
 
def read_revision_history(fname):
210
 
    return [l.rstrip('\r\n') for l in
211
 
            codecs.open(fname, 'rb', 'utf-8').readlines()]
212
 
 
213
 
class RsyncNoFile(Exception):
214
 
    def __init__(self, path):
215
 
        Exception.__init__(self, "No such file %s" % path)
216
 
 
217
 
class RsyncStreamIO(Exception):
218
 
    def __init__(self):
219
 
        Exception.__init__(self, "Error in rsync protocol data stream.")
220
 
 
221
 
def get_revision_history(location):
222
 
    tempdir = tempfile.mkdtemp('push')
223
 
    try:
224
 
        history_fname = os.path.join(tempdir, 'revision-history')
225
 
        cmd = rsync(location+'.bzr/revision-history', history_fname,
226
 
                    silent=True)
227
 
        history = read_revision_history(history_fname)
228
 
    finally:
229
 
        shutil.rmtree(tempdir)
230
 
    return history
231
 
 
232
 
def history_subset(location, branch):
233
 
    remote_history = get_revision_history(location)
234
 
    local_history = branch.revision_history()
235
 
    if len(remote_history) > len(local_history):
236
 
        return False
237
 
    for local, remote in zip(remote_history, local_history):
238
 
        if local != remote:
239
 
            return False 
240
 
    return True
241
 
 
242
 
def empty_or_absent(location):
243
 
    try:
244
 
        files = rsync_ls(location)
245
 
        return files == ['.']
246
 
    except RsyncNoFile:
247
 
        return True
248
 
 
249
 
def push(cur_branch, location=None, overwrite=False):
250
 
    push_location = get_push_data(cur_branch)
251
 
    if location is not None:
252
 
        if not location.endswith('/'):
253
 
            location += '/'
254
 
        push_location = location
255
 
    
256
 
    if push_location is None:
257
 
        raise bzrlib.errors.MustUseDecorated
258
 
 
259
 
    if push_location.find('://') != -1:
260
 
        raise bzrlib.errors.MustUseDecorated
261
 
 
262
 
    if push_location.find(':') == -1:
263
 
        raise bzrlib.errors.MustUseDecorated
264
 
 
265
 
    clean, non_source = is_clean(cur_branch)
266
 
    if not clean:
267
 
        print """Error: This tree has uncommitted changes or unknown (?) files.
268
 
Use "bzr status" to list them."""
269
 
        sys.exit(1)
270
 
    non_source.extend(exclusions)
271
 
    if not overwrite:
272
 
        try:
273
 
            if not history_subset(push_location, cur_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(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
288
 
 
289
 
    set_push_data(cur_branch, push_location)
290
 
 
291
 
def run_tests():
292
 
    import doctest
293
 
    result = doctest.testmod()
294
 
    if result[1] > 0:
295
 
        if result[0] == 0:
296
 
            print "All tests passed"
297
 
    else:
298
 
        print "No tests to run"
299
 
if __name__ == "__main__":
300
 
    run_tests()