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
43
35
>>> import bzrlib.add
44
>>> tree = temp_tree()
36
>>> br = temp_branch()
47
>>> fooname = os.path.join(tree.basedir, "foo")
39
>>> fooname = os.path.join(br.base, "foo")
48
40
>>> file(fooname, "wb").write("bar")
51
>>> bzrlib.add.smart_add_tree(tree, [tree.basedir])
43
>>> bzrlib.add.smart_add_branch(br, [br.base])
55
>>> tree.commit("added file", rev_id='commit-id')
47
>>> br.commit("added file")
61
53
from bzrlib.diff import compare_trees
62
old_tree = cur_tree.basis_tree()
54
old_tree = cur_branch.basis_tree()
55
new_tree = cur_branch.working_tree()
65
for path, file_class, kind, file_id, entry in new_tree.list_files():
57
for path, file_class, kind, file_id in new_tree.list_files():
66
58
if file_class in ('?', 'I'):
67
59
non_source.append(path)
68
60
delta = compare_trees(old_tree, new_tree, want_unchanged=False)
69
return not delta.has_changed(), non_source
71
def set_push_data(tree, location):
72
tree.branch.control_files.put_utf8("x-push-data", "%s\n" % location)
74
def get_push_data(tree):
76
>>> tree = temp_tree()
77
>>> get_push_data(tree) is None
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
79
>>> set_push_data(tree, 'http://somewhere')
80
>>> get_push_data(tree)
96
>>> set_push_data(br, 'http://somewhere')
85
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):
88
return location.rstrip('\n')
104
push_file = file (filename, "rb")
105
(location,) = [f.rstrip('\n') for f in push_file]
91
109
>>> shell_escape('hello')
105
123
arg_str = " ".join([shell_escape(a) for a in args])
106
124
return os.system(arg_str)
108
class RsyncUnknownStatus(Exception):
109
def __init__(self, status):
110
Exception.__init__(self, "Unknown status: %d" % status)
112
class NoRsync(Exception):
113
def __init__(self, rsync_name):
114
Exception.__init__(self, "%s not found." % rsync_name)
116
def rsync(source, target, ssh=False, excludes=(), silent=False,
119
>>> new_dir = tempfile.mkdtemp()
120
>>> old_dir = os.getcwd()
121
>>> os.chdir(new_dir)
122
>>> rsync("a", "b", 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)
126
Traceback (most recent call last):
127
RsyncNoFile: No such file...
128
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
129
Traceback (most recent call last):
130
NoRsync: rsyncc not found.
131
>>> os.chdir(old_dir)
132
>>> os.rmdir(new_dir)
134
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"]
136
138
cmd.extend(('-e', 'ssh'))
137
139
if len(excludes) > 0:
138
140
cmd.extend(('--exclude-from', '-'))
139
141
cmd.extend((source, target))
147
proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
149
if e.errno == errno.ENOENT:
150
raise NoRsync(rsync_name)
142
proc = Popen(cmd, stdin=PIPE)
152
143
proc.stdin.write('\n'.join(excludes)+'\n')
153
144
proc.stdin.close()
160
if proc.returncode == 12:
161
raise RsyncStreamIO()
162
elif proc.returncode == 23:
163
raise RsyncNoFile(source)
164
elif proc.returncode != 0:
165
raise RsyncUnknownStatus(proc.returncode)
169
def rsync_ls(source, ssh=False, silent=True):
172
cmd.extend(('-e', 'ssh'))
178
proc = Popen(cmd, stderr=stderr, stdout=PIPE)
179
result = proc.stdout.read()
185
if proc.returncode == 12:
186
raise RsyncStreamIO()
187
elif proc.returncode == 23:
188
raise RsyncNoFile(source)
189
elif proc.returncode != 0:
190
raise RsyncUnknownStatus(proc.returncode)
191
return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
193
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
194
'.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
195
'.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
196
'.bzr/basis-inventory', '.bzr/inventory.backup.weave')
199
def read_revision_history(fname):
200
return [l.rstrip('\r\n') for l in
201
codecs.open(fname, 'rb', 'utf-8').readlines()]
203
class RsyncNoFile(Exception):
204
def __init__(self, path):
205
Exception.__init__(self, "No such file %s" % path)
207
class RsyncStreamIO(Exception):
209
Exception.__init__(self, "Error in rsync protocol data stream.")
211
def get_revision_history(location):
212
tempdir = tempfile.mkdtemp('push')
214
history_fname = os.path.join(tempdir, 'revision-history')
216
cmd = rsync(location+'.bzr/revision-history', history_fname,
219
cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
221
history = read_revision_history(history_fname)
223
shutil.rmtree(tempdir)
226
def history_subset(location, branch):
227
remote_history = get_revision_history(location)
228
local_history = branch.revision_history()
229
if len(remote_history) > len(local_history):
231
for local, remote in zip(remote_history, local_history):
236
def empty_or_absent(location):
238
files = rsync_ls(location)
239
return files == ['.']
243
def rspush(tree, location=None, overwrite=False, working_tree=True):
244
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)
245
153
if location is not None:
246
154
if not location.endswith('/'):
248
156
push_location = location
250
158
if push_location is None:
251
raise BzrCommandError("No rspush location known or specified.")
253
if (push_location.find('://') != -1 or
254
push_location.find(':') == -1):
255
raise BzrCommandError("Invalid rsync path %r." % push_location)
258
clean, non_source = is_clean(tree)
260
print """Error: This tree has uncommitted changes or unknown (?) files.
261
Use "bzr status" to list them."""
263
final_exclusions = non_source[:]
266
final_exclusions = []
267
for path, status, kind, file_id, entry in wt.list_files():
268
final_exclusions.append(path)
270
final_exclusions.extend(exclusions)
273
if not history_subset(push_location, tree.branch):
274
raise bzrlib.errors.BzrCommandError("Local branch is not a"
275
" newer version of remote"
278
if not empty_or_absent(push_location):
279
raise bzrlib.errors.BzrCommandError("Remote location is not a"
280
" bzr branch (or empty"
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)
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)
286
169
print "Pushing to %s" % push_location
287
rsync(tree.basedir+'/', push_location, ssh=True,
288
excludes=final_exclusions)
290
set_push_data(tree, push_location)
293
def short_committer(committer):
294
new_committer = re.sub('<.*>', '', committer).strip(' ')
295
if len(new_committer) < 2:
301
"""Screen-scrape Apache listings"""
302
apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
305
expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
307
match = expr.search(line)
311
if url.startswith('http://') or url.startswith('/') or '../' in url:
315
yield url.rstrip('/')
318
def iter_branches(t, lister=None):
319
"""Iterate through all the branches under a transport"""
320
for bzrdir in iter_bzrdirs(t, lister):
322
branch = bzrdir.open_branch()
323
if branch.bzrdir is bzrdir:
325
except (NotBranchError, UnsupportedFormatError):
329
def iter_branch_tree(t, lister=None):
330
for bzrdir in iter_bzrdirs(t, lister):
332
wt = bzrdir.open_workingtree()
334
except NoWorkingTree, UnsupportedFormatError:
336
branch = bzrdir.open_branch()
337
if branch.bzrdir is bzrdir:
339
except (NotBranchError, UnsupportedFormatError):
343
def iter_bzrdirs(t, lister=None):
346
return t.list_dir('.')
348
bzrdir = bzrdir_from_transport(t)
350
except (NotBranchError, UnsupportedFormatError, TransportError,
354
for directory in lister(t):
355
if directory == ".bzr":
358
subt = t.clone(directory)
359
except UnicodeDecodeError:
361
for bzrdir in iter_bzrdirs(subt, lister):
363
except (NoSuchFile, PermissionDenied, TransportError):
367
def bzrdir_from_transport(t):
368
"""Open a bzrdir from a transport (not a location)"""
369
format = BzrDirFormat.find_format(t)
370
BzrDir._check_supported(format, False)
371
return format.open(t)
170
rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
172
set_push_data(cur_branch, push_location)