/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to __init__.py

(jelmer) More lazy loading

Show diffs side-by-side

added added

removed removed

Lines of Context:
155
155
import bzrlib
156
156
import bzrlib.api
157
157
 
 
158
from bzrlib import (
 
159
    branch,
 
160
    )
 
161
 
158
162
from info import (
159
163
    bzr_plugin_version as version_info,
160
164
    bzr_compatible_versions,
168
172
 
169
173
bzrlib.api.require_any_api(bzrlib, bzr_compatible_versions)
170
174
 
171
 
from bzrlib import (
172
 
    branch,
173
 
    commands,
174
 
    lazy_import,
175
 
    option,
176
 
    )
177
 
lazy_import.lazy_import(globals(), """
178
 
import stat
179
 
import sys
180
 
 
181
 
from bzrlib import (
182
 
    bzrdir,
183
 
    errors,
184
 
    globbing,
185
 
    ignores,
186
 
    osutils,
187
 
    revision,
188
 
    revisionspec,
189
 
    trace,
190
 
    transport,
191
 
    urlutils,
192
 
    workingtree,
193
 
    )
194
 
""")
195
 
 
196
 
version_info = (1, 0, 0, 'final', 0)
197
 
plugin_name = 'upload'
198
 
 
199
 
 
200
 
def _get_branch_option(branch, option):
201
 
    return branch.get_config().get_user_option(option)
202
 
 
203
 
# FIXME: Get rid of that as soon as we depend on a bzr API that includes
204
 
# get_user_option_as_bool
205
 
def _get_branch_bool_option(branch, option):
206
 
    conf = branch.get_config()
207
 
    if hasattr(conf, 'get_user_option_as_bool'):
208
 
        value = conf.get_user_option_as_bool(option)
209
 
    else:
210
 
        value = conf.get_user_option(option)
211
 
        if value is not None:
212
 
            if value.lower().strip() == 'true':
213
 
                value = True
214
 
            else:
215
 
                value = False
216
 
    return value
217
 
 
218
 
def _set_branch_option(branch, option, value):
219
 
    branch.get_config().set_user_option(option, value)
220
 
 
221
 
 
222
 
def get_upload_location(branch):
223
 
    return _get_branch_option(branch, 'upload_location')
224
 
 
225
 
 
226
 
def set_upload_location(branch, location):
227
 
    _set_branch_option(branch, 'upload_location', location)
228
 
 
229
 
 
230
 
# FIXME: Add more tests around invalid paths used here or relative paths that
231
 
# doesn't exist on remote (if only to get proper error messages)
232
 
def get_upload_revid_location(branch):
233
 
    loc =  _get_branch_option(branch, 'upload_revid_location')
234
 
    if loc is None:
235
 
        loc = '.bzr-upload.revid'
236
 
    return loc
237
 
 
238
 
 
239
 
def set_upload_revid_location(branch, location):
240
 
    _set_branch_option(branch, 'upload_revid_location', location)
241
 
 
242
 
 
243
 
def get_upload_auto(branch):
244
 
    auto = _get_branch_bool_option(branch, 'upload_auto')
245
 
    if auto is None:
246
 
        auto = False # Default to False if not specified
247
 
    return auto
248
 
 
249
 
 
250
 
def set_upload_auto(branch, auto):
251
 
    # FIXME: What's the point in allowing a boolean here instead of requiring
252
 
    # the callers to use strings instead ?
253
 
    if auto:
254
 
        auto_str = "True"
255
 
    else:
256
 
        auto_str = "False"
257
 
    _set_branch_option(branch, 'upload_auto', auto_str)
258
 
 
259
 
 
260
 
def get_upload_auto_quiet(branch):
261
 
    quiet = _get_branch_bool_option(branch, 'upload_auto_quiet')
262
 
    if quiet is None:
263
 
        quiet = False # Default to False if not specified
264
 
    return quiet
265
 
 
266
 
 
267
 
def set_upload_auto_quiet(branch, quiet):
268
 
    _set_branch_option(branch, 'upload_auto_quiet', quiet)
269
 
 
270
 
 
271
 
class BzrUploader(object):
272
 
 
273
 
    def __init__(self, branch, to_transport, outf, tree, rev_id,
274
 
                 quiet=False):
275
 
        self.branch = branch
276
 
        self.to_transport = to_transport
277
 
        self.outf = outf
278
 
        self.tree = tree
279
 
        self.rev_id = rev_id
280
 
        self.quiet = quiet
281
 
        self._pending_deletions = []
282
 
        self._pending_renames = []
283
 
        self._uploaded_revid = None
284
 
        self._ignored = None
285
 
 
286
 
    def _up_stat(self, relpath):
287
 
        return self.to_transport.stat(urlutils.escape(relpath))
288
 
 
289
 
    def _up_rename(self, old_path, new_path):
290
 
        return self.to_transport.rename(urlutils.escape(old_path),
291
 
                                        urlutils.escape(new_path))
292
 
 
293
 
    def _up_delete(self, relpath):
294
 
        return self.to_transport.delete(urlutils.escape(relpath))
295
 
 
296
 
    def _up_delete_tree(self, relpath):
297
 
        return self.to_transport.delete_tree(urlutils.escape(relpath))
298
 
 
299
 
    def _up_mkdir(self, relpath, mode):
300
 
        return self.to_transport.mkdir(urlutils.escape(relpath), mode)
301
 
 
302
 
    def _up_rmdir(self, relpath):
303
 
        return self.to_transport.rmdir(urlutils.escape(relpath))
304
 
 
305
 
    def _up_put_bytes(self, relpath, bytes, mode):
306
 
        self.to_transport.put_bytes(urlutils.escape(relpath), bytes, mode)
307
 
 
308
 
    def _up_get_bytes(self, relpath):
309
 
        return self.to_transport.get_bytes(urlutils.escape(relpath))
310
 
 
311
 
    def set_uploaded_revid(self, rev_id):
312
 
        # XXX: Add tests for concurrent updates, etc.
313
 
        revid_path = get_upload_revid_location(self.branch)
314
 
        self.to_transport.put_bytes(urlutils.escape(revid_path), rev_id)
315
 
        self._uploaded_revid = rev_id
316
 
 
317
 
    def get_uploaded_revid(self):
318
 
        if self._uploaded_revid is None:
319
 
            revid_path = get_upload_revid_location(self.branch)
320
 
            try:
321
 
                self._uploaded_revid = self._up_get_bytes(revid_path)
322
 
            except errors.NoSuchFile:
323
 
                # We have not upload to here.
324
 
                self._uploaded_revid = revision.NULL_REVISION
325
 
        return self._uploaded_revid
326
 
 
327
 
    def _get_ignored(self):
328
 
        if self._ignored is None:
329
 
            try:
330
 
                ignore_file = self.tree.get_file_by_path('.bzrignore-upload')
331
 
                ignored_patterns = ignores.parse_ignore_file(ignore_file)
332
 
            except errors.NoSuchId:
333
 
                ignored_patterns = []
334
 
            self._ignored = globbing.Globster(ignored_patterns)
335
 
        return self._ignored
336
 
 
337
 
    def is_ignored(self, relpath):
338
 
        glob = self._get_ignored()
339
 
        ignored = glob.match(relpath)
340
 
        import os
341
 
        if not ignored:
342
 
            # We still need to check that all parents are not ignored
343
 
            dir = os.path.dirname(relpath)
344
 
            while dir and not ignored:
345
 
                ignored = glob.match(dir)
346
 
                if not ignored:
347
 
                    dir = os.path.dirname(dir)
348
 
        return ignored
349
 
 
350
 
    def upload_file(self, relpath, id, mode=None):
351
 
        if mode is None:
352
 
            if self.tree.is_executable(id):
353
 
                mode = 0775
354
 
            else:
355
 
                mode = 0664
356
 
        if not self.quiet:
357
 
            self.outf.write('Uploading %s\n' % relpath)
358
 
        self._up_put_bytes(relpath, self.tree.get_file_text(id), mode)
359
 
 
360
 
    def upload_file_robustly(self, relpath, id, mode=None):
361
 
        """Upload a file, clearing the way on the remote side.
362
 
 
363
 
        When doing a full upload, it may happen that a directory exists where
364
 
        we want to put our file.
365
 
        """
366
 
        try:
367
 
            st = self._up_stat(relpath)
368
 
            if stat.S_ISDIR(st.st_mode):
369
 
                # A simple rmdir may not be enough
370
 
                if not self.quiet:
371
 
                    self.outf.write('Clearing %s/%s\n' % (
372
 
                            self.to_transport.external_url(), relpath))
373
 
                self._up_delete_tree(relpath)
374
 
        except errors.PathError:
375
 
            pass
376
 
        self.upload_file(relpath, id, mode)
377
 
 
378
 
    def make_remote_dir(self, relpath, mode=None):
379
 
        if mode is None:
380
 
            mode = 0775
381
 
        self._up_mkdir(relpath, mode)
382
 
 
383
 
    def make_remote_dir_robustly(self, relpath, mode=None):
384
 
        """Create a remote directory, clearing the way on the remote side.
385
 
 
386
 
        When doing a full upload, it may happen that a file exists where we
387
 
        want to create our directory.
388
 
        """
389
 
        try:
390
 
            st = self._up_stat(relpath)
391
 
            if not stat.S_ISDIR(st.st_mode):
392
 
                if not self.quiet:
393
 
                    self.outf.write('Deleting %s/%s\n' % (
394
 
                            self.to_transport.external_url(), relpath))
395
 
                self._up_delete(relpath)
396
 
            else:
397
 
                # Ok the remote dir already exists, nothing to do
398
 
                return
399
 
        except errors.PathError:
400
 
            pass
401
 
        self.make_remote_dir(relpath, mode)
402
 
 
403
 
    def delete_remote_file(self, relpath):
404
 
        if not self.quiet:
405
 
            self.outf.write('Deleting %s\n' % relpath)
406
 
        self._up_delete(relpath)
407
 
 
408
 
    def delete_remote_dir(self, relpath):
409
 
        if not self.quiet:
410
 
            self.outf.write('Deleting %s\n' % relpath)
411
 
        self._up_rmdir(relpath)
412
 
        # XXX: Add a test where a subdir is ignored but we still want to
413
 
        # delete the dir -- vila 100106
414
 
 
415
 
    def delete_remote_dir_maybe(self, relpath):
416
 
        """Try to delete relpath, keeping failures to retry later."""
417
 
        try:
418
 
            self._up_rmdir(relpath)
419
 
        # any kind of PathError would be OK, though we normally expect
420
 
        # DirectoryNotEmpty
421
 
        except errors.PathError:
422
 
            self._pending_deletions.append(relpath)
423
 
 
424
 
    def finish_deletions(self):
425
 
        if self._pending_deletions:
426
 
            # Process the previously failed deletions in reverse order to
427
 
            # delete children before parents
428
 
            for relpath in reversed(self._pending_deletions):
429
 
                self._up_rmdir(relpath)
430
 
            # The following shouldn't be needed since we use it once per
431
 
            # upload, but better safe than sorry ;-)
432
 
            self._pending_deletions = []
433
 
 
434
 
    def rename_remote(self, old_relpath, new_relpath):
435
 
        """Rename a remote file or directory taking care of collisions.
436
 
 
437
 
        To avoid collisions during bulk renames, each renamed target is
438
 
        temporarily assigned a unique name. When all renames have been done,
439
 
        each target get its proper name.
440
 
        """
441
 
        # We generate a sufficiently random name to *assume* that
442
 
        # no collisions will occur and don't worry about it (nor
443
 
        # handle it).
444
 
        import os
445
 
        import random
446
 
        import time
447
 
 
448
 
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
449
 
                                     os.getpid(),
450
 
                                     random.randint(0,0x7FFFFFFF))
