~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Aaron Bentley
  • Date: 2005-11-11 17:43:12 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20051111174312-1c627d82a07bf8fd
Added patch for tab-in-patch-filename support

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
17
import bzrlib
 
18
import bzrlib.errors
18
19
import os
19
20
import os.path
20
21
import sys
21
22
import tempfile
22
23
import shutil
 
24
import errno
23
25
from subprocess import Popen, PIPE
 
26
import codecs
24
27
 
25
28
def temp_branch():
26
29
    dirname = tempfile.mkdtemp("temp-branch")
27
 
    return bzrlib.branch.Branch(dirname, init=True)
 
30
    return bzrlib.branch.Branch.initialize(dirname)
28
31
 
29
32
def rm_branch(br):
30
33
    shutil.rmtree(br.base)
39
42
    >>> fooname = os.path.join(br.base, "foo")
40
43
    >>> file(fooname, "wb").write("bar")
41
44
    >>> is_clean(br)
42
 
    (True, ['foo'])
 
45
    (True, [u'foo'])
43
46
    >>> bzrlib.add.smart_add_branch(br, [br.base])
44
47
    1
45
48
    >>> is_clean(br)
46
49
    (False, [])
47
50
    >>> br.commit("added file")
48
 
    added foo
49
51
    >>> is_clean(br)
50
52
    (True, [])
51
53
    >>> rm_branch(br)
54
56
    old_tree = cur_branch.basis_tree()
55
57
    new_tree = cur_branch.working_tree()
56
58
    non_source = []
57
 
    for path, file_class, kind, file_id in new_tree.list_files():
 
59
    for path, file_class, kind, file_id, entry in new_tree.list_files():
58
60
        if file_class in ('?', 'I'):
59
61
            non_source.append(path)
60
62
    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 
 
63
    return not delta.has_changed(), non_source
65
64
 
66
65
def set_pull_data(br, location, rev_id):
67
66
    pull_file = file (br.controlfilename("x-pull-data"), "wb")
123
122
    arg_str = " ".join([shell_escape(a) for a in args])
124
123
    return os.system(arg_str)
125
124
 
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"]
 
125
class RsyncUnknownStatus(Exception):
 
126
    def __init__(self, status):
 
127
        Exception.__init__(self, "Unknown status: %d" % status)
 
128
 
 
129
class NoRsync(Exception):
 
130
    def __init__(self, rsync_name):
 
131
        Exception.__init__(self, "%s not found." % rsync_name)
 
132
 
 
133
def rsync(source, target, ssh=False, excludes=(), silent=False, 
 
134
          rsync_name="rsync"):
 
135
    """
 
136
    >>> rsync("a", "b", silent=True)
 
137
    Traceback (most recent call last):
 
138
    RsyncNoFile: No such file a
 
139
    >>> rsync("a", "b", excludes=("*.py",), silent=True)
 
140
    Traceback (most recent call last):
 
141
    RsyncNoFile: No such file a
 
142
    >>> rsync("a", "b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
 
143
    Traceback (most recent call last):
 
144
    NoRsync: rsyncc not found.
 
145
    """
 
146
    cmd = [rsync_name, "-av", "--delete"]
137
147
    if ssh:
138
148
        cmd.extend(('-e', 'ssh'))
139
149
    if len(excludes) > 0:
140
150
        cmd.extend(('--exclude-from', '-'))
141
151
    cmd.extend((source, target))
142
 
    proc = Popen(cmd, stdin=PIPE)
 
152
    if silent:
 
153
        stderr = PIPE
 
154
        stdout = PIPE
 
155
    else:
 
156
        stderr = None
 
157
        stdout = None
 
158
    try:
 
159
        proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
 
160
    except OSError, e:
 
161
        if e.errno == errno.ENOENT:
 
162
            raise NoRsync(rsync_name)
 
163
            
143
164
    proc.stdin.write('\n'.join(excludes)+'\n')
144
165
    proc.stdin.close()
 
166
    if silent:
 
167
        proc.stderr.read()
 
168
        proc.stderr.close()
 
169
        proc.stdout.read()
 
170
        proc.stdout.close()
145
171
    proc.wait()
 
172
    if proc.returncode == 12:
 
173
        raise RsyncStreamIO()
 
174
    elif proc.returncode == 23:
 
175
        raise RsyncNoFile(source)
 
176
    elif proc.returncode != 0:
 
177
        raise RsyncUnknownStatus(proc.returncode)
146
178
    return cmd
147
179
 
148
 
exclusions = ('.bzr/x-push-data', '.bzr/x-pull-data', '.bzr/stat-cache')
149
 
 
150
 
 
151
 
