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
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 = 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
78
>>> set_push_data(tree, 'http://somewhere')
79
>>> get_push_data(tree)
84
location = tree.branch.control_files.get_utf8('x-push-data').read()
87
return location.rstrip('\n')
90
>>> shell_escape('hello')
93
def shell_escape(arg):
94
return "".join(['\\'+c for c in arg])
96
def safe_system(args):
98
>>> real_system = os.system
99
>>> os.system = sys.stdout.write
100
>>> safe_system(['a', 'b', 'cd'])
102
>>> os.system = real_system
104
arg_str = " ".join([shell_escape(a) for a in args])
105
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"]
135
cmd.extend(('-e', 'ssh'))
136
if len(excludes) > 0:
137
cmd.extend(('--exclude-from', '-'))
138
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)
151
proc.stdin.write('\n'.join(excludes)+'\n')
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)
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."""
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)
244
if location is not None:
245
if not location.endswith('/'):
247
push_location = location
249
if push_location is None:
250
raise BzrCommandError("No rspush location known or specified.")
252
if (push_location.find('::') != -1):
257
if (push_location.find('://') != -1 or
258
push_location.find(':') == -1):
259
raise BzrCommandError("Invalid rsync path %r." % push_location)
262
clean, non_source = is_clean(tree)
264
print """Error: This tree has uncommitted changes or unknown (?) files.
265
Use "bzr status" to list them."""
267
final_exclusions = non_source[:]
270
final_exclusions = []
271
for path, status, kind, file_id, entry in wt.list_files():
272
final_exclusions.append(path)
274
final_exclusions.extend(exclusions)
277
if not history_subset(push_location, tree.branch):
278
raise bzrlib.errors.BzrCommandError("Local branch is not a"
279
" newer version of remote"
282
if not empty_or_absent(push_location):
283
raise bzrlib.errors.BzrCommandError("Remote location is not a"
284
" bzr branch (or empty"
286
except RsyncStreamIO:
287
raise bzrlib.errors.BzrCommandError("Rsync could not use the"
288
" specified location. Please ensure that"
289
' "%s" is of the form "machine:/path".' % push_location)
290
print "Pushing to %s" % push_location
291
rsync(tree.basedir+'/', push_location, ssh=usessh,
292
excludes=final_exclusions)
294
set_push_data(tree, push_location)
297
40
def short_committer(committer):
319
67
yield url.rstrip('/')
322
def iter_branches(t, lister=None):
323
"""Iterate through all the branches under a transport"""
324
for bzrdir in iter_bzrdirs(t, lister):
326
branch = bzrdir.open_branch()
327
if branch.bzrdir is bzrdir:
329
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)
333
100
def iter_branch_tree(t, lister=None):
334
for bzrdir in iter_bzrdirs(t, lister):
336
wt = bzrdir.open_workingtree()
338
except NoWorkingTree, UnsupportedFormatError:
340
branch = bzrdir.open_branch()
341
if branch.bzrdir is bzrdir:
343
except (NotBranchError, UnsupportedFormatError):
347
def iter_bzrdirs(t, lister=None):
350
return t.list_dir('.')
352
bzrdir = bzrdir_from_transport(t)
354
except (NotBranchError, UnsupportedFormatError, TransportError,
358
for directory in lister(t):
359
if directory == ".bzr":
362
subt = t.clone(directory)
363
except UnicodeDecodeError:
365
for bzrdir in iter_bzrdirs(subt, lister):
367
except (NoSuchFile, PermissionDenied, TransportError):
371
def bzrdir_from_transport(t):
372
"""Open a bzrdir from a transport (not a location)"""
373
format = BzrDirFormat.find_format(t)
374
BzrDir._check_supported(format, False)
375
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)