3
This encapsulates the functionality for trying to rsync a local
4
working tree to/from a remote rsync accessible location.
10
_rsync_location = 'x-rsync-data'
11
_parent_locations = ['parent', 'pull', 'x-pull']
15
dirname = tempfile.mkdtemp("temp-branch")
16
return bzrlib.Branch(dirname, init=True)
18
def rm_branch(branch):
20
shutil.rmtree(branch.base)
24
Return true if no files are modifed or unknown
25
>>> br = temp_branch()
28
>>> fooname = os.path.join(br.base, "foo")
29
>>> file(fooname, "wb").write("bar")
32
>>> bzrlib.add.smart_add([fooname])
35
>>> br.commit("added file")
40
old_tree = branch.basis_tree()
41
new_tree = branch.working_tree()
42
for path, file_class, kind, file_id in new_tree.list_files():
45
delta = bzrlib.compare_trees(old_tree, new_tree, want_unchanged=False)
46
if len(delta.added) > 0 or len(delta.removed) > 0 or \
47
len(delta.modified) > 0:
51
def get_default_remote_info(branch):
52
"""Return the value stored in .bzr/x-rsync-location if it exists.
54
>>> br = temp_branch()
55
>>> get_default_remote_info(br)
57
>>> import bzrlib.commit
58
>>> bzrlib.commit.commit(br, 'test commit', rev_id='test-id-12345')
59
>>> set_default_remote_info(br, 'http://somewhere')
60
>>> get_default_remote_info(br)
61
('http://somewhere', 1, 'test-id-12345')
66
def_remote_filename = branch.controlfilename(_rsync_location)
67
if os.path.isfile(def_remote_filename):
68
[def_remote,revno, revision] = [x.strip() for x in open(def_remote_filename).readlines()]
69
return def_remote, int(revno), revision
71
def set_default_remote_info(branch, location):
72
"""Store the location into the .bzr/x-rsync-location.
75
from bzrlib.atomicfile import AtomicFile
76
remote, revno, revision = get_default_remote_info(branch)
77
if (remote == location
78
and revno == branch.revno()
79
and revision == branch.last_patch()):
80
return #Nothing would change, so skip it
81
# TODO: Consider adding to x-pull so that we can try a RemoteBranch
82
# for checking the need to update
83
f = AtomicFile(branch.controlfilename(_rsync_location))
86
f.write(str(branch.revno()))
88
f.write(branch.last_patch())
92
def get_parent_branch(branch):
93
"""Try to get the pull location, in case this directory supports the normal bzr pull.
95
The idea is that we can use RemoteBranch to see if we actually need to do anything,
96
and then we can decide whether to run rsync or not.
100
for fname in _parent_locations:
102
stored_loc = branch.controlfile(fname, 'rb').read().rstrip('\n')
104
if e.errno != errno.ENOENT:
111
from bzrlib.branch import find_branch
112
return find_branch(stored_loc)
115
def get_branch_remote_update(local=None, remote=None, alt_remote=None):
116
from bzrlib.errors import BzrCommandError
117
from bzrlib.branch import find_branch
121
if remote is not None and remote[-1:] != '/':
124
if alt_remote is not None and alt_remote[-1:] != '/':
127
if not os.path.exists(local):
131
raise BzrCommandError('No remote location specified while creating a new local location')
132
return local, remote, 0, None
134
b = find_branch(local)
136
def_remote, last_revno, last_revision = get_default_remote_info(b)
138
if def_remote is None:
139
if alt_remote is None:
140
raise BzrCommandError('No remote location specified, and no default exists.')
146
if remote[-1:] != '/':
149
return b, remote, last_revno, last_revision
151
def check_should_pull(branch, last_revno, last_revision):
152
if isinstance(branch, basestring): # We don't even have a local branch yet
155
if not is_clean(branch):
156
print '** Local tree is not clean. Either has unknown or modified files.'
159
b_parent = get_parent_branch(branch)
160
if b_parent is not None:
161
from bzrlib.branch import DivergedBranches
162
# This may throw a Diverged branches.
164
missing_revisions = branch.missing_revisions(b_parent)
165
except DivergedBranches:
166
print '** Local tree history has diverged from remote.'
167
print '** Not allowing you to overwrite local changes.'
169
if len(missing_revisions) == 0:
170
# There is nothing to do, the remote branch has no changes
171
missing_revisions = b_parent.missing_revisions(branch)
172
if len(missing_revisions) > 0:
173
print '** Local tree is up-to-date with remote.'
174
print '** But remote tree is missing local revisions.'
175
print '** Consider using bzr rsync-push'
177
print '** Both trees fully up-to-date.'
179
# We are sure that we are missing remote revisions
182
if last_revno == branch.revno() and last_revision == branch.last_patch():
183
# We can go ahead and try
186
print 'Local working directory has a different revision than last rsync.'
187
val = raw_input('Are you sure you want to download [y/N]? ')
188
if val.lower() in ('y', 'yes'):
192
def check_should_push(branch, last_revno, last_revision):
193
if not is_clean(branch):
194
print '** Local tree is not clean (either modified or unknown files)'
197
b_parent = get_parent_branch(branch)
198
if b_parent is not None:
199
from bzrlib.branch import DivergedBranches
200
# This may throw a Diverged branches.
202
missing_revisions = b_parent.missing_revisions(branch)
203
except DivergedBranches:
204
print '** Local tree history has diverged from remote.'
205
print '** Not allowing you to overwrite remote changes.'
207
if len(missing_revisions) == 0:
208
# There is nothing to do, the remote branch is up to date
209
missing_revisions = branch.missing_revisions(b_parent)
210
if len(missing_revisions) > 0:
211
print '** Remote tree is up-to-date with local.'
212
print '** But local tree is missing remote revisions.'
213
print '** Consider using bzr rsync-pull'
215
print '** Both trees fully up-to-date.'
217
# We are sure that we are missing remote revisions
220
if last_revno is None and last_revision is None:
221
print 'Local tree does not have a valid last rsync revision.'
222
val = raw_input('push anyway [y/N]? ')
223
if val.lower() in ('y', 'yes'):
227
if last_revno == branch.revno() and last_revision == branch.last_patch():
228
print 'No new revisions.'
234
def pull(branch, remote, verbose=False, dry_run=False):
235
"""Update the local repository from the location specified by 'remote'
237
:param branch: Either a string specifying a local path, or a Branch object.
238
If a local path, the download will be performed, and then
239
a Branch object will be created.
241
:return: Return the branch object that was created
243
if isinstance(branch, basestring):
248
cur_revno = branch.revno()
249
if remote[-1:] != '/':
252
rsyncopts = ['-rltp', '--delete'
253
# Don't pull in a new parent location
254
, "--exclude '**/.bzr/x-rsync*'", "--exclude '**/.bzr/x-pull*'"
255
, "--exclude '**/.bzr/parent'", "--exclude '**/.bzr/pull'"
258
# Note that when pulling, we do not delete excluded files
259
rsync_exclude = os.path.join(local, '.rsyncexclude')
260
if os.path.exists(rsync_exclude):
261
rsyncopts.append('--exclude-from "%s"' % rsync_exclude)
262
bzr_ignore = os.path.join(local, '.bzrignore')
263
if os.path.exists(bzr_ignore):
264
rsyncopts.append('--exclude-from "%s"' % bzr_ignore)
267
rsyncopts.append('-v')
269
rsyncopts.append('--dry-run')
271
cmd = 'rsync %s "%s" "%s"' % (' '.join(rsyncopts), remote, local)
275
status = os.system(cmd)
277
from bzrlib.errors import BzrError
278
raise BzrError('Rsync failed with error code: %s' % status)
281
if isinstance(branch, basestring):
282
from bzrlib.branch import Branch
283
branch = Branch(branch)
285
new_revno = branch.revno()
286
if cur_revno == new_revno:
287
print '** tree is up-to-date'
290
if cur_revno != new_revno:
291
from bzrlib.log import show_log
292
show_log(branch, direction='forward',
293
start_revision=cur_revno+1, end_revision=new_revno)
298
def push(branch, remote, verbose=False, dry_run=False):
299
"""Update the local repository from the location specified by 'remote'
301
:param branch: Should always be a Branch object
303
if isinstance(branch, basestring):
304
from bzrlib.errors import BzrError
305
raise BzrError('rsync push requires a Branch object, not a string')
307
if remote[-1:] != '/':
310
rsyncopts = ['-rltp', '--include-from -'
312
# We don't want to push our local meta information to the remote
313
, "--exclude '.bzr/x-rsync*'", "--exclude '.bzr/x-pull*'"
314
, "--exclude '.bzr/parent'", "--exclude '.bzr/pull'"
315
, "--include '.bzr/**'"
316
, "--exclude '*'", "--exclude '.*'"
317
, '--delete', '--delete-excluded'
320
rsync_exclude = os.path.join(local, '.rsyncexclude')
321
if os.path.exists(rsync_exclude):
322
rsyncopts.append('--exclude-from "%s"' % rsync_exclude)
323
bzr_ignore = os.path.join(local, '.bzrignore')
324
if os.path.exists(bzr_ignore):
325
rsyncopts.append('--exclude-from "%s"' % bzr_ignore)
328
rsyncopts.append('-v')
330
rsyncopts.append('--dry-run')
332
cmd = 'rsync %s "." "%s"' % (' '.join(rsyncopts), remote)
339
child = os.popen(cmd, 'w')
340
inv = branch.read_working_inventory()
341
for path, entry in inv.entries():
345
retval = child.close()
346
if retval is not None:
347
from bzrlib.errors import BzrError
348
raise BzrError('Rsync failed with error code: %s' % retval)