1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
|
#!/usr/bin/env python
"""\
This encapsulates the functionality for trying to rsync a local
working tree to/from a remote rsync accessible location.
"""
import os
import bzrlib
_rsync_location = 'x-rsync-data'
_parent_locations = ['parent', 'pull', 'x-pull']
def temp_branch():
import tempfile
dirname = tempfile.mkdtemp("temp-branch")
return bzrlib.Branch(dirname, init=True)
def rm_branch(branch):
import shutil
shutil.rmtree(branch.base)
def is_clean(branch):
"""
Return true if no files are modifed or unknown
>>> br = temp_branch()
>>> is_clean(br)
True
>>> fooname = os.path.join(br.base, "foo")
>>> file(fooname, "wb").write("bar")
>>> is_clean(br)
False
>>> bzrlib.add.smart_add([fooname])
>>> is_clean(br)
False
>>> br.commit("added file")
>>> is_clean(br)
True
>>> rm_branch(br)
"""
old_tree = branch.basis_tree()
new_tree = branch.working_tree()
for path, file_class, kind, file_id in new_tree.list_files():
if file_class == '?':
return False
delta = bzrlib.compare_trees(old_tree, new_tree, want_unchanged=False)
if len(delta.added) > 0 or len(delta.removed) > 0 or \
len(delta.modified) > 0:
return False
return True
def get_default_remote_info(branch):
"""Return the value stored in .bzr/x-rsync-location if it exists.
>>> br = temp_branch()
>>> get_default_remote_info(br)
(None, 0, None)
>>> import bzrlib.commit
>>> bzrlib.commit.commit(br, 'test commit', rev_id='test-id-12345')
>>> set_default_remote_info(br, 'http://somewhere')
>>> get_default_remote_info(br)
('http://somewhere', 1, 'test-id-12345')
"""
def_remote = None
revno = 0
revision = None
def_remote_filename = branch.controlfilename(_rsync_location)
if os.path.isfile(def_remote_filename):
[def_remote,revno, revision] = [x.strip() for x in open(def_remote_filename).readlines()]
return def_remote, int(revno), revision
def set_default_remote_info(branch, location):
"""Store the location into the .bzr/x-rsync-location.
"""
from bzrlib.atomicfile import AtomicFile
remote, revno, revision = get_default_remote_info(branch)
if (remote == location
and revno == branch.revno()
and revision == branch.last_patch()):
return #Nothing would change, so skip it
# TODO: Consider adding to x-pull so that we can try a RemoteBranch
# for checking the need to update
f = AtomicFile(branch.controlfilename(_rsync_location))
f.write(location)
f.write('\n')
f.write(str(branch.revno()))
f.write('\n')
f.write(branch.last_patch())
f.write('\n')
f.commit()
def get_parent_branch(branch):
"""Try to get the pull location, in case this directory supports the normal bzr pull.
The idea is that we can use RemoteBranch to see if we actually need to do anything,
and then we can decide whether to run rsync or not.
"""
import errno
stored_loc = None
for fname in _parent_locations:
try:
stored_loc = branch.controlfile(fname, 'rb').read().rstrip('\n')
except IOError, e:
if e.errno != errno.ENOENT:
raise
if stored_loc:
break
if stored_loc:
from bzrlib.branch import find_branch
return find_branch(stored_loc)
return None
def get_branch_remote_update(local=None, remote=None, alt_remote=None):
from bzrlib.errors import BzrCommandError
from bzrlib.branch import find_branch
if local is None:
local = '.'
if remote is not None and remote[-1:] != '/':
remote += '/'
if alt_remote is not None and alt_remote[-1:] != '/':
alt_remote += '/'
if not os.path.exists(local):
if remote is None:
remote = alt_remote
if remote is None:
raise BzrCommandError('No remote location specified while creating a new local location')
return local, remote, 0, None
b = find_branch(local)
def_remote, last_revno, last_revision = get_default_remote_info(b)
if remote is None:
if def_remote is None:
if alt_remote is None:
raise BzrCommandError('No remote location specified, and no default exists.')
else:
remote = alt_remote
else:
remote = def_remote
if remote[-1:] != '/':
remote += '/'
return b, remote, last_revno, last_revision
def check_should_pull(branch, last_revno, last_revision):
if isinstance(branch, basestring): # We don't even have a local branch yet
return True
if not is_clean(branch):
print '** Local tree is not clean. Either has unknown or modified files.'
return False
b_parent = get_parent_branch(branch)
if b_parent is not None:
from bzrlib.branch import DivergedBranches
# This may throw a Diverged branches.
try:
missing_revisions = branch.missing_revisions(b_parent)
except DivergedBranches:
print '** Local tree history has diverged from remote.'
print '** Not allowing you to overwrite local changes.'
return False
if len(missing_revisions) == 0:
# There is nothing to do, the remote branch has no changes
missing_revisions = b_parent.missing_revisions(branch)
if len(missing_revisions) > 0:
print '** Local tree is up-to-date with remote.'
print '** But remote tree is missing local revisions.'
print '** Consider using bzr rsync-push'
else:
print '** Both trees fully up-to-date.'
return False
# We are sure that we are missing remote revisions
return True
if last_revno == branch.revno() and last_revision == branch.last_patch():
# We can go ahead and try
return True
print 'Local working directory has a different revision than last rsync.'
val = raw_input('Are you sure you want to download [y/N]? ')
if val.lower() in ('y', 'yes'):
return True
return False
def check_should_push(branch, last_revno, last_revision):
if not is_clean(branch):
print '** Local tree is not clean (either modified or unknown files)'
return False
b_parent = get_parent_branch(branch)
if b_parent is not None:
from bzrlib.branch import DivergedBranches
# This may throw a Diverged branches.
try:
missing_revisions = b_parent.missing_revisions(branch)
except DivergedBranches:
print '** Local tree history has diverged from remote.'
print '** Not allowing you to overwrite remote changes.'
return False
if len(missing_revisions) == 0:
# There is nothing to do, the remote branch is up to date
missing_revisions = branch.missing_revisions(b_parent)
if len(missing_revisions) > 0:
print '** Remote tree is up-to-date with local.'
print '** But local tree is missing remote revisions.'
print '** Consider using bzr rsync-pull'
else:
print '** Both trees fully up-to-date.'
return False
# We are sure that we are missing remote revisions
return True
if last_revno is None and last_revision is None:
print 'Local tree does not have a valid last rsync revision.'
val = raw_input('push anyway [y/N]? ')
if val.lower() in ('y', 'yes'):
return True
return False
if last_revno == branch.revno() and last_revision == branch.last_patch():
print 'No new revisions.'
return False
return True
def pull(branch, remote, verbose=False, dry_run=False):
"""Update the local repository from the location specified by 'remote'
:param branch: Either a string specifying a local path, or a Branch object.
If a local path, the download will be performed, and then
a Branch object will be created.
:return: Return the branch object that was created
"""
if isinstance(branch, basestring):
local = branch
cur_revno = 0
else:
local = branch.base
cur_revno = branch.revno()
if remote[-1:] != '/':
remote += '/'
rsyncopts = ['-rltp', '--delete'
# Don't pull in a new parent location
, "--exclude '**/.bzr/x-rsync*'", "--exclude '**/.bzr/x-pull*'"
, "--exclude '**/.bzr/parent'", "--exclude '**/.bzr/pull'"
]
# Note that when pulling, we do not delete excluded files
rsync_exclude = os.path.join(local, '.rsyncexclude')
if os.path.exists(rsync_exclude):
rsyncopts.append('--exclude-from "%s"' % rsync_exclude)
bzr_ignore = os.path.join(local, '.bzrignore')
if os.path.exists(bzr_ignore):
rsyncopts.append('--exclude-from "%s"' % bzr_ignore)
if verbose:
rsyncopts.append('-v')
if dry_run:
rsyncopts.append('--dry-run')
cmd = 'rsync %s "%s" "%s"' % (' '.join(rsyncopts), remote, local)
if verbose:
print cmd
status = os.system(cmd)
if status != 0:
from bzrlib.errors import BzrError
raise BzrError('Rsync failed with error code: %s' % status)
if isinstance(branch, basestring):
from bzrlib.branch import Branch
branch = Branch(branch)
new_revno = branch.revno()
if cur_revno == new_revno:
print '** tree is up-to-date'
if verbose:
if cur_revno != new_revno:
from bzrlib.log import show_log
show_log(branch, direction='forward',
start_revision=cur_revno+1, end_revision=new_revno)
return branch
def push(branch, remote, verbose=False, dry_run=False):
"""Update the local repository from the location specified by 'remote'
:param branch: Should always be a Branch object
"""
if isinstance(branch, basestring):
from bzrlib.errors import BzrError
raise BzrError('rsync push requires a Branch object, not a string')
local = branch.base
if remote[-1:] != '/':
remote += '/'
rsyncopts = ['-rltp', '--include-from -'
, '--include .bzr'
# We don't want to push our local meta information to the remote
, "--exclude '.bzr/x-rsync*'", "--exclude '.bzr/x-pull*'"
, "--exclude '.bzr/parent'", "--exclude '.bzr/pull'"
, "--include '.bzr/**'"
, "--exclude '*'", "--exclude '.*'"
, '--delete', '--delete-excluded'
]
rsync_exclude = os.path.join(local, '.rsyncexclude')
if os.path.exists(rsync_exclude):
rsyncopts.append('--exclude-from "%s"' % rsync_exclude)
bzr_ignore = os.path.join(local, '.bzrignore')
if os.path.exists(bzr_ignore):
rsyncopts.append('--exclude-from "%s"' % bzr_ignore)
if verbose:
rsyncopts.append('-v')
if dry_run:
rsyncopts.append('--dry-run')
cmd = 'rsync %s "." "%s"' % (' '.join(rsyncopts), remote)
if verbose:
print cmd
pwd = os.getcwd()
try:
os.chdir(local)
child = os.popen(cmd, 'w')
inv = branch.read_working_inventory()
for path, entry in inv.entries():
child.write(path)
child.write('\n')
child.flush()
retval = child.close()
if retval is not None:
from bzrlib.errors import BzrError
raise BzrError('Rsync failed with error code: %s' % retval)
finally:
os.chdir(pwd)
|