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  |