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 urlutils
29
from bzrlib.errors import (BzrCommandError, NotBranchError, NoSuchFile,
30
UnsupportedFormatError, TransportError,
31
NoWorkingTree, PermissionDenied)
32
from bzrlib.bzrdir import BzrDir, BzrDirFormat
33
from bzrlib.transport import get_transport
9
36
dirname = tempfile.mkdtemp("temp-branch")
10
return bzrlib.Branch(dirname, init=True)
13
shutil.rmtree(br.base)
15
def is_clean(cur_branch):
37
return BzrDir.create_standalone_workingtree(dirname)
40
shutil.rmtree(tree.basedir)
42
def is_clean(cur_tree):
17
44
Return true if no files are modifed or unknown
18
>>> br = temp_branch()
21
>>> fooname = os.path.join(br.base, "foo")
22
>>> file(fooname, "wb").write("bar")
25
>>> bzrlib.add.smart_add([fooname])
28
>>> br.commit("added file")
33
old_tree = cur_branch.basis_tree()
34
new_tree = cur_branch.working_tree()
35
for path, file_class, kind, file_id in new_tree.list_files():
38
delta = bzrlib.compare_trees(old_tree, new_tree, want_unchanged=False)
39
if len(delta.added) > 0 or len(delta.removed) > 0 or \
40
len(delta.modified) > 0:
44
def set_pull_data(br, location, rev_id):
45
pull_file = file (br.controlfilename("x-pull-data"), "wb")
46
pull_file.write("%s\n%s\n" % (location, rev_id))
48
def get_pull_data(br):
50
>>> br = temp_branch()
53
>>> set_pull_data(br, 'http://somewhere', '888-777')
55
('http://somewhere', '888-777')
58
filename = br.controlfilename("x-pull-data")
59
if not os.path.exists(filename):
61
pull_file = file (filename, "rb")
62
location, rev_id = [f.rstrip('\n') for f in pull_file]
63
return location, rev_id
65
def set_push_data(br, location):
66
push_file = file (br.controlfilename("x-push-data"), "wb")
67
push_file.write("%s\n" % location)
69
def get_push_data(br):
71
>>> br = temp_branch()
72
>>> get_push_data(br) is None
74
>>> set_push_data(br, 'http://somewhere')
79
filename = br.controlfilename("x-push-data")
80
if not os.path.exists(filename):
46
old_tree = cur_tree.basis_tree()
51
for path, file_class, kind, file_id, entry in new_tree.list_files():
52
if file_class in ('?', 'I'):
53
non_source.append(path)
54
delta = new_tree.changes_from(old_tree, want_unchanged=False)
57
return not delta.has_changed(), non_source
59
def set_push_data(tree, location):
60
tree.branch.control_files.put_utf8("x-push-data", "%s\n" % location)
62
def get_push_data(tree):
64
>>> tree = temp_tree()
65
>>> get_push_data(tree) is None
67
>>> set_push_data(tree, 'http://somewhere')
68
>>> get_push_data(tree)
73
location = tree.branch.control_files.get_utf8('x-push-data').read()
82
push_file = file (filename, "rb")
83
(location,) = [f.rstrip('\n') for f in push_file]
76
return location.rstrip('\n')
87
79
>>> shell_escape('hello')
101
93
arg_str = " ".join([shell_escape(a) for a in args])
102
94
return os.system(arg_str)
104
def rsync(source, target, ssh=False, exclude_globs=()):
106
>>> real_system = os.system
107
>>> os.system = sys.stdout.write
109
\\r\\s\\y\\n\\c \\-\\a\\v \\-\\-\\d\\e\\l\\e\\t\\e \\a \\b
110
>>> rsync("a", "b", exclude_globs=("*.py",))
111
\\r\\s\\y\\n\\c \\-\\a\\v \\-\\-\\d\\e\\l\\e\\t\\e\
112
\\-\\-\\e\\x\\c\\l\\u\\d\\e \\*\\.\\p\\y \\a \\b
113
>>> os.system = real_system
115
cmd = ["rsync", "-av", "--delete"]
96
class RsyncUnknownStatus(Exception):
97
def __init__(self, status):
98
Exception.__init__(self, "Unknown status: %d" % status)
100
class NoRsync(Exception):
101
def __init__(self, rsync_name):
102
Exception.__init__(self, "%s not found." % rsync_name)
104
def rsync(source, target, ssh=False, excludes=(), silent=False,
107
>>> new_dir = tempfile.mkdtemp()
108
>>> old_dir = os.getcwd()
109
>>> os.chdir(new_dir)
110
>>> rsync("a", "b", silent=True)
111
Traceback (most recent call last):
112
RsyncNoFile: No such file...
113
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
114
Traceback (most recent call last):
115
RsyncNoFile: No such file...
116
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
117
Traceback (most recent call last):
118
NoRsync: rsyncc not found.
119
>>> os.chdir(old_dir)
120
>>> os.rmdir(new_dir)
122
cmd = [rsync_name, "-av", "--delete"]
117
124
cmd.extend(('-e', 'ssh'))
118
for exclude in exclude_globs:
119
cmd.extend(('--exclude', exclude))
125
if len(excludes) > 0:
126
cmd.extend(('--exclude-from', '-'))
120
127
cmd.extend((source, target))
123
exclusions = ('x-push-data', 'x-pull-data')
126
def pull(cur_branch, location=None, overwrite=False):
127
pull_location, pull_revision = get_pull_data(cur_branch)
128
if pull_location is not None:
129
if not overwrite and cur_branch.last_patch() != pull_revision:
130
print "Aborting: This branch has had commits, so pull would lose data."
132
if location is not None:
133
pull_location = location
134
if not pull_location.endswith('/'):
137
if pull_location is None:
138
print "No pull location saved. Please specify one on the command line."
141
if not is_clean(cur_branch):
142
print "Error: This tree has uncommitted changes or unknown (?) files."
145
print "Synchronizing with %s" % pull_location
146
rsync (pull_location, cur_branch.base+'/', exclude_globs=exclusions)
148
set_pull_data(cur_branch, pull_location, cur_branch.last_patch())
151
def push(cur_branch, location=None):
152
push_location = get_push_data(cur_branch)
135
proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
137
if e.errno == errno.ENOENT:
138
raise NoRsync(rsync_name)
140
proc.stdin.write('\n'.join(excludes)+'\n')
148
if proc.returncode == 12:
149
raise RsyncStreamIO()
150
elif proc.returncode == 23:
151
raise RsyncNoFile(source)
152
elif proc.returncode != 0:
153
raise RsyncUnknownStatus(proc.returncode)
157
def rsync_ls(source, ssh=False, silent=True):
160
cmd.extend(('-e', 'ssh'))
166
proc = Popen(cmd, stderr=stderr, stdout=PIPE)
167
result = proc.stdout.read()
173
if proc.returncode == 12:
174
raise RsyncStreamIO()
175
elif proc.returncode == 23:
176
raise RsyncNoFile(source)
177
elif proc.returncode != 0:
178
raise RsyncUnknownStatus(proc.returncode)
179
return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
181
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
182
'.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
183
'.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
184
'.bzr/basis-inventory', '.bzr/inventory.backup.weave')
187
def read_revision_history(fname):
188
return [l.rstrip('\r\n') for l in
189
codecs.open(fname, 'rb', 'utf-8').readlines()]
191
class RsyncNoFile(Exception):
192
def __init__(self, path):
193
Exception.__init__(self, "No such file %s" % path)
195
class RsyncStreamIO(Exception):
197
Exception.__init__(self, "Error in rsync protocol data stream.")
199
def get_revision_history(location):
200
tempdir = tempfile.mkdtemp('push')
202
history_fname = os.path.join(tempdir, 'revision-history')
204
cmd = rsync(location+'.bzr/revision-history', history_fname,
207
cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
209
history = read_revision_history(history_fname)
211
shutil.rmtree(tempdir)
214
def history_subset(location, branch):
215
remote_history = get_revision_history(location)
216
local_history = branch.revision_history()
217
if len(remote_history) > len(local_history):
219
for local, remote in zip(remote_history, local_history):
224
def empty_or_absent(location):
226
files = rsync_ls(location)
227
return files == ['.']
231
def rspush(tree, location=None, overwrite=False, working_tree=True):
232
push_location = get_push_data(tree)
153
233
if location is not None:
154
234
if not location.endswith('/'):
156
236
push_location = location
158
238
if push_location is None:
159
print "No push location saved. Please specify one on the command line."
162
if not is_clean(cur_branch):
163
print "Error: This tree has uncommitted changes or unknown (?) files."
239
raise BzrCommandError("No rspush location known or specified.")
241
if (push_location.find('::') != -1):
246
if (push_location.find('://') != -1 or
247
push_location.find(':') == -1):
248
raise BzrCommandError("Invalid rsync path %r." % push_location)
251
clean, non_source = is_clean(tree)
253
print """Error: This tree has uncommitted changes or unknown (?) files.
254
Use "bzr status" to list them."""
256
final_exclusions = non_source[:]
259
final_exclusions = []
260
for path, status, kind, file_id, entry in wt.list_files():
261
final_exclusions.append(path)
263
final_exclusions.extend(exclusions)
266
if not history_subset(push_location, tree.branch):
267
raise bzrlib.errors.BzrCommandError("Local branch is not a"
268
" newer version of remote"
271
if not empty_or_absent(push_location):
272
raise bzrlib.errors.BzrCommandError("Remote location is not a"
273
" bzr branch (or empty"
275
except RsyncStreamIO:
276
raise bzrlib.errors.BzrCommandError("Rsync could not use the"
277
" specified location. Please ensure that"
278
' "%s" is of the form "machine:/path".' % push_location)
166
279
print "Pushing to %s" % push_location
167
rsync(cur_branch.base+'/', push_location, ssh=True,
168
exclude_globs=exclusions)
170
set_push_data(cur_branch, push_location)
280
rsync(tree.basedir+'/', push_location, ssh=usessh,
281
excludes=final_exclusions)
283
set_push_data(tree, push_location)
286
def short_committer(committer):
287
new_committer = re.sub('<.*>', '', committer).strip(' ')
288
if len(new_committer) < 2:
294
"""Screen-scrape Apache listings"""
295
apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
298
expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
300
match = expr.search(line)
304
if url.startswith('http://') or url.startswith('/') or '../' in url:
308
yield url.rstrip('/')
311
def iter_branches(t, lister=None):
312
"""Iterate through all the branches under a transport"""
313
for bzrdir in iter_bzrdirs(t, lister):
315
branch = bzrdir.open_branch()
316
if branch.bzrdir is bzrdir:
318
except (NotBranchError, UnsupportedFormatError):
322
def iter_branch_tree(t, lister=None):
323
for bzrdir in iter_bzrdirs(t, lister):
325
wt = bzrdir.open_workingtree()
327
except NoWorkingTree, UnsupportedFormatError:
329
branch = bzrdir.open_branch()
330
if branch.bzrdir is bzrdir:
332
except (NotBranchError, UnsupportedFormatError):
336
def iter_bzrdirs(t, lister=None):
339
return t.list_dir('.')
341
bzrdir = bzrdir_from_transport(t)
343
except (NotBranchError, UnsupportedFormatError, TransportError,
347
for directory in lister(t):
348
if directory == ".bzr":
351
subt = t.clone(directory)
352
except UnicodeDecodeError:
354
for bzrdir in iter_bzrdirs(subt, lister):
356
except (NoSuchFile, PermissionDenied, TransportError):
360
def bzrdir_from_transport(t):
361
"""Open a bzrdir from a transport (not a location)"""
362
format = BzrDirFormat.find_format(t)
363
BzrDir._check_supported(format, False)
364
return format.open(t)
367
def open_from_url(location):
368
location = urlutils.normalize_url(location)
369
dirname, basename = urlutils.split(location)
370
return get_transport(dirname).get(basename)