451
 
        if not self.quiet:
452
 
            self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath))
453
 
        self._up_rename(old_relpath, stamp)
454
 
        self._pending_renames.append((stamp, new_relpath))
455
 
 
456
 
    def finish_renames(self):
457
 
        for (stamp, new_path) in self._pending_renames:
458
 
            self._up_rename(stamp, new_path)
459
 
        # The following shouldn't be needed since we use it once per upload,
460
 
        # but better safe than sorry ;-)
461
 
        self._pending_renames = []
462
 
 
463
 
    def upload_full_tree(self):
464
 
        self.to_transport.ensure_base() # XXX: Handle errors (add
465
 
                                        # --create-prefix option ?)
466
 
        self.tree.lock_read()
467
 
        try:
468
 
            for relpath, ie in self.tree.inventory.iter_entries():
469
 
                if relpath in ('', '.bzrignore', '.bzrignore-upload'):
470
 
                    # skip root ('')
471
 
                    # .bzrignore and .bzrignore-upload have no meaning outside
472
 
                    # a working tree so do not upload them
473
 
                    continue
474
 
                if self.is_ignored(relpath):
475
 
                    if not self.quiet:
476
 
                        self.outf.write('Ignoring %s\n' % relpath)
477
 
                    continue
478
 
                if ie.kind == 'file':
