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
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."""
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
>>> tree = temp_tree()
47
>>> fooname = os.path.join(tree.basedir, "foo")
48
>>> file(fooname, "wb").write("bar")
51
>>> bzrlib.add.smart_add_tree(tree, [tree.basedir])
55
>>> tree.commit("added file")
60
from bzrlib.diff import compare_trees
61
old_tree = cur_tree.basis_tree()
64
for path, file_class, kind, file_id, entry in new_tree.list_files():
65
if file_class in ('?', 'I'):
66
non_source.append(path)
67
delta = compare_trees(old_tree, new_tree, want_unchanged=False)
68
return not delta.has_changed(), non_source
70
def set_push_data(tree, location):
71
push_file = file (tree.branch.control_files.controlfilename("x-push-data"), "wb")
72
push_file.write("%s\n" % location)
74
def get_push_data(tree):
76
>>> tree = temp_tree()
77
>>> get_push_data(tree) is None
79
>>> set_push_data(tree, 'http://somewhere')
80
>>> get_push_data(tree)
84
filename = tree.branch.control_files.controlfilename("x-push-data")
85
if not os.path.exists(filename):
87
push_file = file (filename, "rb")
88
(location,) = [f.rstrip('\n') for f in push_file]
92
>>> shell_escape('hello')
95
def shell_escape(arg):
96
return "".join(['\\'+c for c in arg])
98
def safe_system(args):
100
>>> real_system = os.system
101
>>> os.system = sys.stdout.write
102
>>> safe_system(['a', 'b', 'cd'])
104
>>> os.system = real_system
106
arg_str = " ".join([shell_escape(a) for a in args])
107
return os.system(arg_str)
109
class RsyncUnknownStatus(Exception):
110
def __init__(self, status):
111
Exception.__init__(self, "Unknown status: %d" % status)
113
class NoRsync(Exception):
114
def __init__(self, rsync_name):
115
Exception.__init__(self, "%s not found." % rsync_name)
117
def rsync(source, target, ssh=False, excludes=(), silent=False,
120
>>> new_dir = tempfile.mkdtemp()
121
>>> old_dir = os.getcwd()
122
>>> os.chdir(new_dir)
123
>>> rsync("a", "b", silent=True)
124
Traceback (most recent call last):
125
RsyncNoFile: No such file...
126
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True)
127
Traceback (most recent call last):
128
RsyncNoFile: No such file...
129
>>> rsync(new_dir + "/a", new_dir + "/b", excludes=("*.py",), silent=True, rsync_name="rsyncc")
130
Traceback (most recent call last):
131
NoRsync: rsyncc not found.
132
>>> os.chdir(old_dir)
133
>>> os.rmdir(new_dir)
135
cmd = [rsync_name, "-av", "--delete"]
137
cmd.extend(('-e', 'ssh'))
138
if len(excludes) > 0:
139
cmd.extend(('--exclude-from', '-'))
140
cmd.extend((source, target))
148
proc = Popen(cmd, stdin=PIPE, stderr=stderr, stdout=stdout)
150
if e.errno == errno.ENOENT:
151
raise NoRsync(rsync_name)
153
proc.stdin.write('\n'.join(excludes)+'\n')
161
if proc.returncode == 12:
162
raise RsyncStreamIO()
163
elif proc.returncode == 23:
164
raise RsyncNoFile(source)
165
elif proc.returncode != 0:
166
raise RsyncUnknownStatus(proc.returncode)
170
def rsync_ls(source, ssh=False, silent=True):
173
cmd.extend(('-e', 'ssh'))
179
proc = Popen(cmd, stderr=stderr, stdout=PIPE)
180
result = proc.stdout.read()
186
if proc.returncode == 12:
187
raise RsyncStreamIO()
188
elif proc.returncode == 23:
189
raise RsyncNoFile(source)
190
elif proc.returncode != 0:
191
raise RsyncUnknownStatus(proc.returncode)
192
return [l.split(' ')[-1].rstrip('\n') for l in result.splitlines(True)]
194
exclusions = ('.bzr/x-push-data', '.bzr/branch/x-push/data', '.bzr/parent',
195
'.bzr/branch/parent', '.bzr/x-pull-data', '.bzr/x-pull',
196
'.bzr/pull', '.bzr/stat-cache', '.bzr/x-rsync-data',
197
'.bzr/basis-inventory', '.bzr/inventory.backup.weave')
200
def read_revision_history(fname):
201
return [l.rstrip('\r\n') for l in
202
codecs.open(fname, 'rb', 'utf-8').readlines()]
204
class RsyncNoFile(Exception):
205
def __init__(self, path):
206
Exception.__init__(self, "No such file %s" % path)
208
class RsyncStreamIO(Exception):
210
Exception.__init__(self, "Error in rsync protocol data stream.")
212
def get_revision_history(location):
213
tempdir = tempfile.mkdtemp('push')
215
history_fname = os.path.join(tempdir, 'revision-history')
217
cmd = rsync(location+'.bzr/revision-history', history_fname,
220
cmd = rsync(location+'.bzr/branch/revision-history', history_fname,
222
history = read_revision_history(history_fname)
224
shutil.rmtree(tempdir)
227
def history_subset(location, branch):
228
remote_history = get_revision_history(location)
229
local_history = branch.revision_history()
230
if len(remote_history) > len(local_history):
232
for local, remote in zip(remote_history, local_history):
237
def empty_or_absent(location):
239
files = rsync_ls(location)
240
return files == ['.']
244
def rspush(tree, location=None, overwrite=False, working_tree=True):
245
push_location = get_push_data(tree)
246
if location is not None:
247
if not location.endswith('/'):
249
push_location = location
251
if push_location is None:
252
raise BzrCommandError("No rspush location known or specified.")
254
if (push_location.find('://') != -1 or
255
push_location.find(':') == -1):
256
raise BzrCommandError("Invalid rsync path %r." % push_location)
259
clean, non_source = is_clean(tree)
261
print """Error: This tree has uncommitted changes or unknown (?) files.
262
Use "bzr status" to list them."""
264
final_exclusions = non_source[:]
267
final_exclusions = []
268
for path, status, kind, file_id, entry in wt.list_files():
269
final_exclusions.append(path)
271
final_exclusions.extend(exclusions)
274
if not history_subset(push_location, tree.branch):
275
raise bzrlib.errors.BzrCommandError("Local branch is not a"
276
" newer version of remote"
279
if not empty_or_absent(push_location):
280
raise bzrlib.errors.BzrCommandError("Remote location is not a"
281
" bzr branch (or empty"
283
except RsyncStreamIO:
284
raise bzrlib.errors.BzrCommandError("Rsync could not use the"
285
" specified location. Please ensure that"
286
' "%s" is of the form "machine:/path".' % push_location)
287
print "Pushing to %s" % push_location
288
rsync(tree.basedir+'/', push_location, ssh=True,
289
excludes=final_exclusions)
291
set_push_data(tree, push_location)
40
294
def short_committer(committer):
67
316
yield url.rstrip('/')
71
def is_inside(branch):
72
return bool(branch.base.startswith(t.base))
74
if t.base.startswith('http://'):
319
def iter_branches(t, lister=None):
320
"""Iterate through all the branches under a transport"""
321
for bzrdir in iter_bzrdirs(t, lister):
323
branch = bzrdir.open_branch()
324
if branch.bzrdir is bzrdir:
326
except (NotBranchError, UnsupportedFormatError):
330
def iter_branch_tree(t, lister=None):
331
for bzrdir in iter_bzrdirs(t, lister):
333
wt = bzrdir.open_workingtree()
335
except NoWorkingTree, UnsupportedFormatError:
77
337
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)
100
def iter_branch_tree(t, lister=None):
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)
338
if branch.bzrdir is bzrdir:
340
except (NotBranchError, UnsupportedFormatError):
344
def iter_bzrdirs(t, lister=None):
347
return t.list_dir('.')
349
bzrdir = bzrdir_from_transport(t)
351
except (NotBranchError, UnsupportedFormatError, TransportError,
355
for directory in lister(t):
356
if directory == ".bzr":
359
subt = t.clone(directory)
360
except UnicodeDecodeError:
362
for bzrdir in iter_bzrdirs(subt, lister):
364
except (NoSuchFile, PermissionDenied, TransportError):
368
def bzrdir_from_transport(t):
369
"""Open a bzrdir from a transport (not a location)"""
370
format = BzrDirFormat.find_format(t)
371
BzrDir._check_supported(format, False)
372
return format.open(t)