14
14
# You should have received a copy of the GNU General Public License
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
23
23
from subprocess import Popen, PIPE
28
from bzrlib.errors import (BzrCommandError, NotBranchError, NoSuchFile,
29
UnsupportedFormatError, TransportError,
30
NoWorkingTree, PermissionDenied)
31
from bzrlib.bzrdir import BzrDir, BzrDirFormat
26
34
dirname = tempfile.mkdtemp("temp-branch")
27
return bzrlib.branch.Branch.initialize(dirname)
30
shutil.rmtree(br.base)
32
def is_clean(cur_branch):
35
return BzrDir.create_standalone_workingtree(dirname)
38
shutil.rmtree(tree.basedir)
40
def is_clean(cur_tree):
34
42
Return true if no files are modifed or unknown
35
43
>>> import bzrlib.add
36
>>> br = temp_branch()
44
>>> tree = temp_tree()
39
>>> fooname = os.path.join(br.base, "foo")
47
>>> fooname = os.path.join(tree.basedir, "foo")
40
48
>>> file(fooname, "wb").write("bar")
43
>>> bzrlib.add.smart_add_branch(br, [br.base])
51
>>> bzrlib.add.smart_add_tree(tree, [tree.basedir])
47
>>> br.commit("added file")
55
>>> tree.commit("added file", rev_id='commit-id')
53
from bzrlib.diff import compare_trees
54
old_tree = cur_branch.basis_tree()
55
new_tree = cur_branch.working_tree()
61
old_tree = cur_tree.basis_tree()
57
for path, file_class, kind, file_id in new_tree.list_files():
58
if file_class in ('?', 'I'):
59
non_source.append(path)
60
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
66
def set_pull_data(br, location, rev_id):
67
pull_file = file (br.controlfilename("x-pull-data"), "wb")
68
pull_file.write("%s\n%s\n" % (location, rev_id))
70
def get_pull_data(br):
72
>>> br = temp_branch()
75
>>> set_pull_data(br, 'http://somewhere', '888-777')
77
('http://somewhere', '888-777')
80
filename = br.controlfilename("x-pull-data")
81
if not os.path.exists(filename):
83
pull_file = file (filename, "rb")
84
location, rev_id = [f.rstrip('\n') for f in pull_file]
85
return location, rev_id
87
def set_push_data(br, location):
88
push_file = file (br.controlfilename("x-push-data"), "wb")
89
push_file.write("%s\n" % location)
91
def get_push_data(br):
93
>>> br = temp_branch()
94
>>> get_push_data(br) is None
66
for path, file_class, kind, file_id, entry in new_tree.list_files():
67
if file_class in ('?', 'I'):
68
non_source.append(path)
69
delta = new_tree.changes_from(old_tree, want_unchanged=False)
72
return not delta.has_changed(), non_source
74
def set_push_data(tree, location):
75
tree.branch.control_files.put_utf8("x-push-data", "%s\n" % location)
77
def get_push_data(tree):
79
>>> tree = temp_tree()
80
>>> get_push_data(tree) is None
96
>>> set_push_data(br, 'http://somewhere')
82
>>> set_push_data(tree, 'http://somewhere')
83
>>> get_push_data(tree)
101
filename = br.controlfilename("x-push-data")
102
if not os.path.exists(filename):
88
location = tree.branch.control_files.get_utf8('x-push-data').read()
104
push_file = file (filename, "rb")
105
(location,) = [f.rstrip('\n') for f in push_file]
91
return location.rstrip('\n')
109
94
>>> shell_escape('hello')
123
108
arg_str = " ".join([shell_escape(a) for a in args])
124
109
return os.system(arg_str)
126
def rsync(source, target, ssh=False, excludes=()):
128
>>> real_system = os.system
129
>>> os.system = sys.stdout.write
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
136
cmd = ["rsync", "-av", "--delete"]
111
class RsyncUnknownStatus(Exception):
112
def __init__(self, status):
113
Exception.__init__(self, "Unknown status: %d" % status)
115
class NoRsync(Exception):
116
def __init__(self, rsync_name):
117
Exception.__init__(self, "%s not found." % rsync_name)
119
def rsync(source, target, ssh=False, excludes=(), silent=False,
122
>>> new_dir = tempfile.mkdtemp()
123
>>> old_dir = os.getcwd()
124
>>> os.chdir(new_dir)
125
>>> rsync("a", "b", silent=True)
126
Traceback (most recent call last):
127
RsyncNoFile: No such file...
128
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
129
Traceback (most recent call last):
130
RsyncNoFile: No such file...
131
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
132
Traceback (most recent call last):
133
NoRsync: rsyncc not found.
134
>>> os.chdir(old_dir)
135
>>> os.rmdir(new_dir)
137
cmd = [rsync_name, "-av", "--delete"]
138
139
cmd.extend(('-e', 'ssh'))
139
140
if len(excludes) > 0:
140
141
cmd.extend(('--exclude-from', '-'))
141
142
cmd.extend((source, target))
142
proc = Popen(cmd, stdin=PIPE)
150
proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
152
if e.errno == errno.ENOENT:
153
raise NoRsync(rsync_name)
143
155
proc.stdin.write('\n'.join(excludes)+'\n')
144
156
proc.stdin.close()
163
if proc.returncode == 12:
164
raise RsyncStreamIO()
165
elif proc.returncode == 23:
166
raise RsyncNoFile(source)
167
elif proc.returncode != 0:
168
raise RsyncUnknownStatus(proc.returncode)
148
exclusions = ('.bzr/x-push-data', '.bzr/x-pull-data', '.bzr/stat-cache')
151
def push(cur_branch, location=None):
152
push_location = get_push_data(cur_branch)
172
def rsync_ls(source, ssh=False, silent=True):
175
cmd.extend(('-e', 'ssh'))
181
proc = Popen(cmd, stderr=stderr, stdout=PIPE)
182
result = proc.stdout.read()
188
if proc.returncode == 12:
189
raise RsyncStreamIO()
190
elif proc.returncode == 23:
191
raise RsyncNoFile(source)
192
elif proc.returncode != 0:
193
raise RsyncUnknownStatus(proc.returncode)
194
return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
196
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
197
'.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
198
'.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
199
'.bzr/basis-inventory', '.bzr/inventory.backup.weave')
202
def read_revision_history(fname):
203
return [l.rstrip('\r\n') for l in
204
codecs.open(fname, 'rb', 'utf-8').readlines()]
206
class RsyncNoFile(Exception):
207
def __init__(self, path):
208
Exception.__init__(self, "No such file %s" % path)
210
class RsyncStreamIO(Exception):
212
Exception.__init__(self, "Error in rsync protocol data stream.")
214
def get_revision_history(location):
215
tempdir = tempfile.mkdtemp('push')
217
history_fname = os.path.join(tempdir, 'revision-history')
219
cmd = rsync(location+'.bzr/revision-history', history_fname,
222
cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
224
history = read_revision_history(history_fname)
226
shutil.rmtree(tempdir)
229
def history_subset(location, branch):
230
remote_history = get_revision_history(location)
231
local_history = branch.revision_history()
232
if len(remote_history) > len(local_history):
234
for local, remote in zip(remote_history, local_history):
239
def empty_or_absent(location):
241
files = rsync_ls(location)
242
return files == ['.']
246
def rspush(tree, location=None, overwrite=False, working_tree=True):
247
push_location = get_push_data(tree)
153
248
if location is not None:
154
249
if not location.endswith('/'):
156
251
push_location = location
158
253
if push_location is None:
159
print "No push location saved. Please specify one on the command line."
162
clean, non_source = is_clean(cur_branch)
164
print """Error: This tree has uncommitted changes or unknown (?) files.
165
Use "bzr status" to list them."""
167
non_source.extend(exclusions)
254
raise BzrCommandError("No rspush location known or specified.")
256
if (push_location.find('::') != -1):
261
if (push_location.find('://') != -1 or
262
push_location.find(':') == -1):
263
raise BzrCommandError("Invalid rsync path %r." % push_location)
266
clean, non_source = is_clean(tree)
268
print """Error: This tree has uncommitted changes or unknown (?) files.
269
Use "bzr status" to list them."""
271
final_exclusions = non_source[:]
274
final_exclusions = []
275
for path, status, kind, file_id, entry in wt.list_files():
276
final_exclusions.append(path)
278
final_exclusions.extend(exclusions)
281
if not history_subset(push_location, tree.branch):
282
raise bzrlib.errors.BzrCommandError("Local branch is not a"
283
" newer version of remote"
286
if not empty_or_absent(push_location):
287
raise bzrlib.errors.BzrCommandError("Remote location is not a"
288
" bzr branch (or empty"
290
except RsyncStreamIO:
291
raise bzrlib.errors.BzrCommandError("Rsync could not use the"
292
" specified location. Please ensure that"
293
' "%s" is of the form "machine:/path".' % push_location)
169
294
print "Pushing to %s" % push_location
170
rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
172
set_push_data(cur_branch, push_location)
295
rsync(tree.basedir+'/', push_location, ssh=usessh,
296
excludes=final_exclusions)
298
set_push_data(tree, push_location)
301
def short_committer(committer):
302
new_committer = re.sub('<.*>', '', committer).strip(' ')
303
if len(new_committer) < 2:
309
"""Screen-scrape Apache listings"""
310
apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
313
expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
315
match = expr.search(line)
319
if url.startswith('http://') or url.startswith('/') or '../' in url:
323
yield url.rstrip('/')
326
def iter_branches(t, lister=None):
327
"""Iterate through all the branches under a transport"""
328
for bzrdir in iter_bzrdirs(t, lister):
330
branch = bzrdir.open_branch()
331
if branch.bzrdir is bzrdir:
333
except (NotBranchError, UnsupportedFormatError):
337
def iter_branch_tree(t, lister=None):
338
for bzrdir in iter_bzrdirs(t, lister):
340
wt = bzrdir.open_workingtree()
342
except NoWorkingTree, UnsupportedFormatError:
344
branch = bzrdir.open_branch()
345
if branch.bzrdir is bzrdir:
347
except (NotBranchError, UnsupportedFormatError):
351
def iter_bzrdirs(t, lister=None):
354
return t.list_dir('.')
356
bzrdir = bzrdir_from_transport(t)
358
except (NotBranchError, UnsupportedFormatError, TransportError,
362
for directory in lister(t):
363
if directory == ".bzr":
366
subt = t.clone(directory)
367
except UnicodeDecodeError:
369
for bzrdir in iter_bzrdirs(subt, lister):
371
except (NoSuchFile, PermissionDenied, TransportError):
375
def bzrdir_from_transport(t):
376
"""Open a bzrdir from a transport (not a location)"""
377
format = BzrDirFormat.find_format(t)
378
BzrDir._check_supported(format, False)
379
return format.open(t)