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
>>> 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", rev_id='commit-id')
61
from bzrlib.diff import compare_trees
62
old_tree = cur_tree.basis_tree()
65
for path, file_class, kind, file_id, entry in new_tree.list_files():
66
if file_class in ('?', 'I'):
67
non_source.append(path)
68
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
79
>>> set_push_data(tree, 'http://somewhere')
80
>>> get_push_data(tree)
85
location = tree.branch.control_files.get_utf8('x-push-data').read()
88
return location.rstrip('\n')
91
>>> shell_escape('hello')
94
def shell_escape(arg):
95
return "".join(['\\'+c for c in arg])
97
def safe_system(args):
99
>>> real_system = os.system
100
>>> os.system = sys.stdout.write
101
>>> safe_system(['a', 'b', 'cd'])
103
>>> os.system = real_system
105
arg_str = " ".join([shell_escape(a) for a in args])
106
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"]
136
cmd.extend(('-e', 'ssh'))
137
if len(excludes) > 0:
138
cmd.extend(('--exclude-from', '-'))
139
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)
152
proc.stdin.write('\n'.join(excludes)+'\n')
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)
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."""
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)
245
if location is not None:
246
if not location.endswith('/'):
248
push_location = location
250
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)
286
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
40
def short_committer(committer):
315
67
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):
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)
329
100
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)
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)