~ubuntu-branches/debian/squeeze/bzr-builddeb/squeeze

« back to all changes in this revision

Viewing changes to import_dsc.py

  • Committer: Bazaar Package Importer
  • Author(s): Jelmer Vernooij
  • Date: 2010-01-18 19:15:26 UTC
  • mfrom: (5.1.5 karmic)
  • Revision ID: james.westby@ubuntu.com-20100118191526-fzyw0n60z6vrhhhn
Tags: 2.2
* Upload to unstable.
* Bump standards version to 3.8.3.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#    import_dsc.py -- Import a series of .dsc files.
2
2
#    Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
 
3
#              (C) 2008 Canonical Ltd.
3
4
#
4
5
#    Code is also taken from bzrtools, which is
5
6
#             (C) 2005, 2006, 2007 Aaron Bentley <aaron.bentley@utoronto.ca>
24
25
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
25
26
#
26
27
 
27
 
import gzip
 
28
from base64 import (
 
29
        standard_b64decode,
 
30
        standard_b64encode,
 
31
        )
 
32
try:
 
33
    import hashlib as md5
 
34
except ImportError:
 
35
    import md5
28
36
import os
 
37
import shutil
29
38
import stat
30
 
import select
31
39
from subprocess import Popen, PIPE
32
 
import tarfile
 
40
from StringIO import StringIO
 
41
import tempfile
33
42
 
34
43
from debian_bundle import deb822
35
 
from debian_bundle.changelog import Version
 
44
from debian_bundle.changelog import Version, Changelog, VersionError
36
45
 
37
 