479
 
                    self.upload_file_robustly(relpath, ie.file_id)
480
 
                elif ie.kind == 'directory':
481
 
                    self.make_remote_dir_robustly(relpath)
482
 
                elif ie.kind == 'symlink':
483
 
                    if not self.quiet:
484
 
                        target = self.tree.path_content_summary(path)[3]
485
 
                        self.outf.write('Not uploading symlink %s -> %s\n'
486
 
                                        % (path, target))
487
 
                else:
488
 
                    raise NotImplementedError
489
 
            self.set_uploaded_revid(self.rev_id)
490
 
        finally:
491
 
            self.tree.unlock()
492
 
 
493
 
    def upload_tree(self):
494
 
        # If we can't find the revid file on the remote location, upload the
495
 
        # full tree instead
496
 
        rev_id = self.get_uploaded_revid()
497
 
 
498
 
        if rev_id == revision.NULL_REVISION:
499
 
            if not self.quiet:
500
 
                self.outf.write('No uploaded revision id found,'
501
 
                                ' switching to full upload\n')
502
 
            self.upload_full_tree()
503
 
            # We're done
504
 
            return
505
 
 
506
 
        # Check if the revision hasn't already been uploaded
507
 
        if rev_id == self.rev_id:
508
 
            if not self.quiet:
