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
34
26
dirname = tempfile.mkdtemp("temp-branch")
35
return BzrDir.create_standalone_workingtree(dirname)
38
shutil.rmtree(tree.basedir)
40
def is_clean(cur_tree):
27
return bzrlib.branch.Branch(dirname, init=True)
30
shutil.rmtree(br.base)
32
def is_clean(cur_branch):
42
34
Return true if no files are modifed or unknown
36
>>> br = temp_branch()
39
>>> fooname = os.path.join(br.base, "foo")
40
>>> file(fooname, "wb").write("bar")
43
>>> bzrlib.add.smart_add_branch(br, [br.base])
47
>>> br.commit("added file")
44
old_tree = cur_tree.basis_tree()
53
from bzrlib.diff import compare_trees
54
old_tree = cur_branch.basis_tree()
55
new_tree = cur_branch.working_tree()
49
for path, file_class, kind, file_id, entry in new_tree.list_files():
50
if file_class in ('?', 'I'):
51
non_source.append(path)
52
delta = new_tree.changes_from(old_tree, want_unchanged=False)
55
return not delta.has_changed(), non_source
57
def set_push_data(tree, location):
58
tree.branch.control_files.put_utf8("x-push-data", "%s\n" % location)
60
def get_push_data(tree):
62
>>> tree = temp_tree()
63
>>> get_push_data(tree) is None
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
65
>>> set_push_data(tree, 'http://somewhere')
66
>>> get_push_data(tree)
96
>>> set_push_data(br, 'http://somewhere')
71
location = tree.branch.control_files.get_utf8('x-push-data').read()
101
filename = br.controlfilename("x-push-data")
102
if not os.path.exists(filename):
74
return location.rstrip('\n')
104
push_file = file (filename, "rb")
105
(location,) = [f.rstrip('\n') for f in push_file]
77
109
>>> shell_escape('hello')
91
123
arg_str = " ".join([shell_escape(a) for a in args])
92
124
return os.system(arg_str)
94
class RsyncUnknownStatus(Exception):
95
def __init__(self, status):
96
Exception.__init__(self, "Unknown status: %d" % status)
98
class NoRsync(Exception):
99
def __init__(self, rsync_name):
100
Exception.__init__(self, "%s not found." % rsync_name)
102
def rsync(source, target, ssh=False, excludes=(), silent=False,
105
>>> new_dir = tempfile.mkdtemp()
106
>>> old_dir = os.getcwd()
107
>>> os.chdir(new_dir)
108
>>> rsync("a", "b", silent=True)
109
Traceback (most recent call last):
110
RsyncNoFile: No such file...
111
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
112
Traceback (most recent call last):
113
RsyncNoFile: No such file...
114
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
115
Traceback (most recent call last):
116
NoRsync: rsyncc not found.
117
>>> os.chdir(old_dir)
118
>>> os.rmdir(new_dir)
120
cmd = [rsync_name, "-av", "--delete"]
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"]
122
138
cmd.extend(('-e', 'ssh'))
123
139
if len(excludes) > 0:
124
140
cmd.extend(('--exclude-from', '-'))
125
141
cmd.extend((source, target))
133
proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
135
if e.errno == errno.ENOENT:
136
raise NoRsync(rsync_name)
142
proc = Popen(cmd, stdin=PIPE)
138
143
proc.stdin.write('\n'.join(excludes)+'\n')
139
144
proc.stdin.close()
146
if proc.returncode == 12:
147
raise RsyncStreamIO()
148
elif proc.returncode == 23:
149
raise RsyncNoFile(source)
150
elif proc.returncode != 0:
151
raise RsyncUnknownStatus(proc.returncode)
155
def rsync_ls(source, ssh=False, silent=True):
158
cmd.extend(('-e', 'ssh'))
164
proc = Popen(cmd, stderr=stderr, stdout=PIPE)
165
result = proc.stdout.read()
171
if proc.returncode == 12:
172
raise RsyncStreamIO()
173
elif proc.returncode == 23:
174
raise RsyncNoFile(source)
175
elif proc.returncode != 0:
176
raise RsyncUnknownStatus(proc.returncode)
177
return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
179
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
180
'.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
181
'.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
182
'.bzr/basis-inventory', '.bzr/inventory.backup.weave')
185
def read_revision_history(fname):
186
return [l.rstrip('\r\n') for l in
187
codecs.open(fname, 'rb', 'utf-8').readlines()]
189
class RsyncNoFile(Exception):
190
def __init__(self, path):
191
Exception.__init__(self, "No such file %s" % path)
193
class RsyncStreamIO(Exception):
195
Exception.__init__(self, "Error in rsync protocol data stream.")
197
def get_revision_history(location):
198
tempdir = tempfile.mkdtemp('push')
200
history_fname = os.path.join(tempdir, 'revision-history')
202
cmd = rsync(location+'.bzr/revision-history', history_fname,
205
cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
207
history = read_revision_history(history_fname)
209
shutil.rmtree(tempdir)
212
def history_subset(location, branch):
213
remote_history = get_revision_history(location)
214
local_history = branch.revision_history()
215
if len(remote_history) > len(local_history):
217
for local, remote in zip(remote_history, local_history):
222
def empty_or_absent(location):
224
files = rsync_ls(location)
225
return files == ['.']
229
def rspush(tree, location=None, overwrite=False, working_tree=True):
230
push_location = get_push_data(tree)
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)
231
153
if location is not None:
232
154
if not location.endswith('/'):
234
156
push_location = location
236
158
if push_location is None:
237
raise BzrCommandError("No rspush location known or specified.")
239
if (push_location.find('::') != -1):
244
if (push_location.find('://') != -1 or
245
push_location.find(':') == -1):
246
raise BzrCommandError("Invalid rsync path %r." % push_location)
249
clean, non_source = is_clean(tree)
251
print """Error: This tree has uncommitted changes or unknown (?) files.
252
Use "bzr status" to list them."""
254
final_exclusions = non_source[:]
257
final_exclusions = []
258
for path, status, kind, file_id, entry in wt.list_files():
259
final_exclusions.append(path)
261
final_exclusions.extend(exclusions)
264
if not history_subset(push_location, tree.branch):
265
raise bzrlib.errors.BzrCommandError("Local branch is not a"
266
" newer version of remote"
269
if not empty_or_absent(push_location):
270
raise bzrlib.errors.BzrCommandError("Remote location is not a"
271
" bzr branch (or empty"
273
except RsyncStreamIO:
274
raise bzrlib.errors.BzrCommandError("Rsync could not use the"
275
" specified location. Please ensure that"
276
' "%s" is of the form "machine:/path".' % push_location)
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)
277
169
print "Pushing to %s" % push_location
278
rsync(tree.basedir+'/', push_location, ssh=usessh,
279
excludes=final_exclusions)
281
set_push_data(tree, push_location)
284
def short_committer(committer):
285
new_committer = re.sub('<.*>', '', committer).strip(' ')
286
if len(new_committer) < 2:
292
"""Screen-scrape Apache listings"""
293
apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
296
expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
298
match = expr.search(line)
302
if url.startswith('http://') or url.startswith('/') or '../' in url:
306
yield url.rstrip('/')
309
def iter_branches(t, lister=None):
310
"""Iterate through all the branches under a transport"""
311
for bzrdir in iter_bzrdirs(t, lister):
313
branch = bzrdir.open_branch()
314
if branch.bzrdir is bzrdir:
316
except (NotBranchError, UnsupportedFormatError):
320
def iter_branch_tree(t, lister=None):
321
for bzrdir in iter_bzrdirs(t, lister):
323
wt = bzrdir.open_workingtree()
325
except NoWorkingTree, UnsupportedFormatError:
327
branch = bzrdir.open_branch()
328
if branch.bzrdir is bzrdir:
330
except (NotBranchError, UnsupportedFormatError):
334
def iter_bzrdirs(t, lister=None):
337
return t.list_dir('.')
339
bzrdir = bzrdir_from_transport(t)
341
except (NotBranchError, UnsupportedFormatError, TransportError,
345
for directory in lister(t):
346
if directory == ".bzr":
349
subt = t.clone(directory)
350
except UnicodeDecodeError:
352
for bzrdir in iter_bzrdirs(subt, lister):
354
except (NoSuchFile, PermissionDenied, TransportError):
358
def bzrdir_from_transport(t):
359
"""Open a bzrdir from a transport (not a location)"""
360
format = BzrDirFormat.find_format(t)
361
BzrDir._check_supported(format, False)
362
return format.open(t)
170
rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
172
set_push_data(cur_branch, push_location)