def push(cur_branch, location=None):
 
180
 
 
181
def rsync_ls(source, ssh=False, silent=True):
 
182
    cmd = ["rsync"]
 
183
    if ssh:
 
184
        cmd.extend(('-e', 'ssh'))
 
185
    cmd.append(source)
 
186
    if silent:
 
187
        stderr = PIPE
 
188
    else:
 
189
        stderr = None
 
190
    proc = Popen(cmd, stderr=stderr, stdout=PIPE)
 
191
    result = proc.stdout.read()
 
192
    proc.stdout.close()
 
193
    if silent:
 
194
        proc.stderr.read()
 
195
        proc.stderr.close()
 
196
    proc.wait()
 
197
    if proc.returncode == 12:
 
198
        raise RsyncStreamIO()
 
199
    elif proc.returncode == 23:
 
200
        raise RsyncNoFile(source)
 
201
    elif proc.returncode != 0:
 
202
        raise RsyncUnknownStatus(proc.returncode)
 
203
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
 
204
 
 
205
exclusions = ('.bzr/x-push-data', '.bzr/parent', '.bzr/x-pull-data', 
 
206
              '.bzr/x-pull', '.bzr/pull', '.bzr/stat-cache',
 
207
              '.bzr/x-rsync-data')
 
208
 
 
209
 
 
210
def read_revision_history(fname):
 
211
    return [l.rstrip('\r\n') for l in
 
212
            codecs.open(fname, 'rb', 'utf-8').readlines()]
 
213
 
 
214
class RsyncNoFile(Exception):
 
215
    def __init__(self, path):
 
216
        Exception.__init__(self, "No such file %s" % path)
 
217
 
 
218
class RsyncStreamIO(Exception):
 
219
    def __init__(self):
 
220
        Exception.__init__(self, "Error in rsync protocol data stream.")
 
221
 
 
222
def get_revision_history(location):
 
223
    tempdir = tempfile.mkdtemp('push')
 
224
    try:
 
225
        history_fname = os.path.join(tempdir, 'revision-history')
 
226
        cmd = rsync(location+'.bzr/revision-history', history_fname,
 
227
                    silent=True)
 
228
        history = read_revision_history(history_fname)
 
229
    finally:
 
230
        shutil.rmtree(tempdir)
 
231
    return history
 
232
 
 
233
def history_subset(location, branch):
 
234
    remote_history = get_revision_history(location)
 
235
    local_history = branch.revision_history()
 
236
    if len(remote_history) > len(local_history):
 
237
        return False
 
238
    for local, remote in zip(remote_history, local_history):
 
239
        if local != remote:
 
240
            return False 
 
241
    return True
 
242
 
 
243
def empty_or_absent(location):
 
244
    try:
 
245
        files = rsync_ls(location)
 
246
        return files == ['.']
 
247
    except RsyncNoFile:
 
248
        return True
 
249
 
 
250
def push(cur_branch, location=None, overwrite=False):
152
251
    push_location = get_push_data(cur_branch)
153
252
    if location is not None:
154
253
        if not location.endswith('/'):
156
255
        push_location = location
157
256
    
158
257
    if push_location is None:
159
 
        print "No push location saved.  Please specify one on the command line."
160
 
        sys.exit(1)
 
258
        raise bzrlib.errors.MustUseDecorated
 
259
 
 
260
    if push_location.find('://') != -1:
 
261
        raise bzrlib.errors.MustUseDecorated
 
262
 
 
263
    if push_location.find(':') == -1:
 
264
        raise bzrlib.errors.MustUseDecorated
161
265
 
162
266
    clean, non_source = is_clean(cur_branch)
163
267
    if not clean:
165
269
Use "bzr status" to list them."""
166
270
        sys.exit(1)
167
271
    non_source.extend(exclusions)
168
 
 
 
272
    if not overwrite:
 
273
        try:
 
274
            if not history_subset(push_location, cur_branch):
 
275
                raise bzrlib.errors.BzrCommandError("Local branch is not a"
 
276
                                                    " newer version of remote"
 
277
                                                    " branch.")
 
278
        except RsyncNoFile:
 
279
            if not empty_or_absent(push_location):
 
280
                raise bzrlib.errors.BzrCommandError("Remote location is not a"
 
281
                                                    " bzr branch (or empty"
 
282
                                                    " directory)")
 
283
        except RsyncStreamIO:
 
284
            raise bzrlib.errors.BzrCommandError("Rsync could not use the"
 
285
                " specified location.  Please ensure that"
 
286
                ' "%s" is of the form "machine:/path".' % push_location)
169
287
    print "Pushing to %s" % push_location
170
288
    rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
171
289