509
 
                self.outf.write('Remote location already up to date\n')
510
 
 
511
 
        from_tree = self.branch.repository.revision_tree(rev_id)
512
 
        self.to_transport.ensure_base() # XXX: Handle errors (add
513
 
                                        # --create-prefix option ?)
514
 
        changes = self.tree.changes_from(from_tree)
515
 
        self.tree.lock_read()
516
 
        try:
517
 
            for (path, id, kind) in changes.removed:
518
 
                if self.is_ignored(path):
519
 
                    if not self.quiet:
520
 
                        self.outf.write('Ignoring %s\n' % path)
521
 
                    continue
522
 
                if kind is 'file':
523
 
                    self.delete_remote_file(path)
524
 
                elif kind is  'directory':
525
 
                    self.delete_remote_dir_maybe(path)
526
 
                elif kind == 'symlink':
527
 
                    if not self.quiet:
528
 
                        target = self.tree.path_content_summary(path)[3]
529
 
                        self.outf.write('Not deleting remote symlink %s -> %s\n'
530
 
                                        % (path, target))
531
 
                else:
532
 
                    raise NotImplementedError
533
 
 
534
 
            for (old_path, new_path, id, kind,
535
 
                 content_change, exec_change) in changes.renamed:
536
 
                if self.is_ignored(old_path) and self.is_ignored(new_path):
537
 
                    if not self.quiet:
538
 
                        self.outf.write('Ignoring %s\n' % old_path)
539
 
                        self.outf.write('Ignoring %s\n' % new_path)
540
 
                    continue
541
 
                if content_change:
542
 
                    # We update the old_path content because renames and
543
 
                    # deletions are differed.
544
 
                    self.upload_file(old_path, id)
545
 
                if kind == 'symlink':
546
 
                    if not self.quiet:
547
 
                        self.outf.write('Not renaming remote symlink %s to %s\n'
548
 
                                        % (old_path, new_path))
549
 
                else:
550
 
                    self.rename_remote(old_path, new_path)
551
 
            self.finish_renames()
552
 
            self.finish_deletions()
553
 
 
554
 
            for (path, id, old_kind, new_kind) in changes.kind_changed:
555
 
                if self.is_ignored(path):
556
 
                    if not self.quiet:
557
 
                        self.outf.write('Ignoring %s\n' % path)
558
 
                    continue
559
 
                if old_kind == 'file':
560
 
                    self.delete_remote_file(path)
561
 
                elif old_kind ==  'directory':
562
 
                    self.delete_remote_dir(path)
563
 
                else:
564
 
                    raise NotImplementedError
565
 
 
566
 
                if new_kind == 'file':
567
 
                    self.upload_file(path, id)
568
 
                elif new_kind is 'directory':
569
 
                    self.make_remote_dir(path)
570
 
                else:
571
 
                    raise NotImplementedError
572
 
 
573
 
            for (path, id, kind) in changes.added:
574
 
                if self.is_ignored(path):
575
 
                    if not self.quiet:
576
 
                        self.outf.write('Ignoring %s\n' % path)
577
 
                    continue
578
 
                if kind == 'file':
579
 
                    self.upload_file(path, id)
580
 
                elif kind == 'directory':
581
 
                    self.make_remote_dir(path)
582
 
                elif kind == 'symlink':
583
 
                    if not self.quiet:
584
 
                        target = self.tree.path_content_summary(path)[3]
585
 
                        self.outf.write('Not uploading symlink %s -> %s\n'
586
 
                                        % (path, target))
587
 
                else:
588
 
                    raise NotImplementedError
589
 
 
590
 
            # XXX: Add a test for exec_change
591
 
            for (path, id, kind,
592
 
                 content_change, exec_change) in changes.modified:
593
 
                if self.is_ignored(path):
594
 
                    if not self.quiet:
595
 
                        self.outf.write('Ignoring %s\n' % path)
596
 
                    continue
597
 
                if kind is 'file':
598
 
                    self.upload_file(path, id)
599
 
                else:
600
 
                    raise NotImplementedError
601
 
 
602
 
            self.set_uploaded_revid(self.rev_id)
603
 
        finally:
604
 
            self.tree.unlock()
605
 
 
606
 
 
607
 
class CannotUploadToWorkingTree(errors.BzrCommandError):
608
 
 
609
 
    _fmt = 'Cannot upload to a bzr managed working tree: %(url)s".'
610
 
 
611
 
 
612
 
class DivergedUploadedTree(errors.BzrCommandError):
613
 
 
614
 
    _fmt = ("Your branch (%(revid)s)"
615
 
            " and the uploaded tree (%(uploaded_revid)s) have diverged: ")
616
 
 
617
 
 
618
 
class cmd_upload(commands.Command):
619
 
    """Upload a working tree, as a whole or incrementally.
620
 
 
621
 
    If no destination is specified use the last one used.
622
 
    If no revision is specified upload the changes since the last upload.
623
 
 
624
 
    Changes include files added, renamed, modified or removed.
625
 
    """
626
 
    _see_also = ['plugins/upload']
627
 
    takes_args = ['location?']
628
 
    takes_options = [
629
 
        'revision',
630
 
        'remember',
631
 
        'overwrite',
632
 
        option.Option('full', 'Upload the full working tree.'),
633
 
        option.Option('quiet', 'Do not output what is being done.',
634
 
                       short_name='q'),
635
 
        option.Option('directory',
636
 
                      help='Branch to upload from, '
637
 
                      'rather than the one containing the working directory.',
638
 
                      short_name='d',
639
 
                      type=unicode,
640
 
                      ),
641
 
        option.Option('auto',
642
 
                      'Trigger an upload from this branch whenever the tip '
643
 
                      'revision changes.')
644
 
       ]
645
 
 
646
 
    def run(self, location=None, full=False, revision=None, remember=None,
647
 
            directory=None, quiet=False, auto=None, overwrite=False
648
 
            ):
649
 
        if directory is None:
650
 
            directory = u'.'
651
 
 
652
 
        if auto and not auto_hook_available:
