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
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()
64
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():
65
58
if file_class in ('?', 'I'):
66
59
non_source.append(path)
67
delta = new_tree.changes_from(old_tree, want_unchanged=False)
68
return not delta.has_changed(), non_source
70
def set_push_data(tree, location):
71
tree.branch.control_files.put_utf8("x-push-data", "%s\n" % location)
73
def get_push_data(tree):
75
>>> tree = temp_tree()
76
>>> get_push_data(tree) is None
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
78
>>> set_push_data(tree, 'http://somewhere')
79
>>> get_push_data(tree)
96
>>> set_push_data(br, 'http://somewhere')
84
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):
87
return location.rstrip('\n')
104
push_file = file (filename, "rb")
105
(location,) = [f.rstrip('\n') for f in push_file]
90
109
>>> shell_escape('hello')
104
123
arg_str = " ".join([shell_escape(a) for a in args])
105
124
return os.system(arg_str)
107
class RsyncUnknownStatus(Exception):
108
def __init__(self, status):
109
Exception.__init__(self, "Unknown status: %d" % status)
111
class NoRsync(Exception):
112
def __init__(self, rsync_name):
113
Exception.__init__(self, "%s not found." % rsync_name)
115
def rsync(source, target, ssh=False, excludes=(), silent=False,
118
>>> new_dir = tempfile.mkdtemp()
119
>>> old_dir = os.getcwd()
120
>>> os.chdir(new_dir)
121
>>> rsync("a", "b", silent=True)
122
Traceback (most recent call last):
123
RsyncNoFile: No such file...
124
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
125
Traceback (most recent call last):
126
RsyncNoFile: No such file...
127
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
128
Traceback (most recent call last):
129
NoRsync: rsyncc not found.
130
>>> os.chdir(old_dir)
131
>>> os.rmdir(new_dir)
133
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"]
135
138
cmd.extend(('-e', 'ssh'))
136
139
if len(excludes) > 0:
137
140
cmd.extend(('--exclude-from', '-'))
138
141
cmd.extend((source, target))
146
proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
148
if e.errno == errno.ENOENT:
149
raise NoRsync(rsync_name)
142
proc = Popen(cmd, stdin=PIPE)
151
143
proc.stdin.write('\n'.join(excludes)+'\n')
152
144
proc.stdin.close()
159
if proc.returncode == 12:
160
raise RsyncStreamIO()
161
elif proc.returncode == 23:
162
raise RsyncNoFile(source)
163
elif proc.returncode != 0:
164
raise RsyncUnknownStatus(proc.returncode)
168
def rsync_ls(source, ssh=False, silent=True):
171
cmd.extend(('-e', 'ssh'))
177
proc = Popen(cmd, stderr=stderr, stdout=PIPE)
178
result = proc.stdout.read()
184
if proc.returncode == 12:
185
raise RsyncStreamIO()
186
elif proc.returncode == 23:
187
raise RsyncNoFile(source)
188
elif proc.returncode != 0:
189
raise RsyncUnknownStatus(proc.returncode)
190
return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
192
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
193
'.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
194
'.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
195
'.bzr/basis-inventory', '.bzr/inventory.backup.weave')
198
def read_revision_history(fname):
199
return [l.rstrip('\r\n') for l in
200
codecs.open(fname, 'rb', 'utf-8').readlines()]
202
class RsyncNoFile(Exception):
203
def __init__(self, path):
204
Exception.__init__(self, "No such file %s" % path)
206
class RsyncStreamIO(Exception):
208
Exception.__init__(self, "Error in rsync protocol data stream.")
210
def get_revision_history(location):
211
tempdir = tempfile.mkdtemp('push')
213
history_fname = os.path.join(tempdir, 'revision-history')
215
cmd = rsync(location+'.bzr/revision-history', history_fname,
218
cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
220
history = read_revision_history(history_fname)
222
shutil.rmtree(tempdir)
225
def history_subset(location, branch):
226
remote_history = get_revision_history(location)
227
local_history = branch.revision_history()
228
if len(remote_history) > len(local_history):
230
for local, remote in zip(remote_history, local_history):
235
def empty_or_absent(location):
237
files = rsync_ls(location)
238
return files == ['.']
242
def rspush(tree, location=None, overwrite=False, working_tree=True):
243
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)
244
153
if location is not None:
245
154
if not location.endswith('/'):
247
156
push_location = location
249
158
if push_location is None:
250
raise BzrCommandError("No rspush location known or specified.")
252
if (push_location.find('://') != -1 or
253
push_location.find(':') == -1):
254
raise BzrCommandError("Invalid rsync path %r." % push_location)
257
clean, non_source = is_clean(tree)
259
print """Error: This tree has uncommitted changes or unknown (?) files.
260
Use "bzr status" to list them."""
262
final_exclusions = non_source[:]
265
final_exclusions = []
266
for path, status, kind, file_id, entry in wt.list_files():
267
final_exclusions.append(path)
269
final_exclusions.extend(exclusions)
272
if not history_subset(push_location, tree.branch):
273
raise bzrlib.errors.BzrCommandError("Local branch is not a"
274
" newer version of remote"
277
if not empty_or_absent(push_location):
278
raise bzrlib.errors.BzrCommandError("Remote location is not a"
279
" bzr branch (or empty"
281
except RsyncStreamIO:
282
raise bzrlib.errors.BzrCommandError("Rsync could not use the"
283
" specified location. Please ensure that"
284
' "%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)
285
169
print "Pushing to %s" % push_location
286
rsync(tree.basedir+'/', push_location, ssh=True,
287
excludes=final_exclusions)
289
set_push_data(tree, push_location)
292
def short_committer(committer):
293
new_committer = re.sub('<.*>', '', committer).strip(' ')
294
if len(new_committer) < 2:
300
"""Screen-scrape Apache listings"""
301
apache_dir = '<img border="0" src="/icons/folder.gif" alt="[dir]">'\
304
expr = re.compile('<a[^>]*href="([^>]*)"[^>]*>', flags=re.I)
306
match = expr.search(line)
310
if url.startswith('http://') or url.startswith('/') or '../' in url:
314
yield url.rstrip('/')
317
def iter_branches(t, lister=None):
318
"""Iterate through all the branches under a transport"""
319
for bzrdir in iter_bzrdirs(t, lister):
321
branch = bzrdir.open_branch()
322
if branch.bzrdir is bzrdir:
324
except (NotBranchError, UnsupportedFormatError):
328
def iter_branch_tree(t, lister=None):
329
for bzrdir in iter_bzrdirs(t, lister):
331
wt = bzrdir.open_workingtree()
333
except NoWorkingTree, UnsupportedFormatError:
335
branch = bzrdir.open_branch()
336
if branch.bzrdir is bzrdir:
338
except (NotBranchError, UnsupportedFormatError):
342
def iter_bzrdirs(t, lister=None):
345
return t.list_dir('.')
347
bzrdir = bzrdir_from_transport(t)
349
except (NotBranchError, UnsupportedFormatError, TransportError,
353
for directory in lister(t):
354
if directory == ".bzr":
357
subt = t.clone(directory)
358
except UnicodeDecodeError:
360
for bzrdir in iter_bzrdirs(subt, lister):
362
except (NoSuchFile, PermissionDenied, TransportError):
366
def bzrdir_from_transport(t):
367
"""Open a bzrdir from a transport (not a location)"""
368
format = BzrDirFormat.find_format(t)
369
BzrDir._check_supported(format, False)
370
return format.open(t)
170
rsync(cur_branch.base+'/', push_location, ssh=True, excludes=non_source)
172
set_push_data(cur_branch, push_location)