1
# Copyright (C) 2005, 2006, 2007 Aaron Bentley <aaron.bentley@utoronto.ca>
2
# Copyright (C) 2007 John Arbash Meinel
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.
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.
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
23
from subprocess import Popen, PIPE
27
from bzrlib import trace, urlutils
29
from bzrlib.errors import (
37
UnsupportedFormatError,
40
from bzrlib.bzrdir import BzrDir, BzrDirFormat
41
from bzrlib.transport import get_transport
44
dirname = tempfile.mkdtemp("temp-branch")
45
return BzrDir.create_standalone_workingtree(dirname)
48
shutil.rmtree(tree.basedir)
50
def is_clean(cur_tree):
52
Return true if no files are modifed or unknown
54
old_tree = cur_tree.basis_tree()
59
for path, file_class, kind, file_id, entry in new_tree.list_files():
60
if file_class in ('?', 'I'):
61
non_source.append(path)
62
delta = new_tree.changes_from(old_tree, want_unchanged=False)
65
return not delta.has_changed(), non_source
67
def set_push_data(tree, location):
68
tree.branch.control_files.put_utf8("x-push-data", "%s\n" % location)
70
def get_push_data(tree):
72
>>> tree = temp_tree()
73
>>> get_push_data(tree) is None
75
>>> set_push_data(tree, 'http://somewhere')
76
>>> get_push_data(tree)
81
location = tree.branch.control_files.get_utf8('x-push-data').read()
84
return location.rstrip('\n')
87
>>> shell_escape('hello')
90
def shell_escape(arg):
91
return "".join(['\\'+c for c in arg])
93
def safe_system(args):
95
>>> real_system = os.system
96
>>> os.system = sys.stdout.write
97
>>> safe_system(['a', 'b', 'cd'])
99
>>> os.system = real_system
101
arg_str = " ".join([shell_escape(a) for a in args])
102
return os.system(arg_str)
104
class RsyncUnknownStatus(Exception):
105
def __init__(self, status):
106
Exception.__init__(self, "Unknown status: %d" % status)
108
class NoRsync(Exception):
109
def __init__(self, rsync_name):
110
Exception.__init__(self, "%s not found." % rsync_name)
113
def rsync(source, target, ssh=False, excludes=(), silent=False,
116
>>> new_dir = tempfile.mkdtemp()
117
>>> old_dir = os.getcwd()
118
>>> os.chdir(new_dir)
119
>>> rsync("a", "b", silent=True)
120
Traceback (most recent call last):
121
RsyncNoFile: No such file...
122
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
123
Traceback (most recent call last):
124
RsyncNoFile: No such file...
125
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
126
Traceback (most recent call last):
127
NoRsync: rsyncc not found.
128
>>> os.chdir(old_dir)
129
>>> os.rmdir(new_dir)
131
cmd = [rsync_name, "-av", "--delete"]
133
cmd.extend(('-e', 'ssh'))
134
if len(excludes) > 0:
135
cmd.extend(('--exclude-from', '-'))
136
cmd.extend((source, target))
144
proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
146
if e.errno == errno.ENOENT:
147
raise NoRsync(rsync_name)
149
proc.stdin.write('\n'.join(excludes)+'\n')
157
if proc.returncode == 12:
158
raise RsyncStreamIO()
159
elif proc.returncode == 23:
160
raise RsyncNoFile(source)
161
elif proc.returncode != 0:
162
raise RsyncUnknownStatus(proc.returncode)
166
def rsync_ls(source, ssh=False, silent=True):
169
cmd.extend(('-e', 'ssh'))
175
proc = Popen(cmd, stderr=stderr, stdout=PIPE)
176
result = proc.stdout.read()
182
if proc.returncode == 12:
183
raise RsyncStreamIO()
184
elif proc.returncode == 23:
185
raise RsyncNoFile(source)
186
elif proc.returncode != 0:
187
raise RsyncUnknownStatus(proc.returncode)
188
return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
190
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
191
'.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
192
'.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
193
'.bzr/basis-inventory', '.bzr/inventory.backup.weave')
196
def read_revision_history(fname):
197
return [l.rstrip('\r\n') for l in
198
codecs.open(fname, 'rb', 'utf-8').readlines()]
200
class RsyncNoFile(Exception):
201
def __init__(self, path):
202
Exception.__init__(self, "No such file %s" % path)
204
class RsyncStreamIO(Exception):
206
Exception.__init__(self, "Error in rsync protocol data stream.")
209
class NotStandalone(BzrError):
211
_format = '%(location) is not a standalone tree.'
214
def __init__(self, location):
215
BzrError.__init__(self, location=location)
218
def get_revision_history(location, _rsync):
219
tempdir = tempfile.mkdtemp('push')
224
history_fname = os.path.join(tempdir, 'revision-history')
226
cmd = my_rsync(location+'.bzr/revision-history', history_fname,
229
cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
231
history = read_revision_history(history_fname)
233
shutil.rmtree(tempdir)
237
def history_subset(location, branch, _rsync=None):
238
remote_history = get_revision_history(location, _rsync)
239
local_history = branch.revision_history()
240
if len(remote_history) > len(local_history):
242
for local, remote in zip(remote_history, local_history):
247
def empty_or_absent(location):
249
files = rsync_ls(location)
250
return files == ['.']
254
def rspush(tree, location=None, overwrite=False, working_tree=True,
261
if (tree.bzrdir.root_transport.base !=
262
tree.branch.bzrdir.root_transport.base):
263
raise NotStandalone(tree.bzrdir.root_transport.base)
264
if (tree.branch.get_bound_location() is not None):
265
raise NotStandalone(tree.bzrdir.root_transport.base)
266
if (tree.branch.repository.is_shared()):
267
raise NotStandalone(tree.bzrdir.root_transport.base)
268
push_location = get_push_data(tree)
269
if location is not None:
270
if not location.endswith('/'):
272
push_location = location
274
if push_location is None:
275
raise BzrCommandError("No rspush location known or specified.")
277
if (push_location.find('::') != -1):
282
if (push_location.find('://') != -1 or
283
push_location.find(':') == -1):
284
raise BzrCommandError("Invalid rsync path %r." % push_location)
287
clean, non_source = is_clean(tree)
289
raise bzrlib.errors.BzrCommandError(
290
'This tree has uncommitted changes or unknown'
291
' (?) files. Use "bzr status" to list them.')
293
final_exclusions = non_source[:]
296
final_exclusions = []
297
for path, status, kind, file_id, entry in wt.list_files():
298
final_exclusions.append(path)
300
final_exclusions.extend(exclusions)
303
if not history_subset(push_location, tree.branch,
305
raise bzrlib.errors.BzrCommandError(
306
"Local branch is not a newer version of remote"
309
if not empty_or_absent(push_location):
310
raise bzrlib.errors.BzrCommandError(
311
"Remote location is not a bzr branch (or empty"
313
except RsyncStreamIO:
314
raise bzrlib.errors.BzrCommandError("Rsync could not use the"
315
" specified location. Please ensure that"
316
' "%s" is of the form "machine:/path".' % push_location)
317
trace.note("Pushing to %s", push_location)
318
my_rsync(tree.basedir+'/', push_location, ssh=usessh,
319
excludes=final_exclusions)
321
set_push_data(tree, push_location)
326
def short_committer(committer):
327
new_committer = re.sub('<.*>', '', committer).strip(' ')
328
if len(new_committer) < 2:
334
"""Screen-scrape Apache listings"""
335
apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
338
t._remote_path = lambda x: t.base
340
expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
342
match = expr.search(line)
346
if url.startswith('http://') or url.startswith('/') or '../' in url:
350
yield url.rstrip('/')
353
def iter_branches(t, lister=None):
354
"""Iterate through all the branches under a transport"""
355
for bzrdir in iter_bzrdirs(t, lister):
357
branch = bzrdir.open_branch()
358
if branch.bzrdir is bzrdir:
360
except (NotBranchError, UnsupportedFormatError):
364
def iter_branch_tree(t, lister=None):
365
for bzrdir in iter_bzrdirs(t, lister):
367
wt = bzrdir.open_workingtree()
369
except NoWorkingTree, UnsupportedFormatError:
371
branch = bzrdir.open_branch()
372
if branch.bzrdir is bzrdir:
374
except (NotBranchError, UnsupportedFormatError):
378
def iter_bzrdirs(t, lister=None):
381
return t.list_dir('.')
383
bzrdir = bzrdir_from_transport(t)
385
except (ConnectionError):
387
except (NotBranchError, UnsupportedFormatError, TransportError,
391
for directory in lister(t):
392
if directory == ".bzr":
395
subt = t.clone(directory)
396
except UnicodeDecodeError:
398
for bzrdir in iter_bzrdirs(subt, lister):
400
except (NoSuchFile, PermissionDenied, TransportError):
404
def bzrdir_from_transport(t):
405
"""Open a bzrdir from a transport (not a location)"""
406
format = BzrDirFormat.find_format(t)
407
BzrDir._check_supported(format, False)
408
return format.open(t)
411
def open_from_url(location):
412
location = urlutils.normalize_url(location)
413
dirname, basename = urlutils.split(location)
414
return get_transport(dirname).get(basename)
419
result = doctest.testmod()
422
print "All tests passed"
424
print "No tests to run"
425
if __name__ == "__main__":