653
 
            raise BzrCommandError("Your version of bzr does not have the "
654
 
                    "hooks necessary for --auto to work")
655
 
 
656
 
        (wt, branch,
657
 
         relpath) = bzrdir.BzrDir.open_containing_tree_or_branch(directory)
658
 
 
659
 
        if wt:
660
 
            wt.lock_read()
661
 
            locked = wt
662
 
        else:
663
 
            branch.lock_read()
664
 
            locked = branch
665
 
        try:
666
 
            if wt:
667
 
                changes = wt.changes_from(wt.basis_tree())
668
 
 
669
 
                if revision is None and  changes.has_changed():
670
 
                    raise errors.UncommittedChanges(wt)
671
 
 
672
 
            if location is None:
673
 
                stored_loc = get_upload_location(branch)
674
 
                if stored_loc is None:
675
 
                    raise errors.BzrCommandError(
676
 
                        'No upload location known or specified.')
677
 
                else:
678
 
                    # FIXME: Not currently tested
679
 
                    display_url = urlutils.unescape_for_display(stored_loc,
680
 
                            self.outf.encoding)
681
 
                    self.outf.write("Using saved location: %s\n" % display_url)
682
 
                    location = stored_loc
683
 
 
684
 
            to_transport = transport.get_transport(location)
685
 
 
686
 
            # Check that we are not uploading to a existing working tree.
687
 
            try:
688
 
                to_bzr_dir = bzrdir.BzrDir.open_from_transport(to_transport)
689
 
                has_wt = to_bzr_dir.has_workingtree()
690
 
            except errors.NotBranchError:
691
 
                has_wt = False
692
 
            except errors.NotLocalUrl:
693
 
                # The exception raised is a bit weird... but that's life.
694
 
                has_wt = True
695
 
 
696
 
            if has_wt:
697
 
                raise CannotUploadToWorkingTree(url=location)
698
 
            if revision is None:
699
 
                rev_id = branch.last_revision()
700
 
            else:
701
 
                if len(revision) != 1:
702
 
                    raise errors.BzrCommandError(
703
 
                        'bzr upload --revision takes exactly 1 argument')
704
 
                rev_id = revision[0].in_history(branch).rev_id
705
 
 
706
 
            tree = branch.repository.revision_tree(rev_id)
707
 
 
708
 
            uploader = BzrUploader(branch, to_transport, self.outf, tree,
709
 
                                   rev_id, quiet=quiet)
710
 
 
711
 
            if not overwrite:
712
 
                prev_uploaded_rev_id = uploader.get_uploaded_revid()
713
 
                graph = branch.repository.get_graph()
714
 
                if not graph.is_ancestor(prev_uploaded_rev_id, rev_id):
715
 
                    raise DivergedUploadedTree(
716
 
                        revid=rev_id, uploaded_revid=prev_uploaded_rev_id)
717
 
            if full:
718
 
                uploader.upload_full_tree()
719
 
            else:
720
 
                uploader.upload_tree()
721
 
        finally:
722
 
            locked.unlock()
723
 
 
724
 
        # We uploaded successfully, remember it
725
 
        if get_upload_location(branch) is None or remember:
726
 
            set_upload_location(branch, urlutils.unescape(to_transport.base))
727
 
        if auto is not None:
728
 
            set_upload_auto(branch, auto)
729
 
 
730
 
 
731
 
commands.register_command(cmd_upload)
732
 
 
 
175
from bzrlib.commands import plugin_cmds
 
176
 
 
177
plugin_cmds.register_lazy('cmd_upload', [], 'bzrlib.plugins.upload.cmds')
733
178
 
734
179
def auto_upload_hook(params):
 
180
    from bzrlib import (
 
181
        osutils,
 
182
        trace,
 
183
        transport,
 
184
        urlutils,
 
185
        )
 
186
    from bzrlib.plugins.upload.cmds import (
 
187
        BzrUploader,
 
188
        get_upload_location,
 
189
        get_upload_auto,
 
190
        get_upload_auto_quiet,
 
191
        )
 
192
    import sys
735
193
    source_branch = params.branch
736
194
    destination = get_upload_location(source_branch)
737
195
    if destination is None: