bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
|
792
by Martin Pool
- rsync upload/download plugins from John A Meinel |
1 |
#!/usr/bin/env python
|
2 |
"""\
|
|
3 |
This encapsulates the functionality for trying to rsync a local
|
|
4 |
working tree to/from a remote rsync accessible location.
|
|
5 |
"""
|
|
6 |
||
7 |
import os |
|
8 |
import bzrlib |
|
9 |
||
10 |
_rsync_location = 'x-rsync-data' |
|
11 |
_parent_locations = ['parent', 'pull', 'x-pull'] |
|
12 |
||
13 |
def temp_branch(): |
|
14 |
import tempfile |
|
15 |
dirname = tempfile.mkdtemp("temp-branch") |
|
16 |
return bzrlib.Branch(dirname, init=True) |
|
17 |
||
18 |
def rm_branch(branch): |
|
19 |
import shutil |
|
20 |
shutil.rmtree(branch.base) |
|
21 |
||
22 |
def is_clean(branch): |
|
23 |
""" |
|
24 |
Return true if no files are modifed or unknown
|
|
25 |
>>> br = temp_branch()
|
|
26 |
>>> is_clean(br)
|
|
27 |
True
|
|
28 |
>>> fooname = os.path.join(br.base, "foo")
|
|
29 |
>>> file(fooname, "wb").write("bar")
|
|
30 |
>>> is_clean(br)
|
|
31 |
False
|
|
32 |
>>> bzrlib.add.smart_add([fooname])
|
|
33 |
>>> is_clean(br)
|
|
34 |
False
|
|
35 |
>>> br.commit("added file")
|
|
36 |
>>> is_clean(br)
|
|
37 |
True
|
|
38 |
>>> rm_branch(br)
|
|
39 |
"""
|
|
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(): |
|
43 |
if file_class == '?': |
|
44 |
return False |
|
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: |
|
48 |
return False |
|
49 |
return True |
|
50 |
||
51 |
def get_default_remote_info(branch): |
|
52 |
"""Return the value stored in .bzr/x-rsync-location if it exists. |
|
53 |
|
|
54 |
>>> br = temp_branch()
|
|
55 |
>>> get_default_remote_info(br)
|
|
56 |
(None, 0, None)
|
|
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')
|
|
62 |
"""
|
|
63 |
def_remote = None |
|
64 |
revno = 0 |
|
65 |
revision = None |
|
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 |
|
70 |
||
71 |
def set_default_remote_info(branch, location): |
|
72 |
"""Store the location into the .bzr/x-rsync-location. |
|
73 |
|
|
74 |
"""
|
|
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)) |
|
84 |
f.write(location) |
|
85 |
f.write('\n') |
|
86 |
f.write(str(branch.revno())) |
|
87 |
f.write('\n') |
|
88 |
f.write(branch.last_patch()) |
|
89 |
f.write('\n') |
|
90 |
f.commit() |
|
91 |
||
92 |
def get_parent_branch(branch): |
|
93 |
"""Try to get the pull location, in case this directory supports the normal bzr pull. |
|
94 |
|
|
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.
|
|
97 |
"""
|
|
98 |
import errno |
|
99 |
stored_loc = None |
|
100 |
for fname in _parent_locations: |
|
101 |
try: |
|
102 |
stored_loc = branch.controlfile(fname, 'rb').read().rstrip('\n') |
|
103 |
except IOError, e: |
|
104 |
if e.errno != errno.ENOENT: |
|
105 |
raise
|
|
106 |
||
107 |
if stored_loc: |
|
108 |
break
|
|
109 |
||
110 |
if stored_loc: |
|
111 |
from bzrlib.branch import find_branch |
|
112 |
return find_branch(stored_loc) |
|
113 |
return None |
|
114 |
||
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 |
|
118 |
if local is None: |
|
119 |
local = '.' |
|
120 |
||
121 |
if remote is not None and remote[-1:] != '/': |
|
122 |
remote += '/' |
|
123 |
||
124 |
if alt_remote is not None and alt_remote[-1:] != '/': |
|
125 |
alt_remote += '/' |
|
126 |
||
127 |
if not os.path.exists(local): |
|
128 |
if remote is None: |
|
129 |
remote = alt_remote |
|
130 |
if remote is None: |
|
131 |
raise BzrCommandError('No remote location specified while creating a new local location') |
|
132 |
return local, remote, 0, None |
|
133 |
||
134 |
b = find_branch(local) |
|
135 |
||
136 |
def_remote, last_revno, last_revision = get_default_remote_info(b) |
|
137 |
if remote is None: |
|
138 |
if def_remote is None: |
|
139 |
if alt_remote is None: |
|
140 |
raise BzrCommandError('No remote location specified, and no default exists.') |
|
141 |
else: |
|
142 |
remote = alt_remote |
|
143 |
else: |
|
144 |
remote = def_remote |
|
145 |
||
146 |
if remote[-1:] != '/': |
|
147 |
remote += '/' |
|
148 |
||
149 |
return b, remote, last_revno, last_revision |
|
150 |
||
151 |
def check_should_pull(branch, last_revno, last_revision): |
|
152 |
if isinstance(branch, basestring): # We don't even have a local branch yet |
|
153 |
return True |
|
154 |
||
155 |
if not is_clean(branch): |
|
156 |
print '** Local tree is not clean. Either has unknown or modified files.' |
|
157 |
return False |
|
158 |
||
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.
|
|
163 |
try: |
|
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.' |
|
168 |
return False |
|
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' |
|
176 |
else: |
|
177 |
print '** Both trees fully up-to-date.' |
|
178 |
return False |
|
179 |
# We are sure that we are missing remote revisions
|
|
180 |
return True |
|
181 |
||
182 |
if last_revno == branch.revno() and last_revision == branch.last_patch(): |
|
183 |
# We can go ahead and try
|
|
184 |
return True |
|
185 |
||
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'): |
|
189 |
return True |
|
190 |
return False |
|
191 |
||
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)' |
|
195 |
return False |
|
196 |
||
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.
|
|
201 |
try: |
|
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.' |
|
206 |
return False |
|
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' |
|
214 |
else: |
|
215 |
print '** Both trees fully up-to-date.' |
|
216 |
return False |
|
217 |
# We are sure that we are missing remote revisions
|
|
218 |
return True |
|
219 |
||
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'): |
|
224 |
return True |
|
225 |
return False |
|
226 |
||
227 |
if last_revno == branch.revno() and last_revision == branch.last_patch(): |
|
228 |
print 'No new revisions.' |
|
229 |
return False |
|
230 |
||
231 |
return True |
|
232 |
||
233 |
||
234 |
def pull(branch, remote, verbose=False, dry_run=False): |
|
235 |
"""Update the local repository from the location specified by 'remote' |
|
236 |
||
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.
|
|
240 |
||
241 |
:return: Return the branch object that was created
|
|
242 |
"""
|
|
243 |
if isinstance(branch, basestring): |
|
244 |
local = branch |
|
245 |
cur_revno = 0 |
|
246 |
else: |
|
247 |
local = branch.base |
|
248 |
cur_revno = branch.revno() |
|
249 |
if remote[-1:] != '/': |
|
250 |
remote += '/' |
|
251 |
||
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'" |
|
256 |
]
|
|
257 |
||
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) |
|
265 |
||
266 |
if verbose: |
|
267 |
rsyncopts.append('-v') |
|
268 |
if dry_run: |
|
269 |
rsyncopts.append('--dry-run') |
|
270 |
||
271 |
cmd = 'rsync %s "%s" "%s"' % (' '.join(rsyncopts), remote, local) |
|
272 |
if verbose: |
|
273 |
print cmd |
|
274 |
||
275 |
status = os.system(cmd) |
|
276 |
if status != 0: |
|
277 |
from bzrlib.errors import BzrError |
|
278 |
raise BzrError('Rsync failed with error code: %s' % status) |
|
279 |
||
280 |
||
281 |
if isinstance(branch, basestring): |
|
282 |
from bzrlib.branch import Branch |
|
283 |
branch = Branch(branch) |
|
284 |
||
285 |
new_revno = branch.revno() |
|
286 |
if cur_revno == new_revno: |
|
287 |
print '** tree is up-to-date' |
|
288 |
||
289 |
if verbose: |
|
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) |
|
294 |
||
295 |
return branch |
|
296 |
||
297 |
||
298 |
def push(branch, remote, verbose=False, dry_run=False): |
|
299 |
"""Update the local repository from the location specified by 'remote' |
|
300 |
||
301 |
:param branch: Should always be a Branch object
|
|
302 |
"""
|
|
303 |
if isinstance(branch, basestring): |
|
304 |
from bzrlib.errors import BzrError |
|
305 |
raise BzrError('rsync push requires a Branch object, not a string') |
|
306 |
local = branch.base |
|
307 |
if remote[-1:] != '/': |
|
308 |
remote += '/' |
|
309 |
||
310 |
rsyncopts = ['-rltp', '--include-from -' |
|
311 |
, '--include .bzr' |
|
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' |
|
318 |
]
|
|
319 |
||
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) |
|
326 |
||
327 |
if verbose: |
|
328 |
rsyncopts.append('-v') |
|
329 |
if dry_run: |
|
330 |
rsyncopts.append('--dry-run') |
|
331 |
||
332 |
cmd = 'rsync %s "." "%s"' % (' '.join(rsyncopts), remote) |
|
333 |
if verbose: |
|
334 |
print cmd |
|
335 |
||
336 |
pwd = os.getcwd() |
|
337 |
try: |
|
338 |
os.chdir(local) |
|
339 |
child = os.popen(cmd, 'w') |
|
340 |
inv = branch.read_working_inventory() |
|
341 |
for path, entry in inv.entries(): |
|
342 |
child.write(path) |
|
343 |
child.write('\n') |
|
344 |
child.flush() |
|
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) |
|
349 |
finally: |
|
350 |
os.chdir(pwd) |
|
351 |