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
17
from contextlib import contextmanager
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
dirname = tempfile.mkdtemp("temp-branch")
35
return BzrDir.create_standalone_workingtree(dirname)
38
shutil.rmtree(tree.basedir)
40
def is_clean(cur_tree):
42
Return true if no files are modifed or unknown
44
old_tree = cur_tree.basis_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
65
>>> set_push_data(tree, 'http://somewhere')
66
>>> get_push_data(tree)
71
location = tree.branch.control_files.get_utf8('x-push-data').read()
74
return location.rstrip('\n')
77
>>> shell_escape('hello')
80
def shell_escape(arg):
81
return "".join(['\\'+c for c in arg])
83
def safe_system(args):
85
>>> real_system = os.system
86
>>> os.system = sys.stdout.write
87
>>> safe_system(['a', 'b', 'cd'])
89
>>> os.system = real_system
91
arg_str = " ".join([shell_escape(a) for a in args])
92
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"]
122
cmd.extend(('-e', 'ssh'))
123
if len(excludes) > 0:
124
cmd.extend(('--exclude-from', '-'))
125
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)
138
proc.stdin.write('\n'.join(excludes)+'\n')
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)
231
if location is not None:
232
if not location.endswith('/'):
234
push_location = location
236
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)
277
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)
20
from bzrlib import urlutils
21
from bzrlib.errors import (
26
from bzrlib.bzrdir import BzrDir
27
from bzrlib.transport import get_transport
31
def read_locked(lockable):
32
"""Read-lock a tree, branch or repository in this context."""
284
40
def short_committer(committer):
306
67
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):
71
def is_inside(branch):
72
return bool(branch.base.startswith(t.base))
74
if t.base.startswith('http://'):
77
branch = bzrdir.open_branch()
82
except NotBranchError:
84
return [b for b in BzrDir.find_bzrdirs(t, list_current=apache_ls,
85
evaluate=evaluate) if b is not None]
86
elif not t.listable():
87
raise BzrCommandError("Can't list this type of location.")
88
return [b for b in BzrDir.find_branches(t) if is_inside(b)]
91
def evaluate_branch_tree(bzrdir):
93
tree, branch = bzrdir._get_tree_branch()
94
except NotBranchError:
97
return True, (branch, tree)
320
100
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)
101
return (x for x in BzrDir.find_bzrdirs(t, evaluate=evaluate_branch_tree,
102
list_current=lister) if x is not None)
105
def open_from_url(location):
106
location = urlutils.normalize_url(location)
107
dirname, basename = urlutils.split(location)
108
if location.endswith('/') and not basename.endswith('/'):
110
return get_transport(dirname).get(basename)