~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to plugins/rsync/rsync_update.py

  • Committer: Martin Pool
  • Date: 2005-09-13 05:22:41 UTC
  • Revision ID: mbp@sourcefrog.net-20050913052241-52dbd8e8ced620f6
- better BZR_DEBUG trace output

Show diffs side-by-side

added added

removed removed

Lines of Context:
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