~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to bzrtools.py

  • Committer: Robert Collins
  • Date: 2005-09-28 05:50:54 UTC
  • mfrom: (201)
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050928055054-dbbd7d7169db3a72
merge from abentley

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)
123
126
    arg_str = " ".join([shell_escape(a) for a in args])
124
127
    return os.system(arg_str)
125
128
 
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"]
 
129
class RsyncUnknownStatus(Exception):
 
130
    def __init__(self, status):
 
131
        Exception.__init__(self, "Unknown status: %d" % status)
 
132
 
 
133
class NoRsync(Exception):
 
134
    def __init__(self, rsync_name):
 
135
        Exception.__init__(self, "%s not found." % rsync_name)
 
136
 
 
137
def rsync(source, target, ssh=False, excludes=(), silent=False, 
 
138
          rsync_name="rsync"):
 
139
    """
 
140
    >>> rsync("a", "b", silent=True)
 
141
    Traceback (most recent call last):
 
142
    RsyncNoFile: No such file a
 
143
    >>> rsync("a", "b", excludes=("*.py",), silent=True)
 
144
    Traceback (most recent call last):
 
145
    RsyncNoFile: No such file a
 
146
    >>> rsync("a", "b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
 
147
    Traceback (most recent call last):
 
148
    NoRsync: rsyncc not found.
 
149
    """
 
150
    cmd = [rsync_name, "-av", "--delete"]
137
151
    if ssh:
138
152
        cmd.extend(('-e', 'ssh'))
139
153
    if len(excludes) > 0:
140
154
        cmd.extend(('--exclude-from', '-'))
141
155
    cmd.extend((source, target))
142
 
    proc = Popen(cmd, stdin=PIPE)
 
156
    if silent:
 
157
        stderr = PIPE
 
158
        stdout = PIPE
 
159
    else:
 
160
        stderr = None
 
161
        stdout = None
 
162
    try:
 
163
        proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
 
164
    except OSError, e:
 
165
        if e.errno == errno.ENOENT:
 
166
            raise NoRsync(rsync_name)
 
167
            
143
168
    proc.stdin.write('\n'.join(excludes)+'\n')
144
169
    proc.stdin.close()
 
170
    if silent:
 
171
        proc.stderr.read()
 
172
        proc.stderr.close()
 
173
        proc.stdout.read()
 
174
        proc.stdout.close()
145
175
    proc.wait()
 
176
    if proc.returncode == 12:
 
177
        raise RsyncStreamIO()
 
178
    elif proc.returncode == 23:
 
179
        raise RsyncNoFile(source)
 
180
    elif proc.returncode != 0:
 
181
        raise RsyncUnknownStatus(proc.returncode)
146
182
    return cmd
147
183
 
148
 
exclusions = ('.bzr/x-push-data', '.bzr/x-pull-data', '.bzr/stat-cache')
149
 
 
150
 
 
151
 
def push(cur_branch, location=None):
 
184
 
 
185
def rsync_ls(source, ssh=False, silent=True):
 
186
    cmd = ["rsync"]
 
187
    if ssh:
 
188
        cmd.extend(('-e', 'ssh'))
 
189
    cmd.append(source)
 
190
    if silent:
 
191
        stderr = PIPE
 
192
    else:
 
193
        stderr = None
 
194
    proc = Popen(cmd, stderr=stderr, stdout=PIPE)
 
195
    result = proc.stdout.read()
 
196
    proc.stdout.close()
 
197
    if silent:
 
198
        proc.stderr.read()
 
199
        proc.stderr.close()
 
200
    proc.wait()
 
201
    if proc.returncode == 12:
 
202
        raise RsyncStreamIO()
 
203
    elif proc.returncode == 23:
 
204
        raise RsyncNoFile(source)
 
205
    elif proc.returncode != 0:
 
206
        raise RsyncUnknownStatus(proc.returncode)
 
207
    return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
 
208
 
 
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')
 
212
 
 
213
 
 
214
def read_revision_history(fname):
 
215
    return [l.rstrip('\r\n') for l in
 
216
            codecs.open(fname, 'rb', 'utf-8').readlines()]
 
217
 
 
218
class RsyncNoFile(Exception):
 
219
    def __init__(self, path):
 
220
        Exception.__init__(self, "No such file %s" % path)
 
221
 
 
222
class RsyncStreamIO(Exception):
 
223
    def __init__(self):
 
224
        Exception.__init__(self, "Error in rsync protocol data stream.")
 
225
 
 
226
def get_revision_history(location):
 
227
    tempdir = tempfile.mkdtemp('push')
 
228
    try:
 
229
        history_fname = os.path.join(tempdir, 'revision-history')
 
230
        cmd = rsync(location+'.bzr/revision-history', history_fname,
 
231
                    silent=True)
 
232
        history = read_revision_history(history_fname)
 
233
    finally:
 
234
        shutil.rmtree(tempdir)
 
235
    return history
 
236
 
 
237
def history_subset(location, branch):
 
238
    remote_history = get_revision_history(location)
 
239
    local_history = branch.revision_history()
 
240
    if len(remote_history) > len(local_history):
 
241
        return False
 
242
    for local, remote in zip(remote_history, local_history):
 
243
        if local != remote:
 
244
            return False 
 
245
    return True
 
246
 
 
247
def empty_or_absent(location):
 
248
    try:
 
249
        files = rsync_ls(location)
 
250
        return files == ['.']
 
251
    except RsyncNoFile:
 
252
        return True
 
253
 
 
254
def push(cur_branch, location=None, overwrite=False):
152
255
    push_location = get_push_data(cur_branch)
153
256
    if location is not None:
154
257
        if not location.endswith('/'):
165
268
Use "bzr status" to list them."""
166
269
        sys.exit(1)
167
270
    non_source.extend(exclusions)
168
 
 
 
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)
169
286
    print "Pushing to %s" % push_location
170
287
    rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
171
288