from bzrlib import (bzrdir,
 
46
from bzrlib import (
 
47
                    bzrdir,
38
48
                    generate_ids,
39
49
                    osutils,
40
 
                    urlutils,
41
50
                    )
42
51
from bzrlib.config import ConfigObj
43
 
from bzrlib.errors import FileExists, BzrError, UncommittedChanges
 
52
from bzrlib.errors import (
 
53
        AlreadyBranchError,
 
54
        BzrCommandError,
 
55
        NotBranchError,
 
56
        NoWorkingTree,
 
57
        )
 
58
from bzrlib.export import export
44
59
from bzrlib.osutils import file_iterator, isdir, basename, splitpath
 
60
from bzrlib.revisionspec import RevisionSpec
45
61
from bzrlib.revision import NULL_REVISION
46
 
from bzrlib.trace import warning, info
 
62
from bzrlib.trace import warning, mutter, note
47
63
from bzrlib.transform import TreeTransform, cook_conflicts, resolve_conflicts
48
 
from bzrlib.transport import get_transport
49
 
from bzrlib.workingtree import WorkingTree
 
64
from bzrlib.transport import (
 
65
    get_transport,
 
66
    )
50
67
 
51
68
from bzrlib.plugins.bzrtools.upstream_import import (
52
69
                                                     names_of_files,
53
70
                                                     add_implied_parents,
54
71
                                                     )
55
72
 
56
 
from bzrlib.plugins.builddeb.errors import (ImportError,
57
 
                OnlyImportSingleDsc,
 
73
from bzrlib.plugins.builddeb.errors import (
 
74
                PristineTarError,
 
75
                TarFailed,
58
76
                UnknownType,
59
 
                )
60
 
from bzrlib.plugins.builddeb.merge_upstream import (make_upstream_tag,
61
 
                upstream_tag_to_version,
62
 
                )
63
 
 
64
 
# TODO: support explicit upstream branch.
65
 
 
66
 
files_to_ignore = set(['.cvsignore', '.arch-inventory', '.bzrignore',
67
 
    '.gitignore', 'CVS', 'RCS', '.deps', '{arch}', '.arch-ids', '.svn',
68
 
    '.hg', '_darcs', '.git', '.shelf', '.bzr', '.bzr.backup', '.bzrtags',
69
 
    '.bzr-builddeb'])
70
 
 
71
 
exclude_as_files = ['*/' + x for x in files_to_ignore]
72
 
exclude_as_dirs = ['*/' + x + '/*' for x in files_to_ignore]
73
 
exclude = exclude_as_files + exclude_as_dirs
74
 
underscore_x = ['-x'] * len(exclude)
75
 
ignore_arguments = []
76
 
map(ignore_arguments.extend, zip(underscore_x, exclude))
77
 
ignore_arguments = ignore_arguments + ['-x', '*,v']
78
 
 
79
 
def import_tar(tree, tar_input, file_ids_from=None):
80
 
    """Replace the contents of a working directory with tarfile contents.
81
 
    The tarfile may be a gzipped stream.  File ids will be updated.
82
 
    """
83
 
    tar_file = tarfile.open('lala', 'r', tar_input)
84
 
    import_archive(tree, tar_file, file_ids_from=file_ids_from)
 
77
                UpstreamAlreadyImported,
 
78
                UpstreamBranchAlreadyMerged,
 
79
                )
 
80
from bzrlib.plugins.builddeb.util import (
 
81
    get_commit_info_from_changelog,
 
82
    get_snapshot_revision,
 
83
    open_file_via_transport,
 
84
    open_transport,
 
85
    subprocess_setup,
 
86
    )
 
87
 
 
88
 
 
89
files_to_ignore = set(
 
90
    ['.bzrignore', '.shelf', '.bzr', '.bzr.backup', '.bzrtags',
 
91
     '.bzr-builddeb'])
 
92
 
 
93
 
 
94
class DirWrapper(object):
 
95
    def __init__(self, fileobj, mode='r'):
 
96
        assert mode == 'r', mode
 
97
        self.root = os.path.realpath(fileobj.read())
 
98
 
 
99
    def __repr__(self):
 
100
        return 'DirWrapper(%r)' % self.root
 
101
 
 
102
    def getmembers(self, subdir=None):
 
103
        if subdir is not None:
 
104
            mydir = os.path.join(self.root, subdir)
 
105
        else:
 
106
            mydir = self.root
 
107
        for child in os.listdir(mydir):
 
108
            if subdir is not None:
 
109
                child = os.path.join(subdir, child)
 
110
            fi = FileInfo(self.root, child)
 
111
            yield fi
 
112
            if fi.isdir():
 
113
                for v in self.getmembers(child):
 
114
                    yield v
 
115
 
 
116
    def extractfile(self, member):
 
117
        return open(member.fullpath)
 
118
 
 
119
 
 
120
class FileInfo(object):
 
121
 
 
122
    def __init__(self, root, filepath):
 
123
        self.fullpath = os.path.join(root, filepath)
 
124
        self.root = root
 
125
        if filepath != '':
 
126
            self.name = os.path.join(basename(root), filepath)
 
127
        else:
 
128
            self.name = basename(root)
 
129
        self.type = None
 
130
        stat = os.lstat(self.fullpath)
 
131
        self.mode = stat.st_mode
 
132
        if self.isdir():
 
133
            self.name += '/'
 
134
 
 
135
    def __repr__(self):
 
136
        return 'FileInfo(%r)' % self.name
 
137
 
 
138
    def isreg(self):
 
139
        return stat.S_ISREG(self.mode)
 
140
 
 
141
    def isdir(self):
 
142
        return stat.S_ISDIR(self.mode)
 
143
 
 
144
    def issym(self):
 
145
        if stat.S_ISLNK(self.mode):
 
146
            self.linkname = os.readlink(self.fullpath)
 
147
            return True
 
148
        else:
 
149
            return False
 
150
 
 
151
    def islnk(self):
 
152
        # This could be accurate, but the use below seems like
 
153
        # it wouldn't really care
 
154
        return False
 
155
 
 
156
 
 
157
def import_dir(tree, dir, file_ids_from=None):
 
158
    dir_input = StringIO(dir)
 
159
    dir_file = DirWrapper(dir_input)
 
160
    import_archive(tree, dir_file, file_ids_from=file_ids_from)
85
161
 
86
162
 
87
163
def do_directory(tt, trans_id, tree, relative_path, path):
92
168
 
93
169
 
94
170
def should_ignore(relative_path):
95
 
  parts = splitpath(relative_path)
96
 
  if not parts:
97
 
    return False
98
 
  for part in parts:
99
 
    if part in files_to_ignore:
100
 
      return True
101
 
    if part.endswith(',v'):
102
 
      return True
 
171
    parts = splitpath(relative_path)
 
172
    if not parts:
 
173
        return False
 
174
    for part in parts:
 
175
        if part in files_to_ignore:
 
176
            return True
 
177
        if part.endswith(',v'):
 
178
            return True
103
179
 
104
180
 
105
181
def top_directory(path):
106
182
    """Return the top directory given in a path."""
107
183
    parts = osutils.splitpath(osutils.normpath(path))
108
184
    if len(parts) > 0:
109
 
      return parts[0]
 
185
        return parts[0]
110
186
    return ''
111
187
 
112
188
 
115
191
    prefixes = set()
116
192
    prefixes.update(map(top_directory, names))
117
193
    if '' in prefixes:
118
 
      prefixes.remove('')
 
194
        prefixes.remove('')
119
195
    if len(prefixes) != 1:
120
 
      return None
 
196
        return None
121
197
    prefix = prefixes.pop()
122
198
    if prefix == '':
123
 
      return None
 
199
        return None
124
200
    return prefix
125
201
 
126
202
 
128
204
    prefix = common_directory(names_of_files(archive_file))
129
205
    tt = TreeTransform(tree)
130
206
 
 
207
    if file_ids_from is None:
 
208
        file_ids_from = []
 
209
 
131
210
    removed = set()
132
211
    for path, entry in tree.inventory.iter_entries():
133
212
        if entry.parent_id is None:
173
252
        else:
174
253
            raise UnknownType(relative_path)
175
254
        if tt.tree_file_id(trans_id) is None:
176
 
            if (file_ids_from is not None and
177
 
                file_ids_from.has_filename(relative_path)):
178
 
                file_id = file_ids_from.path2id(relative_path)
179
 
                assert file_id is not None
180
 
                tt.version_file(file_id, trans_id)
181
 
            else:
 
255
            found = False
 
256
            for other_tree in file_ids_from:
 
257
                other_tree.lock_read()
 
258
                try:
 
259
                    if other_tree.has_filename(relative_path):
 
260
                        file_id = other_tree.path2id(relative_path)
 
261
                        if file_id is not None:
 
262
                            tt.version_file(file_id, trans_id)
 
263
                            found = True
 
264
                            break
 
265
                finally:
 
266
                    other_tree.unlock()
 
267
            if not found:
182
268
                name = basename(member.name.rstrip('/'))
183
269
                file_id = generate_ids.gen_file_id(name)
184
270
                tt.version_file(file_id, trans_id)
190
276
        path = tree.abspath(relative_path)
191
277
        do_directory(tt, trans_id, tree, relative_path, path)
192
278
        if tt.tree_file_id(trans_id) is None:
193
 
            if (file_ids_from is not None and
194
 
                file_ids_from.has_filename(relative_path)):
195
 
                file_id = file_ids_from.path2id(relative_path)
196
 
                assert file_id is not None
197
 
                tt.version_file(file_id, trans_id)
198
 
            else:
 
279
            found = False
 
280
            for other_tree in file_ids_from:
 
281
                other_tree.lock_read()
 
282
                try:
 
283
                    if other_tree.has_filename(relative_path):
 
284
                        file_id = other_tree.path2id(relative_path)
 
285
                        if file_id is not None:
 
286
                            tt.version_file(file_id, trans_id)
 
287
                            found = True
 
288
                            break
 
289
                finally:
 
290
                    other_tree.unlock()
 
291
            if not found:
199
292
                tt.version_file(trans_id, trans_id)
200
293
        added.add(relative_path)
201
294
 
207
300
    tt.apply()
208
301
 
209
302
 
210
 
def open_file(path, transport, base_dir=None):
211
 
  """Open a file, possibly over a transport.
212
 
 
213
 
  Open the named path, using the transport if not None. If the transport and
214
 
  base_dir are not None, then path will be interpreted relative to base_dir.
215
 
  """
216
 
  if transport is None:
217
 
    base_dir, path = urlutils.split(path)
218
 
    transport = get_transport(base_dir)
219
 
  else:
220
 
    if base_dir is not None:
221
 
      path = urlutils.join(base_dir, path)
222
 
  return (transport.get(path), transport)
223
 
 
224
 
 
225
303
class DscCache(object):
226
304
 
227
305
  def __init__(self, transport=None):
230
308
    self.transport = transport
231
309
 
232
310
  def get_dsc(self, name):
 
311
 
233
312
    if name in self.cache:
234
313
      dsc1 = self.cache[name]
235
314
    else:
236
 
      (f1, transport) = open_file(name, self.transport)
 
315
      # Obtain the dsc file, following any redirects as needed.
 
316
      filename, transport = open_transport(name)
 
317
      f1 = open_file_via_transport(filename, transport)
237
318
      try:
238
319
        dsc1 = deb822.Dsc(f1)
239
320
      finally:
240
321
        f1.close()
241
322
      self.cache[name] = dsc1
242
323
      self.transport_cache[name] = transport
 
324
 
243
325
    return dsc1
244
326
 
245
327
  def get_transport(self, name):
262
344
    return -1
263
345
 
264
346
 
265
 
class DscImporter(object):
266
 
 
267
 
  transport = None
268
 
 
269
 
  def __init__(self, dsc_files):
270
 
    self.dsc_files = dsc_files
271
 
 
272
 
  def import_orig(self, tree, origname, version, last_upstream=None,
273
 
                  transport=None, base_dir=None, dangling_tree=None):
274
 
    f = open_file(origname, transport, base_dir=base_dir)[0]
275
 
    try:
276
 
      if self.orig_target is not None:
277
 
        # Make the orig dir and copy the orig tarball in to it
278
 
        if not os.path.isdir(self.orig_target):
279
 
          os.mkdir(self.orig_target)
280
 
        new_filename = os.path.join(self.orig_target,
281
 
                                    os.path.basename(origname))
282
 
        new_f = open(new_filename, 'wb')
 
347
class DistributionBranchSet(object):
 
348
    """A collection of DistributionBranches with an ordering.
 
349
 
 
350
    A DistributionBranchSet collects a group of DistributionBranches
 
351
    and an order, and then can provide the branches with information
 
352
    about their place in the relationship with other branches.
 
353
    """
 
354
 
 
355
    def __init__(self):
 
356
        """Create a DistributionBranchSet."""
 
357
        self._branch_list = []
 
358
 
 
359
    def add_branch(self, branch):
 
360
        """Adds a DistributionBranch to the end of the list.
 
361
 
 
362
        Appends the passed distribution branch to the end of the list
 
363
        that this DistributionBranchSet represents. It also provides
 
364
        the distribution branch with a way to get the branches that
 
365
        are before and after it in the list.
 
366
 
 
367
        It will call branch.set_get_lesser_branches_callback() and
 
368
        branch.set_get_greater_branches_callback(), passing it methods
 
369
        that the DistributionBranch can call to get the list of branches
 
370
        before it in the list and after it in the list respectively.
 
371
        The passed methods take no arguments and return a list (possibly
 
372
        empty) of the desired branches.
 
373
 
 
374
        :param branch: the DistributionBranch to add.
 
375
        """
 
376
        self._branch_list.append(branch)
 
377
        lesser_callback = self._make_lesser_callback(branch)
 
378
        branch.set_get_lesser_branches_callback(lesser_callback)
 
379
        greater_callback = self._make_greater_callback(branch)
 
380
        branch.set_get_greater_branches_callback(greater_callback)
 
381
 
 
382
    def _make_lesser_callback(self, branch):
 
383
        return lambda: self.get_lesser_branches(branch)
 
384
 
 
385
    def _make_greater_callback(self, branch):
 
386
        return lambda: self.get_greater_branches(branch)
 
387
 
 
388
    def get_lesser_branches(self, branch):
 
389
        """Return the list of branches less than the argument.
 
390
 
 
391
        :param branch: The branch that all branches returned must be less
 
392
            than.
 
393
        :return: a (possibly empty) list of all the branches that are
 
394
            less than the argument. The list is sorted starting with the
 
395
            least element.
 
396
        """
 
397
        index = self._branch_list.index(branch)
 
398
        return self._branch_list[:index]
 
399
 
 
400
    def get_greater_branches(self, branch):
 
401
        """Return the list of branches greater than the argument.
 
402
 
 
403
        :param branch: The branch that all branches returned must be greater
 
404
            than.
 
405
        :return: a (possibly empty) list of all the branches that are
 
406
            greater than the argument. The list is sorted starting with the
 
407
            least element.
 
408
        """
 
409
        index = self._branch_list.index(branch)
 
410
        return self._branch_list[index+1:]
 
411
 
 
412
 
 
413
class DistributionBranch(object):
 
414
    """A DistributionBranch is a representation of one line of development.
 
415
 
 
416
    It is a branch that is linked to a line of development, such as Debian
 
417
    unstable. It also has associated branches, some of which are "lesser"
 
418
    and some are "greater". A lesser branch is one that this branch
 
419
    derives from. A greater branch is one that derives from this. For
 
420
    instance Debian experimental would have unstable as a lesser branch,
 
421
    and vice-versa. It is assumed that a group of DistributionBranches will
 
422
    have a total ordering with respect to these relationships.
 
423
    """
 
424
 
 
425
    def __init__(self, branch, upstream_branch, tree=None,
 
426
            upstream_tree=None):
 
427
        """Create a distribution branch.
 
428
 
 
429
        You can only import packages on to the DistributionBranch
 
430
        if both tree and upstream_tree are provided.
 
431
 
 
432
        :param branch: the Branch for the packaging part.
 
433
        :param upstream_branch: the Branch for the upstream part, if any.
 
434
        :param tree: an optional tree for the branch.
 
435
        :param upstream_tree: an optional upstream_tree for the
 
436
            upstream_branch.
 
437
        """
 
438
        self.branch = branch
 
439
        self.upstream_branch = upstream_branch
 
440
        self.tree = tree
 
441
        self.upstream_tree = upstream_tree
 
442
        self.get_lesser_branches = None
 
443
        self.get_greater_branches = None
 
444
 
 
445
    def set_get_lesser_branches_callback(self, callback):
 
446
        """Set the callback to get the branches "lesser" than this.
 
447
 
 
448
        The function passed to this method will be used to get the
 
449
        list of branches that are "lesser" than this one. It is
 
450
        expected to require no arguments, and to return the desired
 
451
        (possibly empty) list of branches. The returned list should
 
452
        be sorted starting with the least element.
 
453
 
 
454
        :param callback: a function that is called to get the desired list
 
455
            of branches.
 
456
        """
 
457
        self.get_lesser_branches = callback
 
458
 
 
459
    def set_get_greater_branches_callback(self, callback):
 
460
        """Set the callback to get the branches "greater" than this.
 
461
 
 
462
        The function passed to this method will be used to get the
 
463
        list of branches that are "greater" than this one. It is
 
464
        expected to require no arguments, and to return the desired
 
465
        (possibly empty) list of branches. The returned list should
 
466
        be sorted starting with the least element.
 
467
 
 
468
        :param callback: a function that is called to get the desired list
 
469
            of branches.
 
470
        """
 
471
        self.get_greater_branches = callback
 
472
 
 
473
    def get_other_branches(self):
 
474
        """Return all the other branches in this set.
 
475
 
 
476
        The returned list will be ordered, and will not contain this
 
477
        branch.
 
478
 
 
479
        :return: a list of all the other branches in this set (if any).
 
480
        """
 
481
        return self.get_lesser_branches() + self.get_greater_branches()
 
482
 
 
483
    def tag_name(self, version):
 
484
        """Gets the name of the tag that is used for the version.
 
485
 
 
486
        :param version: the Version object that the tag should refer to.
 
487
        :return: a String with the name of the tag.
 
488
        """
 
489
        return str(version)
 
490
 
 
491
    def upstream_tag_name(self, version, distro=None):
 
492
        """Gets the tag name for the upstream part of version.
 
493
 
 
494
        :param version: the Version object to extract the upstream
 
495
            part of the version number from.
 
496
        :return: a String with the name of the tag.
 
497
        """
 
498
        assert isinstance(version, str)
 
499
        tag_name = self.tag_name(version)
 
500
        if distro is None:
 
501
            return "upstream-" + tag_name
 
502
        return "upstream-%s-%s" % (distro, tag_name)
 
503
 
 
504
    def _has_version(self, branch, tag_name, md5=None):
 
505
        if branch.tags.has_tag(tag_name):
 
506
            revid = branch.tags.lookup_tag(tag_name)
 
507
            branch.lock_read()
 
508
            try:
 
509
                graph = branch.repository.get_graph()
 
510
                if not graph.is_ancestor(revid, branch.last_revision()):
 
511
                    return False
 
512
            finally:
 
513
                branch.unlock()
 
514
            if md5 is None:
 
515
                return True
 
516
            rev = branch.repository.get_revision(revid)
 
517
            try:
 
518
                return rev.properties['deb-md5'] == md5
 
519
            except KeyError:
 
520
                warning("tag %s present in branch, but there is no "
 
521
                    "associated 'deb-md5' property" % tag_name)
 
522
                pass
 
523
        return False
 
524
 
 
525
    def has_version(self, version, md5=None):
 
526
        """Whether this branch contains the package version specified.
 
527
 
 
528
        The version must be judged present by having the appropriate tag
 
529
        in the branch. If the md5 argument is not None then the string
 
530
        passed must the the md5sum that is associated with the revision
 
531
        pointed to by the tag.
 
532
 
 
533
        :param version: a Version object to look for in this branch.
 
534
        :param md5: a string with the md5sum that if not None must be
 
535
            associated with the revision.
 
536
        :return: True if this branch contains the specified version of the
 
537
            package. False otherwise.
 
538
        """
 
539
        tag_name = self.tag_name(version)
 
540
        if self._has_version(self.branch, tag_name, md5=md5):
 
541
            return True
 
542
        debian_tag_name = "debian-" + tag_name
 
543
        if self._has_version(self.branch, debian_tag_name, md5=md5):
 
544
            return True
 
545
        ubuntu_tag_name = "ubuntu-" + tag_name
 
546
        if self._has_version(self.branch, ubuntu_tag_name, md5=md5):
 
547
            return True
 
548
        return False
 
549
 
 
550
    def has_upstream_version(self, version, md5=None):
 
551
        """Whether this branch contains the upstream version specified.
 
552
 
 
553
        The version must be judged present by having the appropriate tag
 
554
        in the upstream branch. If the md5 argument is not None then the
 
555
        string passed must the the md5sum that is associated with the
 
556
        revision pointed to by the tag.
 
557
 
 
558
        :param version: a upstream version number to look for in the upstream 
 
559
            branch.
 
560
        :param md5: a string with the md5sum that if not None must be
 
561
            associated with the revision.
 
562
        :return: True if the upstream branch contains the specified upstream
 
563
            version of the package. False otherwise.
 
564
        """
 
565
        tag_name = self.upstream_tag_name(version)
 
566
        if self._has_version(self.upstream_branch, tag_name, md5=md5):
 
567
            return True
 
568
        tag_name = self.upstream_tag_name(version, distro="debian")
 
569
        if self._has_version(self.upstream_branch, tag_name, md5=md5):
 
570
            return True
 
571
        tag_name = self.upstream_tag_name(version, distro="ubuntu")
 
572
        if self._has_version(self.upstream_branch, tag_name, md5=md5):
 
573
            return True
 
574
        return False
 
575
 
 
576
    def has_upstream_version_in_packaging_branch(self, version, md5=None):
 
577
        assert isinstance(version, str)
 
578
        tag_name = self.upstream_tag_name(version)
 
579
        if self._has_version(self.branch, tag_name, md5=md5):
 
580
            return True
 
581
        tag_name = self.upstream_tag_name(version, distro="debian")
 
582
        if self._has_version(self.branch, tag_name, md5=md5):
 
583
            return True
 
584
        tag_name = self.upstream_tag_name(version, distro="ubuntu")
 
585
        if self._has_version(self.branch, tag_name, md5=md5):
 
586
            return True
 
587
        return False
 
588
 
 
589
    def contained_versions(self, versions):
 
590
        """Splits a list of versions depending on presence in the branch.
 
591
 
 
592
        Partitions the input list of versions depending on whether they
 
593
        are present in the branch or not.
 
594
 
 
595
        The two output lists will be sorted in the same order as the input
 
596
        list.
 
597
 
 
598
        :param versions: a list of Version objects to look for in the
 
599
            branch. May be an empty list.
 
600
        :return: A tuple of two lists. The first list is the list of those
 
601
            items from the input list that are present in the branch. The
 
602
            second list is the list of those items from the input list that
 
603
            are not present in the branch. The two lists will be disjoint
 
604
            and cover the input list. Either list may be empty, or both if
 
605
            the input list is empty.
 
606
        """
 
607
        #FIXME: should probably do an ancestory check to find all
 
608
        # merged revisions. This will avoid adding an extra parent
 
609
        # when say
 
610
        # experimental 1-1~rc1
 
611
        # unstable 1-1 1-1~rc1
 
612
        # Ubuntu 1-1ubuntu1 1-1 1-1~rc1
 
613
        # where only the first in each list is actually uploaded.
 
614
        contained = []
 
615
        not_contained = []
 
616
        for version in versions:
 
617
            if self.has_version(version):
 
618
                contained.append(version)
 
619
            else:
 
620
                not_contained.append(version)
 
621
        return contained, not_contained
 
622
 
 
623
    def missing_versions(self, versions):
 
624
        """Returns the versions from the list that the branch does not have.
 
625
 
 
626
        Looks at all the versions specified and returns a list of the ones
 
627
        that are earlier in the list that the last version that is
 
628
        contained in this branch.
 
629
 
 
630
        :param versions: a list of Version objects to look for in the branch.
 
631
            May be an empty list.
 
632
        :return: The subset of versions from the list that are not present
 
633
            in this branch. May be an empty list.
 
634
        """
 
635
        last_contained = self.last_contained_version(versions)
 
636
        if last_contained is None:
 
637
            return versions
 
638
        index = versions.index(last_contained)
 
639
        return versions[:index]
 
640
 
 
641
    def last_contained_version(self, versions):
 
642
        """Returns the highest version from the list present in this branch.
 
643
 
 
644
        It assumes that the input list of versions is sorted with the
 
645
        highest version first.
 
646
 
 
647
        :param versions: a list of Version objects to look for in the branch.
 
648
            Must be sorted with the highest version first. May be an empty
 
649
            list.
 
650
        :return: the highest version that is contained in this branch, or
 
651
            None if none of the versions are contained within the branch.
 
652
        """
 
653
        for version in versions:
 
654
            if self.has_version(version):
 
655
                return version
 
656
        return None
 
657
 
 
658
    def revid_of_version(self, version):
 
659
        """Returns the revision id corresponding to that version.
 
660
 
 
661
        :param version: the Version object that you wish to retrieve the
 
662
            revision id of. The Version must be present in the branch.
 
663
        :return: the revision id corresponding to that version
 
664
        """
 
665
        tag_name = self.tag_name(version)
 
666
        if self._has_version(self.branch, tag_name):
 
667
            return self.branch.tags.lookup_tag(tag_name)
 
668
        debian_tag_name = "debian-" + tag_name
 
669
        if self._has_version(self.branch, debian_tag_name):
 
670
            return self.branch.tags.lookup_tag(debian_tag_name)
 
671
        ubuntu_tag_name = "ubuntu-" + tag_name
 
672
        if self._has_version(self.branch, ubuntu_tag_name):
 
673
            return self.branch.tags.lookup_tag(ubuntu_tag_name)
 
674
        return self.branch.tags.lookup_tag(tag_name)
 
675
 
 
676
    def revid_of_upstream_version(self, version):
 
677
        """Returns the revision id corresponding to the upstream version.
 
678
 
 
679
        :param version: the Version object to extract the upstream version
 
680
            from to retreive the revid of. The upstream version must be
 
681
            present in the upstream branch.
 
682
        :return: the revision id corresponding to the upstream portion
 
683
            of the version
 
684
        """
 
685
        tag_name = self.upstream_tag_name(version)
 
686
        if self._has_version(self.upstream_branch, tag_name):
 
687
            return self.upstream_branch.tags.lookup_tag(tag_name)
 
688
        tag_name = self.upstream_tag_name(version, distro="debian")
 
689
        if self._has_version(self.upstream_branch, tag_name):
 
690
            return self.upstream_branch.tags.lookup_tag(tag_name)
 
691
        tag_name = self.upstream_tag_name(version, distro="ubuntu")
 
692
        if self._has_version(self.upstream_branch, tag_name):
 
693
            return self.upstream_branch.tags.lookup_tag(tag_name)
 
694
        tag_name = self.upstream_tag_name(version)
 
695
        return self.upstream_branch.tags.lookup_tag(tag_name)
 
696
 
 
697
    def tag_version(self, version):
 
698
        """Tags the branch's last revision with the given version.
 
699
 
 
700
        Sets a tag on the last revision of the branch with a tag that refers
 
701
        to the version provided.
 
702
 
 
703
        :param version: the Version object to derive the tag name from.
 
704
        :return: Name of the tag set
 
705
        """
 
706
        tag_name = self.tag_name(version)
 
707
        self.branch.tags.set_tag(tag_name,
 
708
                self.branch.last_revision())
 
709
        return tag_name
 
710
 
 
711
    def tag_upstream_version(self, version):
 
712
        """Tags the upstream branch's last revision with an upstream version.
 
713
 
 
714
        Sets a tag on the last revision of the upstream branch with a tag
 
715
        that refers to the upstream part of the version provided.
 
716
 
 
717
        :param version: the upstream part of the version number to derive the 
 
718
            tag name from.
 
719
        """
 
720
        assert isinstance(version, str)
 
721
        tag_name = self.upstream_tag_name(version)
 
722
        self.upstream_branch.tags.set_tag(tag_name,
 
723
                self.upstream_branch.last_revision())
 
724
        self.branch.tags.set_tag(tag_name,
 
725
                self.upstream_branch.last_revision())
 
726
 
 
727
    def _default_config_for_tree(self, tree):
 
728
        # FIXME: shouldn't go to configobj directly
 
729
        path = '.bzr-builddeb/default.conf'
 
730
        c_fileid = tree.path2id(path)
 
731
        config = None
 
732
        if c_fileid is not None:
 
733
            tree.lock_read()
 
734
            try:
 
735
                config = ConfigObj(tree.get_file(c_fileid, path))
 
736
                try:
 
737
                    config['BUILDDEB']
 
738
                except KeyError:
 
739
                    config['BUILDDEB'] = {}
 
740
            finally:
 
741
                tree.unlock()
 
742
        return config
 
743
 
 
744
 
 
745
    def _is_tree_native(self, tree):
 
746
        config = self._default_config_for_tree(tree)
 
747
        if config is not None:
 
748
            try:
 
749
                current_value = config['BUILDDEB']['native']
 
750
            except KeyError:
 
751
                current_value = False
 
752
            return current_value == "True"
 
753
        return False
 
754
 
 
755
    def is_version_native(self, version):
 
756
        """Determines whether the given version is native.
 
757
 
 
758
        :param version: the Version object to test. Must be present in
 
759
            the branch.
 
760
        :return: True if the version is was recorded as native when
 
761
            imported, False otherwise.
 
762
        """
 
763
        revid = self.revid_of_version(version)
 
764
        rev_tree = self.branch.repository.revision_tree(revid)
 
765
        if self._is_tree_native(rev_tree):
 
766
            return True
 
767
        rev = self.branch.repository.get_revision(revid)
283
768
        try:
284
 
          new_f.write(f.read())
285
 
        finally:
286
 
          new_f.close()
287
 
        f.close()
288
 
        f = open(new_filename)
289
 
      dangling_revid = None
290
 
      if last_upstream is not None:
291
 
        dangling_revid = tree.branch.last_revision()
292
 
        dangling_tree = tree.branch.repository.revision_tree(dangling_revid)
293
 
        old_upstream_revid = tree.branch.tags.lookup_tag(
294
 
                                 make_upstream_tag(last_upstream))
295
 
        tree.revert(None,
296
 
                    tree.branch.repository.revision_tree(old_upstream_revid))
297
 
      import_tar(tree, f, file_ids_from=dangling_tree)
298
 
      if last_upstream is not None:
299
 
        tree.set_parent_ids([old_upstream_revid])
300
 
        revno = tree.branch.revision_id_to_revno(old_upstream_revid)
301
 
        tree.branch.set_last_revision_info(revno, old_upstream_revid)
302
 
      tree.commit('import upstream from %s' % (os.path.basename(origname)))
303
 
      upstream_version = version.upstream_version
304
 
      tree.branch.tags.set_tag(make_upstream_tag(upstream_version),
305
 
                               tree.branch.last_revision())
306
 
    finally:
307
 
      f.close()
308
 
    return dangling_revid
309
 
 
310
 
  def import_native(self, tree, origname, version, last_upstream=None,
311
 
                    transport=None, base_dir=None):
312
 
    f = open_file(origname, transport, base_dir=base_dir)[0]
313
 
    try:
314
 
      dangling_revid = None
315
 
      dangling_tree = None
316
 
      old_upstream_tree = None
317
 
      if last_upstream is not None:
318
 
        old_upstream_revid = tree.branch.tags.lookup_tag(
319
 
                                 make_upstream_tag(last_upstream))
320
 
        old_upstream_tree = tree.branch.repository.revision_tree(
321
 
                                  old_upstream_revid)
322
 
        if old_upstream_revid != tree.branch.last_revision():
323
 
          dangling_revid = tree.branch.last_revision()
324
 
          dangling_tree = tree.branch.repository.revision_tree(dangling_revid)
325
 
        tree.revert(None,
326
 
                    tree.branch.repository.revision_tree(old_upstream_revid))
327
 
      import_tar(tree, f, file_ids_from=dangling_tree)
328
 
      if last_upstream is not None:
329
 
        tree.set_parent_ids([old_upstream_revid])
330
 
        revno = tree.branch.revision_id_to_revno(old_upstream_revid)
331
 
        tree.branch.set_last_revision_info(revno, old_upstream_revid)
332
 
      if dangling_revid is not None:
333
 
        tree.add_parent_tree_id(dangling_revid)
334
 
      config_filename = '.bzr-builddeb/default.conf'
335
 
      to_add = False
336
 
      to_add_dir = False
337
 
      if not tree.has_filename(config_filename):
338
 
        if not tree.has_filename(os.path.dirname(config_filename)):
339
 
          os.mkdir(os.path.join(tree.basedir,
340
 
                                os.path.dirname(config_filename)))
341
 
          to_add_dir = True
342
 
        conf = open(os.path.join(tree.basedir, config_filename), 'wb')
343
 
        conf.close()
344
 
        to_add = True
345
 
      config_ = ConfigObj(os.path.join(tree.basedir, config_filename))
346
 
      try:
347
 
        config_['BUILDDEB']
348
 
      except KeyError:
349
 
        config_['BUILDDEB'] = {}
350
 
      try:
351
 
        current_value = config_['BUILDDEB']['native']
352
 
      except KeyError:
353
 
        current_value = False
354
 
      if not current_value:
355
 
        config_['BUILDDEB']['native'] = True
356
 
        config_.write()
357
 
      if to_add_dir:
358
 
        file_id = None
359
 
        parent = os.path.dirname(config_filename)
360
 
        if old_upstream_tree is not None:
361
 
          file_id = old_upstream_tree.path2id(parent)
362
 
        if file_id is not None:
363
 
          tree.add([parent], [file_id])
364
 
        else:
365
 
          tree.add([parent])
366
 
      if to_add:
367
 
        file_id = None
368
 
        if old_upstream_tree is not None:
369
 
          file_id = old_upstream_tree.path2id(config_filename)
370
 
        if file_id is not None:
371
 
          tree.add([config_filename], [file_id])
372
 
        else:
373
 
          tree.add([config_filename])
374
 
      if os.path.isfile(os.path.join(tree.basedir, 'debian', 'rules')):
375
 
        os.chmod(os.path.join(tree.basedir, 'debian', 'rules'),
376
 
                 (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|
377
 
                  stat.S_IXOTH))
378
 
      tree.commit('import package from %s' % (os.path.basename(origname)))
379
 
      upstream_version = version.upstream_version
380
 
      tree.branch.tags.set_tag(make_upstream_tag(upstream_version),
381
 
                               tree.branch.last_revision())
382
 
    finally:
383
 
      f.close()
384
 
 
385
 
  def _make_filter_proc(self):
386
 
    """Create a filterdiff subprocess."""
387
 
    filter_cmd = ['filterdiff'] + ignore_arguments
388
 
    filter_proc = Popen(filter_cmd, stdin=PIPE, stdout=PIPE)
389
 
    return filter_proc
390
 
 
391
 
  def _patch_tree(self, patch, basedir):
392
 
    """Patch a tree located at basedir."""
393
 
    filter_proc = self._make_filter_proc()
394
 
    patch_cmd =  ['patch', '-g', '0', '--strip', '1', '--quiet', '-f',
395
 
                  '--directory', basedir]
396
 
    patch_proc = Popen(patch_cmd, stdin=filter_proc.stdout, close_fds=True)
397
 
    for line in patch:
398
 
      filter_proc.stdin.write(line)
399
 
      filter_proc.stdin.flush()
400
 
    filter_proc.stdin.close()
401
 
    r = patch_proc.wait()
402
 
    if r != 0:
403
 
      raise BzrError('patch failed')
404
 
 
405
 
  def _get_touched_paths(self, patch):
406
 
    filter_proc = self._make_filter_proc()
407
 
    cmd = ['lsdiff', '--strip', '1']
408
 
    child_proc = Popen(cmd, stdin=filter_proc.stdout, stdout=PIPE,
409
 
                       close_fds=True)
410
 
    output = ''
411
 
    for line in patch:
412
 
      filter_proc.stdin.write(line)
413
 
      filter_proc.stdin.flush()
414
 
      while select.select([child_proc.stdout], [], [], 0)[0]:
415
 
          output += child_proc.stdout.read(1)
416
 
    filter_proc.stdin.close()
417
 
    output += child_proc.stdout.read()
418
 
    touched_paths = []
419
 
    for filename in output.split('\n'):
420
 
      if filename.endswith('\n'):
421
 
        filename = filename[:-1]
422
 
      touched_paths.append(filename)
423
 
    r = child_proc.wait()
424
 
    if r != 0:
425
 
      raise BzrError('lsdiff failed')
426
 
    return touched_paths
427
 
 
428
 
  def _add_implied_parents(self, tree, implied_parents, path,
429
 
                           file_ids_from=None):
430
 
    parent = os.path.dirname(path)
431
 
    if parent == '':
432
 
      return
433
 
    if parent in implied_parents:
434
 
      return
435
 
    implied_parents.add(parent)
436
 
    self._add_implied_parents(tree, implied_parents, parent,
437
 
                              file_ids_from=file_ids_from)
438
 
    if file_ids_from is None:
439
 
      tree.add([parent])
440
 
    else:
441
 
      file_id = file_ids_from.path2id(parent)
442
 
      if file_id is None:
443
 
        tree.add([parent])
444
 
      else:
445
 
        tree.add([parent], [file_id])
446
 
 
447
 
  def _update_path_info(self, tree, touched_paths, other_parent, main_parent):
448
 
    implied_parents = set()
449
 
    for path in touched_paths:
450
 
      if not tree.has_filename(path):
451
 
        tree.remove([path], verbose=False)
452
 
      elif not other_parent.has_filename(path):
453
 
        self._add_implied_parents(tree, implied_parents, path,
454
 
                                  file_ids_from=other_parent)
455
 
        tree.add([path])
456
 
      elif not (main_parent.has_filename(path) and
457
 
                other_parent.has_filename(path)):
458
 
        self._add_implied_parents(tree, implied_parents, path,
459
 
                                  file_ids_from=other_parent)
460
 
        file_id = other_parent.path2id(path)
461
 
        if file_id is None:
462
 
          tree.add([path])
463
 
        else:
464
 
          tree.add([path], [file_id])
465
 
 
466
 
  def _get_upstream_tree(self, version, tree):
467
 
    upstream_version = version.upstream_version
468
 
    up_revid = tree.branch.tags.lookup_tag(make_upstream_tag(upstream_version))
469
 
    return tree.branch.repository.revision_tree(up_revid)
470
 
 
471
 
 
472
 
  def import_diff(self, tree, diffname, version, dangling_revid=None,
473
 
                  transport=None, base_dir=None, no_add_extra_parent=False):
474
 
    up_tree = self._get_upstream_tree(version, tree)
475
 
    if dangling_revid is None:
476
 
      current_revid = tree.branch.last_revision()
477
 
    else:
478
 
      current_revid = dangling_revid
479
 
    current_tree = tree.branch.repository.revision_tree(current_revid)
480
 
    tree.revert(None, up_tree)
481
 
    f = open_file(diffname, transport, base_dir=base_dir)[0]
482
 
    f = gzip.GzipFile(fileobj=f)
483
 
    try:
484
 
      self._patch_tree(f, tree.basedir)
485
 
      if os.path.isfile(os.path.join(tree.basedir, 'debian', 'rules')):
486
 
        os.chmod(os.path.join(tree.basedir, 'debian', 'rules'),
487
 
                 (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|
488
 
                  stat.S_IXOTH))
489
 
      f.seek(0)
490
 
      touched_paths = self._get_touched_paths(f)
491
 
      self._update_path_info(tree, touched_paths, current_tree, up_tree)
492
 
      if (not no_add_extra_parent and dangling_revid is not None):
493
 
        tree.add_parent_tree_id(dangling_revid)
494
 
      tree.commit('merge packaging changes from %s' % \
495
 
                  (os.path.basename(diffname)))
496
 
    finally:
497
 
      f.close()
498
 
 
499
 
  def _add_to_safe(self, file, version, type, base, transport):
500
 
    found = False
501
 
    for safe_file in self.safe_files:
502
 
      if file == safe_file[0]:
503
 
        found = True
504
 
        break
505
 
    if not found:
506
 
      self.safe_files.append((file, version, type, base, transport))
507
 
 
508
 
  def _check_orig_exists(self, version):
509
 
    found = False
510
 
    for safe_file in self.safe_files:
511
 
      if safe_file[0].endswith("_%s.orig.tar.gz" % version.upstream_version):
512
 
        found = True
513
 
        break
514
 
    if found == False:
515
 
      raise ImportError("There is no upstream tarball corresponding to %s" % \
516
 
                          version)
517
 
 
518
 
  def _check_package_name(self, name):
519
 
    if self.package_name is not None and name != self.package_name:
520
 
      raise ImportError("The reported package name has changed from %s to "
521
 
                        "%s. I don't know what to do in this case. If this "
522
 
                        "case should be handled, please contact the author "
523
 
                        "with details of your case, and the expected outcome."
524
 
                        % (self.package_name, name))
525
 
    self.package_name = name
526
 
 
527
 
  def _decode_dsc(self, dsc, dscname, incremental=False):
528
 
    orig_file = None
529
 
    diff_file = None
530
 
    native_file = None
531
 
    self._check_package_name(dsc['Source'])
532
 
    for file_details in dsc['files']:
533
 
      name = file_details['name']
534
 
      if name.endswith('.orig.tar.gz'):
535
 
        if orig_file is not None:
536
 
          raise ImportError("%s contains more than one .orig.tar.gz" % dscname)
537
 
        orig_file = name
538
 
      elif name.endswith('.diff.gz'):
539
 
        if diff_file is not None:
540
 
          raise ImportError("%s contains more than one .diff.gz" % dscname)
541
 
        diff_file = name
542
 
      elif name.endswith('.tar.gz'):
543
 
        if native_file is not None:
544
 
          raise ImportError("%s contains more than one .tar.gz" % dscname)
545
 
        native_file = name
546
 
    version = Version(dsc['Version'])
547
 
    if self.transport is not None:
548
 
      base_dir = urlutils.split(dscname)[0]
549
 
    else:
550
 
      base_dir = None
551
 
    dsc_transport = self.cache.get_transport(dscname)
552
 
    if native_file is not None:
553
 
      if diff_file is not None or orig_file is not None:
554
 
        raise ImportError("%s contains both a native package and a normal "
555
 
                          "package." % dscname)
556
 
      self._add_to_safe(native_file, version, 'native', base_dir,
557
 
                        dsc_transport)
558
 
    else:
559
 
      if diff_file is None:
560
 
        raise ImportError("%s contains only a .orig.tar.gz, it must contain a "
561
 
                          ".diff.gz as well" % dscname)
562
 
      if orig_file is not None:
563
 
        self._add_to_safe(orig_file, version, 'orig', base_dir, dsc_transport)
564
 
      if not incremental:
565
 
        self._check_orig_exists(version)
566
 
      self._add_to_safe(diff_file, version, 'diff', base_dir, dsc_transport)
567
 
 
568
 
  def import_dsc(self, target_dir, orig_target=None):
569
 
    if os.path.exists(target_dir):
570
 
      raise FileExists(target_dir)
571
 
    self.orig_target = orig_target
572
 
    self.cache = DscCache(transport=self.transport)
573
 
    self.dsc_files.sort(cmp=DscComp(self.cache).cmp)
574
 
    self.safe_files = []
575
 
    self.package_name = None
576
 
    for dscname in self.dsc_files:
577
 
      dsc = self.cache.get_dsc(dscname)
578
 
      self._decode_dsc(dsc, dscname)
579
 
    os.mkdir(target_dir)
580
 
    format = bzrdir.format_registry.make_bzrdir('default')
581
 
    branch  = bzrdir.BzrDir.create_branch_convenience(target_dir,
582
 
                                                      format=format)
583
 
    tree = branch.bzrdir.open_workingtree()
584
 
    tree.lock_write()
585
 
    try:
586
 
      last_upstream = None
587
 
      dangling_revid = None
588
 
      last_native = False
589
 
      for (filename, version, type, base_dir, transport) in self.safe_files:
590
 
        if type == 'orig':
591
 
          if last_native:
592
 
            last_upstream = None
593
 
          dangling_revid = self.import_orig(tree, filename, version,
594
 
                                            last_upstream=last_upstream,
595
 
                                            transport=transport,
596
 
                                            base_dir=base_dir)
597
 
          info("imported %s" % filename)
598
 
          last_upstream = version.upstream_version
599
 
          last_native = False
600
 
        elif type == 'diff':
601
 
          self.import_diff(tree, filename, version,
602
 
                           dangling_revid=dangling_revid,
603
 
                           transport=transport, base_dir=base_dir)
604
 
          info("imported %s" % filename)
605
 
          dangling_revid = None
606
 
          last_native = False
607
 
        elif type == 'native':
608
 
          self.import_native(tree, filename, version,
609
 
                             last_upstream=last_upstream,
610
 
                             transport=transport, base_dir=base_dir)
611
 
          last_upstream = version.upstream_version
612
 
          last_native = True
613
 
          info("imported %s" % filename)
614
 
    finally:
615
 
      tree.unlock()
616
 
 
617
 
  def _find_last_upstream(self, tree, version):
618
 
    last_upstream = None
619
 
    previous_upstream = None
620
 
    tag_dict = tree.branch.tags.get_reverse_tag_dict()
621
 
    for rev in reversed(tree.branch.revision_history()):
622
 
      if rev in tag_dict:
623
 
        for poss_tag in tag_dict[rev]:
624
 
          poss_version = upstream_tag_to_version(poss_tag)
625
 
          if poss_version is None:
626
 
            continue
627
 
          if poss_version < version:
628
 
            return poss_version, previous_upstream
629
 
          previous_upstream = poss_version
630
 
    return None, previous_upstream
631
 
 
632
 
  def incremental_import_dsc(self, target, orig_target=None):
633
 
    self.orig_target = orig_target
634
 
    tree = WorkingTree.open_containing(target)[0]
635
 
    if tree.changes_from(tree.basis_tree()).has_changed():
636
 
      raise UncommittedChanges(tree)
637
 
    self.cache = DscCache(transport=self.transport)
638
 
    if len(self.dsc_files) > 1:
639
 
      raise OnlyImportSingleDsc
640
 
    self.safe_files = []
641
 
    self.package_name = None
642
 
    dsc = self.cache.get_dsc(self.dsc_files[0])
643
 
    self._decode_dsc(dsc, self.dsc_files[0], incremental=True)
644
 
    tree.lock_write()
645
 
    try:
646
 
      current_rev_id = tree.branch.last_revision()
647
 
      current_revno = tree.branch.revision_id_to_revno(current_rev_id)
648
 
      dangling_revid = None
649
 
      merge_base = None
650
 
      for (filename, version, type, base_dir, transport) in self.safe_files:
651
 
        if type == 'diff':
652
 
          self.import_diff(tree, filename, version,
653
 
                           dangling_revid=dangling_revid,
654
 
                           transport=transport, base_dir=base_dir,
655
 
                           no_add_extra_parent=True)
656
 
          info("imported %s" % filename)
657
 
          dangling_revid = tree.branch.last_revision()
658
 
          tree.pull(tree.branch, overwrite=True, stop_revision=current_rev_id)
659
 
          tree.merge_from_branch(tree.branch, to_revision=dangling_revid,
660
 
                  from_revision=merge_base)
661
 
        elif type == 'orig':
662
 
          dangling_tree = None
663
 
          last_upstream, previous_upstream = \
664
 
              self._find_last_upstream(tree, version)
665
 
          if last_upstream is None:
666
 
            dangling_revid = tree.branch.tags.lookup_tag(
667
 
                make_upstream_tag(previous_upstream))
668
 
            dangling_tree = tree.branch.repository.revision_tree(dangling_revid)
669
 
            tree.revert(None,
670
 
                tree.branch.repository.revision_tree(NULL_REVISION),
671
 
                backups=False)
672
 
            tree.set_parent_ids([])
673
 
            tree.branch.set_last_revision_info(0, NULL_REVISION)
674
 
          dangling_revid = self.import_orig(tree, filename, version,
675
 
                                            last_upstream=last_upstream,
676
 
                                            transport=transport,
677
 
                                            dangling_tree=dangling_tree,
678
 
                                            base_dir=base_dir)
679
 
          if last_upstream is None:
680
 
            merge_base = NULL_REVISION
681
 
            dangling_revid = current_rev_id
682
 
          info("imported %s" % filename)
683
 
        elif type == 'native':
684
 
          assert False, "Native packages not yet supported"
685
 
    finally:
686
 
      tree.unlock()
687
 
 
688
 
 
689
 
class SourcesImporter(DscImporter):
690
 
  """For importing all the .dsc files from a Sources file."""
691
 
 
692
 
  def __init__(self, base, sources_path, other_sources=[]):
693
 
    """Create a SourcesImporter.
694
 
 
695
 
    :param base: the base URI from which all paths should be interpreted.
696
 
    :type base: string
697
 
    :param sources_path: the path to the Sources file to import the
698
 
                         packages from, relative to the base parameter.
699
 
    :type base: string
700
 
    """
701
 
    self.base = urlutils.normalize_url(base)
702
 
    if isinstance(sources_path, unicode):
703
 
      sources_path = sources_path.encode('utf-8')
704
 
    self.sources_path = sources_path
705
 
    self.transport = get_transport(self.base)
706
 
    sources_file = self.transport.get(self.sources_path)
707
 
    if self.sources_path.endswith(".gz"):
708
 
      sources_file = gzip.GzipFile(fileobj=sources_file)
709
 
    dsc_files = []
710
 
    for source in deb822.Sources.iter_paragraphs(sources_file):
711
 
      base_dir = source['Directory']
712
 
      if not self._check_basedir(base_dir):
713
 
        continue
714
 
      for file_info in source['files']:
715
 
        name = file_info['name']
716
 
        if name.endswith('.dsc'):
717
 
          dsc_files.append(urlutils.join(base_dir, name))
718
 
    dsc_files += other_sources
719
 
    super(SourcesImporter, self).__init__(dsc_files)
720
 
 
721
 
  def _check_basedir(self, base_dir):
722
 
    return True
723
 
 
724
 
 
725
 
class SnapshotImporter(SourcesImporter):
726
 
  """Import all versions of a package recorded on snapshot.debian.net."""
727
 
 
728
 
  def __init__(self, package_name, other_sources=[]):
729
 
    base = 'http://snapshot.debian.net/archive/'
730
 
    path = 'pool/%s/%s/source/Sources.gz' % (package_name[0], package_name)
731
 
    super(SnapshotImporter, self).__init__(base, path, other_sources=other_sources)
732
 
    warning("snapshot.debian.net has lost packages from before 12/03/2005, "
733
 
            "only packages from after that date will be imported.")
734
 
 
735
 
  def _check_basedir(self, base_dir):
736
 
    import re
737
 
    match = re.match(r'(?P<year>\d\d\d\d)/(?P<month>\d\d)/(?P<day>\d\d)',
738
 
                     base_dir)
739
 
    if match is not None:
740
 
      year = int(match.group('year'))
741
 
      if year < 2005:
742
 
        return False
743
 
      if year == 2005:
744
 
        month = int(match.group('month'))
745
 
        if month < 3:
746
 
          return False
747
 
        if month == 3:
748
 
          day = int(match.group('day'))
749
 
          if day < 13:
 
769
            prop = rev.properties["deb-native"]
 
770
            return prop == "True"
 
771
        except KeyError:
750
772
            return False
751
 
    return True
752
 
 
753
 
# vim: ts=2 sts=2 sw=2
754
 
 
 
773
 
 
774
    def branch_to_pull_version_from(self, version, md5):
 
775
        """Checks whether this upload is a pull from a lesser branch.
 
776
 
 
777
        Looks in all the lesser branches for the given version/md5 pair
 
778
        in a branch that has not diverged from this.
 
779
 
 
780
        If it is present in another branch that has not diverged this
 
781
        method will return the greatest branch that it is present in,
 
782
        otherwise it will return None. If it returns a branch then it
 
783
        indicates that a pull should be done from that branch, rather
 
784
        than importing the version as a new revision in this branch.
 
785
 
 
786
        :param version: the Version object to look for in the lesser
 
787
            branches.
 
788
        :param md5: a String containing the md5 associateed with the
 
789
            version.
 
790
        :return: a DistributionBranch object to pull from if that is
 
791
            what should be done, otherwise None.
 
792
        """
 
793
        assert md5 is not None, \
 
794
            ("It's not a good idea to use branch_to_pull_version_from with "
 
795
             "md5 == None, as you may pull the wrong revision.")
 
796
        self.branch.lock_read()
 
797
        try:
 
798
            for branch in reversed(self.get_lesser_branches()):
 
799
                if branch.has_version(version, md5=md5):
 
800
                    # Check that they haven't diverged
 
801
                    branch.branch.lock_read()
 
802
                    try:
 
803
                        graph = branch.branch.repository.get_graph(
 
804
                                self.branch.repository)
 
805
                        other_revid = branch.revid_of_version(version)
 
806
                        if len(graph.heads([other_revid,
 
807
                                    self.branch.last_revision()])) == 1:
 
808
                            return branch
 
809
                    finally:
 
810
                        branch.branch.unlock()
 
811
            for branch in self.get_greater_branches():
 
812
                if branch.has_version(version, md5=md5):
 
813
                    # Check that they haven't diverged
 
814
                    branch.branch.lock_read()
 
815
                    try:
 
816
                        graph = branch.branch.repository.get_graph(
 
817
                                self.branch.repository)
 
818
                        other_revid = branch.revid_of_version(version)
 
819
                        if len(graph.heads([other_revid,
 
820
                                    self.branch.last_revision()])) == 1:
 
821
                            return branch
 
822
                    finally:
 
823
                        branch.branch.unlock()
 
824
            return None
 
825
        finally:
 
826
            self.branch.unlock()
 
827
 
 
828
    def branch_to_pull_upstream_from(self, version, md5):
 
829
        """Checks whether this upstream is a pull from a lesser branch.
 
830
 
 
831
        Looks in all the other upstream branches for the given
 
832
        version/md5 pair in a branch that has not diverged from this.
 
833
        If it is present in a lower branch this method will return the
 
834
        greatest branch that it is present in that has not diverged,
 
835
        otherwise it will return None. If it returns a branch then it
 
836
        indicates that a pull should be done from that branch, rather
 
837
        than importing the upstream as a new revision in this branch.
 
838
 
 
839
        :param version: the upstream version to use when searching in the 
 
840
            lesser branches.
 
841
        :param md5: a String containing the md5 associateed with the
 
842
            upstream version.
 
843
        :return: a DistributionBranch object to pull the upstream from
 
844
            if that is what should be done, otherwise None.
 
845
        """
 
846
        assert isinstance(version, str)
 
847
        assert md5 is not None, \
 
848
            ("It's not a good idea to use branch_to_pull_upstream_from with "
 
849
             "md5 == None, as you may pull the wrong revision.")
 
850
        up_branch = self.upstream_branch
 
851
        up_branch.lock_read()
 
852
        try:
 
853
            for branch in reversed(self.get_lesser_branches()):
 
854
                if branch.has_upstream_version(version, md5=md5):
 
855
                    # Check for divergenge.
 
856
                    other_up_branch = branch.upstream_branch
 
857
                    other_up_branch.lock_read()
 
858
                    try:
 
859
                        graph = other_up_branch.repository.get_graph(
 
860
                                up_branch.repository)
 
861
                        other_revid = branch.revid_of_upstream_version(
 
862
                                version)
 
863
                        if len(graph.heads([other_revid,
 
864
                                    up_branch.last_revision()])) == 1:
 
865
                            return branch
 
866
                    finally:
 
867
                        other_up_branch.unlock()
 
868
            for branch in self.get_greater_branches():
 
869
                if branch.has_upstream_version(version, md5=md5):
 
870
                    # Check for divergenge.
 
871
                    other_up_branch = branch.upstream_branch
 
872
                    other_up_branch.lock_read()
 
873
                    try:
 
874
                        graph = other_up_branch.repository.get_graph(
 
875
                                up_branch.repository)
 
876
                        other_revid = branch.revid_of_upstream_version(
 
877
                                version)
 
878
                        if len(graph.heads([other_revid,
 
879
                                    up_branch.last_revision()])) == 1:
 
880
                            return branch
 
881
                    finally:
 
882
                        other_up_branch.unlock()
 
883
            return None
 
884
        finally:
 
885
            up_branch.unlock()
 
886
 
 
887
    def get_parents(self, versions):
 
888
        """Return the list of parents for a specific version.
 
889
 
 
890
        This method returns the list of revision ids that should be parents
 
891
        for importing a specifc package version. The specific package version
 
892
        is the first element of the list of versions passed.
 
893
 
 
894
        The parents are determined by looking at the other versions in the
 
895
        passed list and examining which of the branches (if any) they are
 
896
        already present in.
 
897
 
 
898
        You should probably use get_parents_with_upstream rather than
 
899
        this method.
 
900
 
 
901
        :param versions: a list of Version objects, the first item of
 
902
            which is the version of the package that is currently being
 
903
            imported.
 
904
        :return: a list of tuples of (DistributionBranch, version,
 
905
            revision id). The revision ids should all be parents of the
 
906
            revision that imports the specified version of the package.
 
907
            The versions are the versions that correspond to that revision
 
908
            id. The DistributionBranch is the branch that contains that
 
909
            version.
 
910
        """
 
911
        assert len(versions) > 0, "Need a version to import"
 
912
        mutter("Getting parents of %s" % str(versions))
 
913
        missing_versions = self.missing_versions(versions)
 
914
        mutter("Versions we don't have are %s" % str(missing_versions))
 
915
        last_contained_version = self.last_contained_version(versions)
 
916
        parents = []
 
917
        if last_contained_version is not None:
 
918
            assert last_contained_version != versions[0], \
 
919
                "Reupload of a version?"
 
920
            mutter("The last versions we do have is %s" \
 
921
                    % str(last_contained_version))
 
922
            parents = [(self, last_contained_version,
 
923
                    self.revid_of_version(last_contained_version))]
 
924
        else:
 
925
            mutter("We don't have any of those versions")
 
926
        for branch in reversed(self.get_lesser_branches()):
 
927
            merged, missing_versions = \
 
928
                branch.contained_versions(missing_versions)
 
929
            if merged:
 
930
                revid = branch.revid_of_version(merged[0])
 
931
                parents.append((branch, merged[0], revid))
 
932
                mutter("Adding merge from lesser of %s for version %s"
 
933
                        % (revid, str(merged[0])))
 
934
                #FIXME: should this really be here?
 
935
                branch.branch.tags.merge_to(self.branch.tags)
 
936
                self.branch.fetch(branch.branch,
 
937
                        last_revision=revid)
 
938
        for branch in self.get_greater_branches():
 
939
            merged, missing_versions = \
 
940
                branch.contained_versions(missing_versions)
 
941
            if merged:
 
942
                revid = branch.revid_of_version(merged[0])
 
943
                parents.append((branch, merged[0], revid))
 
944
                mutter("Adding merge from greater of %s for version %s"
 
945
                    % (revid, str(merged[0])))
 
946
                #FIXME: should this really be here?
 
947
                branch.branch.tags.merge_to(self.branch.tags)
 
948
                self.branch.fetch(branch.branch,
 
949
                        last_revision=revid)
 
950
        return parents
 
951
 
 
952
    def pull_upstream_from_branch(self, pull_branch, version):
 
953
        """Pulls an upstream version from a branch.
 
954
 
 
955
        Given a DistributionBranch and a version number this method
 
956
        will pull the upstream part of the given version from the
 
957
        branch in to this. The upstream version must be present
 
958
        in the DistributionBranch, and it is assumed that the md5
 
959
        matches.
 
960
 
 
961
        It sets the necessary tags so that the pulled version is
 
962
        recognised as being part of this branch.
 
963
 
 
964
        :param pull_branch: the DistributionBranch to pull from.
 
965
        :param version: the upstream version string
 
966
        """
 
967
        assert isinstance(version, str)
 
968
        pull_revision = pull_branch.revid_of_upstream_version(version)
 
969
        mutter("Pulling upstream part of %s from revision %s" % \
 
970
                (version, pull_revision))
 
971
        up_pull_branch = pull_branch.upstream_branch
 
972
        assert self.upstream_tree is not None, \
 
973
            "Can't pull upstream with no tree"
 
974
        self.upstream_tree.pull(up_pull_branch,
 
975
                stop_revision=pull_revision)
 
976
        self.tag_upstream_version(version)
 
977
        self.branch.fetch(self.upstream_branch, last_revision=pull_revision)
 
978
        self.upstream_branch.tags.merge_to(self.branch.tags)
 
979
 
 
980
    def pull_version_from_branch(self, pull_branch, version, native=False):
 
981
        """Pull a version from a particular branch.
 
982
 
 
983
        Given a DistributionBranch and a version number this method
 
984
        will pull the given version from the branch in to this. The
 
985
        version must be present in the DistributionBranch, and it
 
986
        is assumed that the md5 matches.
 
987
 
 
988
        It will also pull in any upstream part that is needed to
 
989
        the upstream branch. It is assumed that the md5 matches
 
990
        here as well. If the upstream version must be present in
 
991
        at least one of the upstream branches.
 
992
 
 
993
        It sets the necessary tags on the revisions so they are
 
994
        recongnised in this branch as well.
 
995
 
 
996
        :param pull_branch: the DistributionBranch to pull from.
 
997
        :param version: the Version to pull.
 
998
        :param native: whether it is a native version that is being
 
999
            imported.
 
1000
        """
 
1001
        pull_revision = pull_branch.revid_of_version(version)
 
1002
        mutter("already has version %s so pulling from revision %s"
 
1003
                % (str(version), pull_revision))
 
1004
        assert self.tree is not None, "Can't pull branch with no tree"
 
1005
        self.tree.pull(pull_branch.branch, stop_revision=pull_revision)
 
1006
        self.tag_version(version)
 
1007
        if not native and not self.has_upstream_version(version.upstream_version):
 
1008
            if pull_branch.has_upstream_version(version.upstream_version):
 
1009
                self.pull_upstream_from_branch(pull_branch, 
 
1010
                    version.upstream_version)
 
1011
            else:
 
1012
                assert False, ("Can't find the needed upstream part "
 
1013
                        "for version %s" % version)
 
1014
        if (native and self.upstream_branch.last_revision() == NULL_REVISION
 
1015
            and pull_branch.upstream_branch.last_revision() != NULL_REVISION):
 
1016
            # in case the package wasn't native before then we pull
 
1017
            # the upstream. These checks may be a bit restrictive.
 
1018
            self.upstream_tree.pull(pull_branch.upstream_branch)
 
1019
            pull_branch.upstream_branch.tags.merge_to(self.upstream_branch.tags)
 
1020
        elif native:
 
1021
            mutter("Not checking for upstream as it is a native package")
 
1022
        else:
 
1023
            mutter("Not importing the upstream part as it is already "
 
1024
                    "present in the upstream branch")
 
1025
 
 
1026
    def get_parents_with_upstream(self, version, versions,
 
1027
            force_upstream_parent=False):
 
1028
        """Get the list of parents including any upstream parents.
 
1029
 
 
1030
        Further to get_parents this method includes any upstream parents
 
1031
        that are needed. An upstream parent is needed if none of
 
1032
        the other parents include the upstream version. The needed
 
1033
        upstream must already present in the upstream branch before
 
1034
        calling this method.
 
1035
 
 
1036
        If force_upstream_parent is True then the upstream parent will
 
1037
        be included, even if another parent is already using that
 
1038
        upstream. This is for use in cases where the .orig.tar.gz
 
1039
        is different in two ditributions.
 
1040
 
 
1041
        :param version: the Version that we are currently importing.
 
1042
        :param versions: the list of Versions that are ancestors of
 
1043
            version, including version itself. Sorted with the latest
 
1044
            versions first, so version must be the first entry.
 
1045
        :param force_upstream_parent: if True then an upstream parent
 
1046
            will be added as the first parent, regardless of what the
 
1047
            other parents are.
 
1048
        :return: a list of revision ids that should be the parents when
 
1049
            importing the specified revision.
 
1050
        """
 
1051
        assert version == versions[0], \
 
1052
            "version is not the first entry of versions"
 
1053
        parents = self.get_parents(versions)
 
1054
        need_upstream_parent = True
 
1055
        if not force_upstream_parent:
 
1056
            for parent_pair in parents:
 
1057
                if (parent_pair[1].upstream_version == \
 
1058
                        version.upstream_version):
 
1059
                    need_upstream_parent = False
 
1060
                    break
 
1061
        real_parents = [p[2] for p in parents]
 
1062
        if need_upstream_parent:
 
1063
            parent_revid = self.revid_of_upstream_version(version.upstream_version)
 
1064
            if len(parents) > 0:
 
1065
                real_parents.insert(1, parent_revid)
 
1066
            else:
 
1067
                real_parents = [parent_revid]
 
1068
        return real_parents
 
1069
 
 
1070
    def _fetch_upstream_to_branch(self, revid):
 
1071
        """Fetch the revision from the upstream branch in to the packaging one.
 
1072
        """
 
1073
        # Make sure we see any revisions added by the upstream branch
 
1074
        # since self.tree was locked.
 
1075
        self.branch.repository.refresh_data()
 
1076
        self.branch.fetch(self.upstream_branch, last_revision=revid)
 
1077
        self.upstream_branch.tags.merge_to(self.branch.tags)
 
1078
 
 
1079
    def import_upstream(self, upstream_part, version, md5, upstream_parents,
 
1080
            upstream_tarball=None, upstream_branch=None,
 
1081
            upstream_revision=None, timestamp=None, author=None):
 
1082
        """Import an upstream part on to the upstream branch.
 
1083
 
 
1084
        This imports the upstream part of the code and places it on to
 
1085
        the upstream branch, setting the necessary tags.
 
1086
 
 
1087
        :param upstream_part: the path of a directory containing the
 
1088
            unpacked upstream part of the source package.
 
1089
        :param version: upstream version that is being imported
 
1090
        :param md5: the md5 of the upstream part.
 
1091
        :param upstream_parents: the parents to give the upstream revision
 
1092
        :param timestamp: a tuple of (timestamp, timezone) to use for
 
1093
            the commit, or None to use the current time.
 
1094
        """
 
1095
        # Should we just dump the upstream part on whatever is currently
 
1096
        # there, or try and pull all of the other upstream versions
 
1097
        # from lesser branches first? For now we'll just dump it on.
 
1098
        # TODO: this method needs a lot of work for when we will make
 
1099
        # the branches writeable by others.
 
1100
        assert isinstance(version, str)
 
1101
        mutter("Importing upstream version %s from %s with parents %s" \
 
1102
                % (version, upstream_part, str(upstream_parents)))
 
1103
        assert self.upstream_tree is not None, \
 
1104
            "Can't import upstream with no tree"
 
1105
        if len(upstream_parents) > 0:
 
1106
            parent_revid = upstream_parents[0]
 
1107
        else:
 
1108
            parent_revid = NULL_REVISION
 
1109
        self.upstream_tree.pull(self.upstream_tree.branch, overwrite=True,
 
1110
                stop_revision=parent_revid)
 
1111
        other_branches = self.get_other_branches()
 
1112
        def get_last_revision_tree(br):
 
1113
            return br.repository.revision_tree(br.last_revision())
 
1114
        upstream_trees = [get_last_revision_tree(o.upstream_branch)
 
1115
            for o in other_branches]
 
1116
        if upstream_branch is not None:
 
1117
            if upstream_revision is None:
 
1118
                upstream_revision = upstream_branch.last_revision()
 
1119
            self.upstream_branch.fetch(upstream_branch,
 
1120
                    last_revision=upstream_revision)
 
1121
            upstream_branch.tags.merge_to(self.upstream_branch.tags)
 
1122
            upstream_parents.append(upstream_revision)
 
1123
            upstream_trees.insert(0,
 
1124
                    self.upstream_branch.repository.revision_tree(
 
1125
                        upstream_revision))
 
1126
        import_dir(self.upstream_tree, upstream_part,
 
1127
                file_ids_from=upstream_trees + [self.tree])
 
1128
        self.upstream_tree.set_parent_ids(upstream_parents)
 
1129
        revprops = {"deb-md5": md5}
 
1130
        if upstream_tarball is not None:
 
1131
            delta = self.make_pristine_tar_delta(self.upstream_tree,
 
1132
                    upstream_tarball)
 
1133
            uuencoded = standard_b64encode(delta)
 
1134
            revprops["deb-pristine-delta"] = uuencoded
 
1135
        if author is not None:
 
1136
            revprops['authors'] = author
 
1137
        timezone=None
 
1138
        if timestamp is not None:
 
1139
            timezone = timestamp[1]
 
1140
            timestamp = timestamp[0]
 
1141
        revid = self.upstream_tree.commit("Import upstream version %s" \
 
1142
                % (version,),
 
1143
                revprops=revprops, timestamp=timestamp, timezone=timezone)
 
1144
        self.tag_upstream_version(version)
 
1145
        return revid
 
1146
 
 
1147
    def _mark_native_config(self, native):
 
1148
        poss_native_tree = self.branch.repository.revision_tree(
 
1149
                self.branch.last_revision())
 
1150
        current_native = self._is_tree_native(poss_native_tree)
 
1151
        current_config = self._default_config_for_tree(poss_native_tree)
 
1152
        dirname = os.path.join(self.tree.basedir,
 
1153
                '.bzr-builddeb')
 
1154
        if current_config is not None:
 
1155
            # Add that back to the current tree
 
1156
            if not os.path.exists(dirname):
 
1157
                os.mkdir(dirname)
 
1158
            current_config.filename = os.path.join(dirname,
 
1159
                    'default.conf')
 
1160
            current_config.write()
 
1161
            dir_id = poss_native_tree.path2id('.bzr-builddeb')
 
1162
            file_id = poss_native_tree.path2id(
 
1163
                    '.bzr-builddeb/default.conf')
 
1164
            self.tree.add(['.bzr-builddeb/',
 
1165
                    '.bzr-builddeb/default.conf'],
 
1166
                    ids=[dir_id, file_id])
 
1167
        if native != current_native:
 
1168
            if current_config is None:
 
1169
                needs_add = True
 
1170
                if native:
 
1171
                    current_config = ConfigObj()
 
1172
                    current_config['BUILDDEB'] = {}
 
1173
            if current_config is not None:
 
1174
                if native:
 
1175
                    current_config['BUILDDEB']['native'] = True
 
1176
                else:
 
1177
                    del current_config['BUILDDEB']['native']
 
1178
                    if len(current_config['BUILDDEB']) == 0:
 
1179
                        del current_config['BUILDDEB']
 
1180
                if len(current_config) == 0:
 
1181
                    self.tree.remove(['.bzr-builddeb',
 
1182
                            '.bzr-builddeb/default.conf'],
 
1183
                            keep_files=False)
 
1184
                else:
 
1185
                    if needs_add:
 
1186
                        os.mkdir(dirname)
 
1187
                    current_config.filename = os.path.join(dirname,
 
1188
                            'default.conf')
 
1189
                    current_config.write()
 
1190
                    if needs_add:
 
1191
                        self.tree.add(['.bzr-builddeb/',
 
1192
                                '.bzr-builddeb/default.conf'])
 
1193
 
 
1194
    def import_debian(self, debian_part, version, parents, md5,
 
1195
            native=False, timestamp=None):
 
1196
        """Import the debian part of a source package.
 
1197
 
 
1198
        :param debian_part: the path of a directory containing the unpacked
 
1199
            source package.
 
1200
        :param version: the Version of the source package.
 
1201
        :param parents: a list of revision ids that should be the
 
1202
            parents of the imported revision.
 
1203
        :param md5: the md5 sum reported by the .dsc for
 
1204
            the .diff.gz part of this source package.
 
1205
        :param native: whether the package is native.
 
1206
        :param timestamp: a tuple of (timestamp, timezone) to use for
 
1207
            the commit, or None to use the current values.
 
1208
        """
 
1209
        mutter("Importing debian part for version %s from %s, with parents "
 
1210
                "%s" % (str(version), debian_part, str(parents)))
 
1211
        assert self.tree is not None, "Can't import with no tree"
 
1212
        # First we move the branch to the first parent
 
1213
        if parents:
 
1214
            if self.branch.last_revision() == NULL_REVISION:
 
1215
                parent_revid = parents[0]
 
1216
                self.tree.pull(self.tree.branch, overwrite=True,
 
1217
                        stop_revision=parent_revid)
 
1218
            elif parents[0] != self.branch.last_revision():
 
1219
                mutter("Adding current tip as parent: %s"
 
1220
                        % self.branch.last_revision())
 
1221
                parents.insert(0, self.branch.last_revision())
 
1222
        elif self.branch.last_revision() != NULL_REVISION:
 
1223
            # We were told to import with no parents. That's not
 
1224
            # right, so import with the current parent. Should
 
1225
            # perhaps be fixed in the methods to determine the parents.
 
1226
            mutter("Told to import with no parents. Adding current tip "
 
1227
                   "as the single parent")
 
1228
            parents = [self.branch.last_revision()]
 
1229
        other_branches = self.get_other_branches()
 
1230
        def get_last_revision_tree(br):
 
1231
            return br.repository.revision_tree(br.last_revision())
 
1232
        debian_trees = [get_last_revision_tree(o.branch)
 
1233
            for o in other_branches]
 
1234
        parent_trees = []
 
1235
        for parent in parents:
 
1236
            parent_trees.append(self.branch.repository.revision_tree(
 
1237
                        parent))
 
1238
        import_dir(self.tree, debian_part,
 
1239
                file_ids_from=parent_trees + debian_trees)
 
1240
        rules_path = os.path.join(self.tree.basedir, 'debian', 'rules')
 
1241
        if os.path.isfile(rules_path):
 
1242
            os.chmod(rules_path,
 
1243
                     (stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|
 
1244
                      stat.S_IROTH|stat.S_IXOTH))
 
1245
        self.tree.set_parent_ids(parents)
 
1246
        changelog_path = os.path.join(self.tree.basedir, 'debian',
 
1247
                'changelog')
 
1248
        if os.path.exists(changelog_path):
 
1249
            f = open(changelog_path)
 
1250
            try:
 
1251
                changelog_contents = f.read()
 
1252
            finally:
 
1253
                f.close()
 
1254
            changelog = Changelog(file=changelog_contents, max_blocks=1)
 
1255
        message, authors, thanks, bugs = \
 
1256
                get_commit_info_from_changelog(changelog, self.branch)
 
1257
        if message is None:
 
1258
            message = 'Import packaging changes for version %s' % \
 
1259
                        (str(version),)
 
1260
        revprops={"deb-md5":md5}
 
1261
        if native:
 
1262
            revprops['deb-native'] = "True"
 
1263
        if authors:
 
1264
            revprops['authors'] = "\n".join(authors)
 
1265
        if thanks:
 
1266
            revprops['deb-thanks'] = "\n".join(thanks)
 
1267
        if bugs:
 
1268
            revprops['bugs'] = "\n".join(bugs)
 
1269
        timezone = None
 
1270
        if timestamp is not None:
 
1271
            timezone = timestamp[1]
 
1272
            timestamp = timestamp[0]
 
1273
        self._mark_native_config(native)
 
1274
        self.tree.commit(message, revprops=revprops, timestamp=timestamp,
 
1275
                timezone=timezone)
 
1276
        self.tag_version(version)
 
1277
 
 
1278
    def _get_dsc_part(self, dsc, end):
 
1279
        """Get the path and md5 of a file ending with end in dsc."""
 
1280
        files = dsc['files']
 
1281
        for file_info in files:
 
1282
            name = file_info['name']
 
1283
            if name.endswith(end):
 
1284
                filename = name
 
1285
                md5 = file_info['md5sum']
 
1286
                return (filename, md5)
 
1287
        return (None, None)
 
1288
 
 
1289
    def get_upstream_part(self, dsc):
 
1290
        """Gets the information about the upstream part from the dsc.
 
1291
 
 
1292
        :param dsc: a deb822.Dsc object to take the information from.
 
1293
        :return: a tuple (path, md5), both strings, the former being
 
1294
            the path to the .orig.tar.gz, the latter being the md5
 
1295
            reported for it. If there is no upstream part both will
 
1296
            be None.
 
1297
        """
 
1298
        return self._get_dsc_part(dsc, ".orig.tar.gz")
 
1299
 
 
1300
    def get_diff_part(self, dsc):
 
1301
        """Gets the information about the diff part from the dsc.
 
1302
 
 
1303
        :param dsc: a deb822.Dsc object to take the information from.
 
1304
        :return: a tuple (path, md5), both strings, the former being
 
1305
            the path to the .diff.gz, the latter being the md5
 
1306
            reported for it. If there is no diff part both will be
 
1307
            None.
 
1308
        """
 
1309
        return self._get_dsc_part(dsc, ".diff.gz")
 
1310
 
 
1311
    def get_native_part(self, dsc):
 
1312
        """Gets the information about the native part from the dsc.
 
1313
 
 
1314
        :param dsc: a deb822.Dsc object to take the information from.
 
1315
        :return: a tuple (path, md5), both strings, the former being
 
1316
            the path to the .tar.gz, the latter being the md5 reported
 
1317
            for it. If there is not native part both will be None.
 
1318
        """
 
1319
        (path, md5) = self._get_dsc_part(dsc, ".tar.gz")
 
1320
        assert not path.endswith(".orig.tar.gz")
 
1321
        return (path, md5)
 
1322
 
 
1323
    def upstream_parents(self, versions, version):
 
1324
        """Get the parents for importing a new upstream.
 
1325
 
 
1326
        The upstream parents will be the last upstream version,
 
1327
        except for some cases when the last version was native.
 
1328
 
 
1329
        :return: the list of revision ids to use as parents when
 
1330
            importing the specified upstream version.
 
1331
        """
 
1332
        parents = []
 
1333
        first_parent = self.upstream_branch.last_revision()
 
1334
        if first_parent != NULL_REVISION:
 
1335
            parents = [first_parent]
 
1336
        last_contained_version = self.last_contained_version(versions)
 
1337
        if last_contained_version is not None:
 
1338
            # If the last version was native, and was not from the same
 
1339
            # upstream as a non-native version (i.e. it wasn't a mistaken
 
1340
            # native -2 version), then we want to add an extra parent.
 
1341
            if (self.is_version_native(last_contained_version)
 
1342
                and not self.has_upstream_version(last_contained_version.upstream_version)):
 
1343
                revid = self.revid_of_version(last_contained_version)
 
1344
                parents.append(revid)
 
1345
                self.upstream_branch.fetch(self.branch,
 
1346
                        last_revision=revid)
 
1347
        pull_parents = self.get_parents(versions)
 
1348
        if ((first_parent == NULL_REVISION and len(pull_parents) > 0)
 
1349
                or len(pull_parents) > 1):
 
1350
            if first_parent == NULL_REVISION:
 
1351
                pull_branch = pull_parents[0][0]
 
1352
                pull_version = pull_parents[0][1]
 
1353
            else:
 
1354
                pull_branch = pull_parents[1][0]
 
1355
                pull_version = pull_parents[1][1]
 
1356
            if not pull_branch.is_version_native(pull_version):
 
1357
                    pull_revid = \
 
1358
                        pull_branch.revid_of_upstream_version(pull_version.upstream_version)
 
1359
                    mutter("Initialising upstream from %s, version %s" \
 
1360
                        % (str(pull_branch), str(pull_version)))
 
1361
                    parents.append(pull_revid)
 
1362
                    self.upstream_branch.fetch(
 
1363
                            pull_branch.upstream_branch,
 
1364
                            last_revision=pull_revid)
 
1365
                    pull_branch.upstream_branch.tags.merge_to(
 
1366
                            self.upstream_branch.tags)
 
1367
        return parents
 
1368
 
 
1369
    def get_changelog_from_source(self, dir):
 
1370
        cl_filename = os.path.join(dir, "debian", "changelog")
 
1371
        cl = Changelog()
 
1372
        cl.parse_changelog(open(cl_filename).read(), strict=False)
 
1373
        return cl
 
1374
 
 
1375
    def extract_dsc(self, dsc_filename):
 
1376
        """Extract a dsc file in to a temporary directory."""
 
1377
        tempdir = tempfile.mkdtemp()
 
1378
        dsc_filename = os.path.abspath(dsc_filename)
 
1379
        proc = Popen("dpkg-source -su -x %s" % (dsc_filename,), shell=True,
 
1380
                cwd=tempdir, stdout=PIPE, stderr=PIPE,
 
1381
                preexec_fn=subprocess_setup)
 
1382
        (stdout, stderr) = proc.communicate()
 
1383
        assert proc.returncode == 0, "dpkg-source -x failed, output:\n%s\n%s" % \
 
1384
                    (stdout, stderr)
 
1385
        return tempdir
 
1386
 
 
1387
    def _do_import_package(self, version, versions, debian_part, md5,
 
1388
            upstream_part, upstream_md5, upstream_tarball=None,
 
1389
            timestamp=None, author=None):
 
1390
        pull_branch = self.branch_to_pull_version_from(version, md5)
 
1391
        if pull_branch is not None:
 
1392
            if (self.branch_to_pull_upstream_from(version.upstream_version,
 
1393
                        upstream_md5)
 
1394
                    is None):
 
1395
                pull_branch = None
 
1396
        if pull_branch is not None:
 
1397
            self.pull_version_from_branch(pull_branch, version)
 
1398
        else:
 
1399
            # We need to import at least the diff, possibly upstream.
 
1400
            # Work out if we need the upstream part first.
 
1401
            imported_upstream = False
 
1402
            if not self.has_upstream_version(version.upstream_version):
 
1403
                up_pull_branch = \
 
1404
                    self.branch_to_pull_upstream_from(version.upstream_version,
 
1405
                            upstream_md5)
 
1406
                if up_pull_branch is not None:
 
1407
                    self.pull_upstream_from_branch(up_pull_branch,
 
1408
                            version.upstream_version)
 
1409
                else:
 
1410
                    imported_upstream = True
 
1411
                    # Check whether we should pull first if this initialises
 
1412
                    # from another branch:
 
1413
                    upstream_parents = self.upstream_parents(versions,
 
1414
                            version.upstream_version)
 
1415
                    new_revid = self.import_upstream(upstream_part,
 
1416
                            version.upstream_version,
 
1417
                            upstream_md5, upstream_parents,
 
1418
                            upstream_tarball=upstream_tarball,
 
1419
                            timestamp=timestamp, author=author)
 
1420
                    self._fetch_upstream_to_branch(new_revid)
 
1421
            else:
 
1422
                mutter("We already have the needed upstream part")
 
1423
            parents = self.get_parents_with_upstream(version, versions,
 
1424
                    force_upstream_parent=imported_upstream)
 
1425
            # Now we have the list of parents we need to import the .diff.gz
 
1426
            self.import_debian(debian_part, version, parents, md5,
 
1427
                    timestamp=timestamp)
 
1428
 
 
1429
    def get_native_parents(self, version, versions):
 
1430
        last_contained_version = self.last_contained_version(versions)
 
1431
        if last_contained_version is None:
 
1432
            parents = []
 
1433
        else:
 
1434
            parents = [self.revid_of_version(last_contained_version)]
 
1435
        missing_versions = self.missing_versions(versions)
 
1436
        for branch in reversed(self.get_lesser_branches()):
 
1437
            merged, missing_versions = \
 
1438
                branch.contained_versions(missing_versions)
 
1439
            if merged:
 
1440
                revid = branch.revid_of_version(merged[0])
 
1441
                parents.append(revid)
 
1442
                #FIXME: should this really be here?
 
1443
                branch.branch.tags.merge_to(self.branch.tags)
 
1444
                self.branch.fetch(branch.branch,
 
1445
                        last_revision=revid)
 
1446
                if self.upstream_branch.last_revision() == NULL_REVISION:
 
1447
                    self.upstream_tree.pull(branch.upstream_branch)
 
1448
                    branch.upstream_branch.tags.merge_to(self.upstream_branch.tags)
 
1449
        for branch in self.get_greater_branches():
 
1450
            merged, missing_versions = \
 
1451
                branch.contained_versions(missing_versions)
 
1452
            if merged:
 
1453
                revid = branch.revid_of_version(merged[0])
 
1454
                parents.append(revid)
 
1455
                #FIXME: should this really be here?
 
1456
                branch.branch.tags.merge_to(self.branch.tags)
 
1457
                self.branch.fetch(branch.branch,
 
1458
                        last_revision=revid)
 
1459
                if self.upstream_branch.last_revision() == NULL_REVISION:
 
1460
                    self.upstream_tree.pull(branch.upstream_branch)
 
1461
                    branch.upstream_branch.tags.merge_to(self.upstream_branch.tags)
 
1462
        if (self.branch.last_revision() != NULL_REVISION
 
1463
                and not self.branch.last_revision() in parents):
 
1464
            parents.insert(0, self.branch.last_revision())
 
1465
        return parents
 
1466
 
 
1467
 
 
1468
    def _import_native_package(self, version, versions, debian_part, md5,
 
1469
            timestamp=None):
 
1470
        pull_branch = self.branch_to_pull_version_from(version, md5)
 
1471
        if pull_branch is not None:
 
1472
            self.pull_version_from_branch(pull_branch, version, native=True)
 
1473
        else:
 
1474
            parents = self.get_native_parents(version, versions)
 
1475
            self.import_debian(debian_part, version, parents, md5,
 
1476
                    native=True, timestamp=timestamp)
 
1477
 
 
1478
    def _get_safe_versions_from_changelog(self, cl):
 
1479
        versions = []
 
1480
        for block in cl._blocks:
 
1481
            try:
 
1482
                versions.append(block.version)
 
1483
            except VersionError:
 
1484
                break
 
1485
        return versions
 
1486
 
 
1487
    def import_package(self, dsc_filename, use_time_from_changelog=True):
 
1488
        """Import a source package.
 
1489
 
 
1490
        :param dsc_filename: a path to a .dsc file for the version
 
1491
            to be imported.
 
1492
        :param use_time_from_changelog: whether to use the current time or
 
1493
            the one from the last changelog entry.
 
1494
        """
 
1495
        base_path = osutils.dirname(dsc_filename)
 
1496
        dsc = deb822.Dsc(open(dsc_filename).read())
 
1497
        version = Version(dsc['Version'])
 
1498
        name = dsc['Source']
 
1499
        upstream_tarball = None
 
1500
        for part in dsc['files']:
 
1501
            if part['name'].endswith(".orig.tar.gz"):
 
1502
                assert upstream_tarball is None, "Two .orig.tar.gz?"
 
1503
                upstream_tarball = os.path.abspath(
 
1504
                        os.path.join(base_path, part['name']))
 
1505
        tempdir = self.extract_dsc(dsc_filename)
 
1506
        try:
 
1507
            # TODO: make more robust against strange .dsc files.
 
1508
            upstream_part = os.path.join(tempdir,
 
1509
                    "%s-%s.orig" % (name, str(version.upstream_version)))
 
1510
            debian_part = os.path.join(tempdir,
 
1511
                    "%s-%s" % (name, str(version.upstream_version)))
 
1512
            native = False
 
1513
            if not os.path.exists(upstream_part):
 
1514
                mutter("It's a native package")
 
1515
                native = True
 
1516
                (_, md5) = self.get_native_part(dsc)
 
1517
            else:
 
1518
                (_, upstream_md5) = self.get_upstream_part(dsc)
 
1519
                (_, md5) = self.get_diff_part(dsc)
 
1520
            cl = self.get_changelog_from_source(debian_part)
 
1521
            timestamp = None
 
1522
            author = None
 
1523
            if use_time_from_changelog and len(cl._blocks) > 0:
 
1524
                 raw_timestamp = cl.date
 
1525
                 import rfc822, time
 
1526
                 time_tuple = rfc822.parsedate_tz(raw_timestamp)
 
1527
                 if time_tuple is not None:
 
1528
                     timestamp = (time.mktime(time_tuple[:9]), time_tuple[9])
 
1529
                 author = cl.author.decode("utf-8")
 
1530
            versions = self._get_safe_versions_from_changelog(cl)
 
1531
            assert not self.has_version(version), \
 
1532
                "Trying to import version %s again" % str(version)
 
1533
            #TODO: check that the versions list is correctly ordered,
 
1534
            # as some methods assume that, and it's not clear what
 
1535
            # should happen if it isn't.
 
1536
            if not native:
 
1537
                self._do_import_package(version, versions, debian_part, md5,
 
1538
                        upstream_part, upstream_md5,
 
1539
                        upstream_tarball=upstream_tarball,
 
1540
                        timestamp=timestamp, author=author)
 
1541
            else:
 
1542
                self._import_native_package(version, versions, debian_part,
 
1543
                        md5, timestamp=timestamp)
 
1544
        finally:
 
1545
            shutil.rmtree(tempdir)
 
1546
 
 
1547
    def extract_upstream_tree(self, upstream_tip, basedir):
 
1548
        # Extract that to a tempdir so we can get a working
 
1549
        # tree for it.
 
1550
        # TODO: should stack rather than trying to use the repository,
 
1551
        # as that will be more efficient.
 
1552
        # TODO: remove the _extract_upstream_tree alias below.
 
1553
        to_location = os.path.join(basedir, "upstream")
 
1554
        dir_to = self.branch.bzrdir.sprout(to_location,
 
1555
                revision_id=upstream_tip,
 
1556
                accelerator_tree=self.tree)
 
1557
        try:
 
1558
            self.upstream_tree = dir_to.open_workingtree()
 
1559
        except NoWorkingTree:
 
1560
            # Handle shared treeless repo's.
 
1561
            self.upstream_tree = dir_to.create_workingtree()
 
1562
        self.upstream_branch = self.upstream_tree.branch
 
1563
 
 
1564
    _extract_upstream_tree = extract_upstream_tree
 
1565
 
 
1566
    def _create_empty_upstream_tree(self, basedir):
 
1567
        to_location = os.path.join(basedir, "upstream")
 
1568
        to_transport = get_transport(to_location)
 
1569
        to_transport.ensure_base()
 
1570
        format = bzrdir.format_registry.make_bzrdir('default')
 
1571
        try:
 
1572
            existing_bzrdir = bzrdir.BzrDir.open_from_transport(
 
1573
                    to_transport)
 
1574
        except NotBranchError:
 
1575
            # really a NotBzrDir error...
 
1576
            create_branch = bzrdir.BzrDir.create_branch_convenience
 
1577
            branch = create_branch(to_transport.base,
 
1578
                    format=format,
 
1579
                    possible_transports=[to_transport])
 
1580
        else:
 
1581
            if existing_bzrdir.has_branch():
 
1582
                raise AlreadyBranchError(to_location)
 
1583
            else:
 
1584
                branch = existing_bzrdir.create_branch()
 
1585
                existing_bzrdir.create_workingtree()
 
1586
        self.upstream_branch = branch
 
1587
        self.upstream_tree = branch.bzrdir.open_workingtree()
 
1588
        self.upstream_tree.set_root_id(self.tree.path2id(""))
 
1589
 
 
1590
    def _extract_tarball_to_tempdir(self, tarball_filename):
 
1591
        tempdir = tempfile.mkdtemp()
 
1592
        try:
 
1593
            proc = Popen(["/usr/bin/tar", "xzf", tarball_filename, "-C",
 
1594
                    tempdir, "--strip-components", "1"],
 
1595
                    preexec_fn=subprocess_setup)
 
1596
            proc.communicate()
 
1597
            if proc.returncode != 0:
 
1598
                raise TarFailed("extract", tarball_filename)
 
1599
            return tempdir
 
1600
        except:
 
1601
            shutil.rmtree(tempdir)
 
1602
            raise
 
1603
 
 
1604
    def _revid_of_upstream_version_from_branch(self, version):
 
1605
        """The private method below will go away eventually."""
 
1606
        return self.revid_of_upstream_version_from_branch(version)
 
1607
 
 
1608
    def revid_of_upstream_version_from_branch(self, version):
 
1609
        # TODO: remove the _revid_of_upstream_version_from_branch alias below.
 
1610
        assert isinstance(version, str)
 
1611
        tag_name = self.upstream_tag_name(version)
 
1612
        if self._has_version(self.branch, tag_name):
 
1613
            return self.branch.tags.lookup_tag(tag_name)
 
1614
        tag_name = self.upstream_tag_name(version, distro="debian")
 
1615
        if self._has_version(self.branch, tag_name):
 
1616
            return self.branch.tags.lookup_tag(tag_name)
 
1617
        tag_name = self.upstream_tag_name(version, distro="ubuntu")
 
1618
        if self._has_version(self.branch, tag_name):
 
1619
            return self.branch.tags.lookup_tag(tag_name)
 
1620
        tag_name = self.upstream_tag_name(version)
 
1621
        return self.branch.tags.lookup_tag(tag_name)
 
1622
 
 
1623
    _revid_of_upstream_version_from_branch = revid_of_upstream_version_from_branch
 
1624
 
 
1625
    def merge_upstream(self, tarball_filename, version, previous_version,
 
1626
            upstream_branch=None, upstream_revision=None, merge_type=None):
 
1627
        assert self.upstream_branch is None, \
 
1628
                "Should use self.upstream_branch if set"
 
1629
        tempdir = tempfile.mkdtemp(dir=os.path.join(self.tree.basedir, '..'))
 
1630
        try:
 
1631
            if previous_version is not None:
 
1632
                previous_upstream_revision = get_snapshot_revision(previous_version.upstream_version)
 
1633
                if self.has_upstream_version_in_packaging_branch(
 
1634
                        previous_version.upstream_version):
 
1635
                    upstream_tip = self.revid_of_upstream_version_from_branch(
 
1636
                            previous_version.upstream_version)
 
1637
                    self.extract_upstream_tree(upstream_tip, tempdir)
 
1638
                elif (upstream_branch is not None and 
 
1639
                      previous_upstream_revision is not None):
 
1640
                    upstream_tip = RevisionSpec.from_string(previous_upstream_revision).as_revision_id(upstream_branch)
 
1641
                    assert isinstance(upstream_tip, str)
 
1642
                    self.extract_upstream_tree(upstream_tip, tempdir)
 
1643
                else:
 
1644
                    raise BzrCommandError("Unable to find the tag for the "
 
1645
                            "previous upstream version, %s, in the branch: "
 
1646
                            "%s" % (
 
1647
                        previous_version.upstream_version,
 
1648
                        self.upstream_tag_name(
 
1649
                            previous_version.upstream_version)))
 
1650
            else:
 
1651
                self._create_empty_upstream_tree(tempdir)
 
1652
            if self.has_upstream_version_in_packaging_branch(version.upstream_version):
 
1653
                raise UpstreamAlreadyImported(version)
 
1654
            try:
 
1655
                if upstream_branch is not None:
 
1656
                    upstream_branch.lock_read()
 
1657
                    if upstream_revision is not None:
 
1658
                        upstream_revision = upstream_branch.last_revision()
 
1659
                    graph = self.branch.repository.get_graph(
 
1660
                            other_repository=upstream_branch.repository)
 
1661
                    if graph.is_ancestor(upstream_revision,
 
1662
                            self.branch.last_revision()):
 
1663
                        raise UpstreamBranchAlreadyMerged
 
1664
                tarball_filename = os.path.abspath(tarball_filename)
 
1665
                m = md5.md5()
 
1666
                m.update(open(tarball_filename).read())
 
1667
                md5sum = m.hexdigest()
 
1668
                tarball_dir = self._extract_tarball_to_tempdir(tarball_filename)
 
1669
                try:
 
1670
                    # FIXME: should use upstream_parents()?
 
1671
                    parents = []
 
1672
                    if self.upstream_branch.last_revision() != NULL_REVISION:
 
1673
                        parents = [self.upstream_branch.last_revision()]
 
1674
                    new_revid = self.import_upstream(tarball_dir,
 
1675
                            version.upstream_version,
 
1676
                            md5sum, parents, upstream_tarball=tarball_filename,
 
1677
                            upstream_branch=upstream_branch,
 
1678
                            upstream_revision=upstream_revision)
 
1679
                    self._fetch_upstream_to_branch(new_revid)
 
1680
                finally:
 
1681
                    shutil.rmtree(tarball_dir)
 
1682
                if self.branch.last_revision() != NULL_REVISION:
 
1683
                    conflicts = self.tree.merge_from_branch(
 
1684
                            self.upstream_branch, merge_type=merge_type)
 
1685
                else:
 
1686
                    # Pull so that merge-upstream allows you to start a branch
 
1687
                    # from upstream tarball.
 
1688
                    conflicts = 0
 
1689
                    self.tree.pull(self.upstream_branch)
 
1690
                self.upstream_branch.tags.merge_to(self.branch.tags)
 
1691
                return conflicts
 
1692
            finally:
 
1693
                if upstream_branch is not None:
 
1694
                    upstream_branch.unlock()
 
1695
        finally:
 
1696
            shutil.rmtree(tempdir)
 
1697
 
 
1698
    def has_pristine_tar_delta(self, revid):
 
1699
        rev = self.branch.repository.get_revision(revid)
 
1700
        return 'deb-pristine-delta' in rev.properties
 
1701
 
 
1702
    def pristine_tar_delta(self, revid):
 
1703
        rev = self.branch.repository.get_revision(revid)
 
1704
        uuencoded = rev.properties['deb-pristine-delta']
 
1705
        delta = standard_b64decode(uuencoded)
 
1706
        return delta
 
1707
 
 
1708
    def reconstruct_pristine_tar(self, revid, package, version,
 
1709
            dest_filename):
 
1710
        """Reconstruct a pristine-tar tarball from a bzr revision."""
 
1711
        if not os.path.exists("/usr/bin/pristine-tar"):
 
1712
            raise PristineTarError("/usr/bin/pristine-tar is not available")
 
1713
        tree = self.branch.repository.revision_tree(revid)
 
1714
        tmpdir = tempfile.mkdtemp(prefix="builddeb-pristine-")
 
1715
        try:
 
1716
            dest = os.path.join(tmpdir, "orig")
 
1717
            export(tree, dest, format='dir')
 
1718
            delta = self.pristine_tar_delta(revid)
 
1719
            command = ["/usr/bin/pristine-tar", "gentar", "-",
 
1720
                       os.path.abspath(dest_filename)]
 
1721
            proc = Popen(command, stdin=PIPE, cwd=dest,
 
1722
                    preexec_fn=subprocess_setup)
 
1723
            (stdout, stderr) = proc.communicate(delta)
 
1724
            if proc.returncode != 0:
 
1725
                raise PristineTarError("Generating tar from delta failed: %s" % stderr)
 
1726
        finally:
 
1727
            shutil.rmtree(tmpdir)
 
1728
 
 
1729
    def make_pristine_tar_delta(self, tree, tarball_path):
 
1730
        if not os.path.exists("/usr/bin/pristine-tar"):
 
1731
            raise PristineTarError("/usr/bin/pristine-tar is not available")
 
1732
        tmpdir = tempfile.mkdtemp(prefix="builddeb-pristine-")
 
1733
        try:
 
1734
            dest = os.path.join(tmpdir, "orig")
 
1735
            tree.lock_read()
 
1736
            try:
 
1737
                for (dp, ie) in tree.inventory.iter_entries():
 
1738
                    ie._read_tree_state(dp, tree)
 
1739
                export(tree, dest, format='dir')
 
1740
            finally:
 
1741
                tree.unlock()
 
1742
            command = ["/usr/bin/pristine-tar", "gendelta", tarball_path, "-"]
 
1743
            note(" ".join(command))
 
1744
            proc = Popen(command, stdout=PIPE, cwd=dest,
 
1745
                    preexec_fn=subprocess_setup)
 
1746
            (stdout, stderr) = proc.communicate()
 
1747
            if proc.returncode != 0:
 
1748
                raise PristineTarError("Generating delta from tar failed: %s" % stderr)
 
1749
            return stdout
 
1750
        finally:
 
1751
            shutil.rmtree(tmpdir)