예제 #1
0
def save_changelog(output_dir, cl_versions, pv, bases, limit=None):
  fh = None
  name = None
  n = 0

  for (v, text) in cl_versions:
    n += 1

    if v in bases:
      break

    if limit is not None and n > limit:
      break

    if fh is None:
      name = '%s_changelog.txt' % pv.version
      path = '%s/%s' % (output_dir, name)
      tree.ensure(path)
      fh = open(path, 'w')

    fh.write(text + '\n')

  if fh is not None:
    fh.close()

  return name
예제 #2
0
def create_tarball(package, version, output_dir, merged_dir):
    """Create a tarball of a merge with conflicts."""
    quilt_format = False
    try:
        with open(merged_dir + '/debian/source/format', 'r') as fd:
            quilt_format = fd.read().strip() == '3.0 (quilt)'
    except IOError:
        pass

    if quilt_format:
        filename = '%s/%s_%s.debian.tar.gz' % (output_dir, package,
                                               version.without_epoch)
        contained = 'debian'
        source = merged_dir + '/debian'
    else:
        filename = "%s/%s_%s.src.tar.gz" % (output_dir, package,
                                            version.without_epoch)
        contained = "%s-%s" % (package, version.without_epoch)
        source = merged_dir

    tree.ensure("%s/tmp/" % config.get('ROOT'))
    parent = tempfile.mkdtemp(dir="%s/tmp/" % config.get('ROOT'))
    try:
        tree.copytree(source, "%s/%s" % (parent, contained))
        shell.run(("tar", "czf", filename, contained), chdir=parent)
        logger.info("Created %s", tree.subdir(config.get('ROOT'), filename))
        return os.path.basename(filename)
    finally:
        tree.remove(parent)
예제 #3
0
    def __init__(self, control_path, left_dir, left_name, right_dir,
                 right_name, base_dir, merged_dir):
        # Merge notes recorded here
        self.notes = []
        # If the merged file was modified (relative to the right version)
        self.modified = False

        self.left_name = left_name
        self.right_name = right_name

        self.left_dir = left_dir
        self.right_dir = right_dir
        self.base_dir = base_dir
        self.merged_dir = merged_dir

        self.left_control_path = os.path.join(left_dir, control_path)
        self.right_control_path = os.path.join(right_dir, control_path)
        self.base_control_path = os.path.join(base_dir, control_path)
        self.merged_control_path = os.path.join(merged_dir, control_path)
        tree.ensure(self.merged_control_path)

        self.base_control = ControlFileParser(filename=self.base_control_path)
        self.left_control = ControlFileParser(filename=self.left_control_path)
        self.right_control = \
            ControlFileParser(filename=self.right_control_path)

        self.orig_right_md5sum = md5sum(self.right_control_path)
예제 #4
0
def unpack_source(pv):
    """Unpack the given source and return location."""
    destdir = unpack_directory(pv)
    if os.path.isdir(destdir):
        return destdir

    srcdir = pv.package.poolPath
    dsc_file = pv.dscPath

    logger.info("Unpacking %s from %s/%s", pv, srcdir, dsc_file)

    tree.ensure(destdir)
    try:
        # output directory for "dpkg-source -x" must not exist
        if (os.path.isdir(destdir)):
            os.rmdir(destdir)
        shell.run(("dpkg-source", "--skip-patches", "-x", dsc_file, destdir), chdir=srcdir, stdout=sys.stdout, stderr=sys.stderr)
        # Make sure we can at least read everything under .pc, which isn't
        # automatically true with dpkg-dev 1.15.4.
        pc_dir = os.path.join(destdir, ".pc")
        for filename in tree.walk(pc_dir):
            pc_filename = os.path.join(pc_dir, filename)
            pc_stat = os.lstat(pc_filename)
            if pc_stat is not None and stat.S_IMODE(pc_stat.st_mode) == 0:
                os.chmod(pc_filename, 0400)
    except:
        cleanup(destdir)
        raise

    return destdir
예제 #5
0
def save_changelog(output_dir, cl_versions, pv, bases, limit=None):
    fh = None
    name = None
    n = 0

    for (v, text) in cl_versions:
        n += 1

        if v in bases:
            break

        if limit is not None and n > limit:
            break

        if fh is None:
            name = '%s_changelog.txt' % pv.version
            path = '%s/%s' % (output_dir, name)
            tree.ensure(path)
            fh = open(path, 'w')

        fh.write(text + '\n')

    if fh is not None:
        fh.close()

    return name
예제 #6
0
    def downloadPackage(self, dist, component, package=None, version=None):
        """Populate the 'pool' directory by downloading Debian source packages
        from the given release and component.

        :param str dist: a release codename such as "wheezy" or "precise"
        :param str component: a component (archive area) such as "main" or
        "contrib"
        :param package: a source package name, or None to download all of them
        :type package: str or None
        :return: True if anything actually changed, False otherwise
        :rtype: bool
        """
        if package is None:
            logger.debug('Downloading all packages from %s/%s/%s into %s pool',
                         self, dist, component, self)
        else:
            logger.debug('Downloading package "%s" from %s/%s/%s into %s pool',
                         package, self, dist, component, self)

        mirror = self.mirrorURL()
        sources = self.getSources(dist, component)

        changed = False

        for source in sources:
            if package != source["Package"] and not (package is None):
                continue
            if package is not None and version is not None \
                    and source["Version"] != str(version):
                continue

            sourcedir = source["Directory"]

            pkg = self.package(dist, component, source['Package'])
            for md5sum, size, name in files(source):
                url = "%s/%s/%s" % (mirror, sourcedir, name)
                filename = "%s/%s" % (pkg.poolPath, name)

                if os.path.isfile(filename):
                    if os.path.getsize(filename) == int(size):
                        logger.debug("Skipping %s, already downloaded.",
                                     filename)
                        continue

                logger.debug("Downloading %s", url)
                changed = True
                tree.ensure(filename)
                with open(filename, 'w') as fd:
                    try:
                        dl = urllib2.urlopen(url)
                        fd.write(dl.read())
                    except IOError:
                        logger.error("Downloading %s failed", url)
                        raise
                logger.debug("Saved %s",
                             tree.subdir(config.get('ROOT'), filename))

        return changed
예제 #7
0
 def merge(ours, upstream, base, output_dir, force=False):
   """Merge PackageVersion instances @ours (left) and @upstream (right) using
   the common ancestor PackageVersion @base, placing the result in
   @output_dir.
   """
   mergedVersion = Version(upstream.version()+"co1")
   base_version = Version(re.sub("build[0-9]+$", "", base.version()))
   left_version = Version(re.sub("build[0-9]+$", "", ours.version()))
   right_version = Version(re.sub("build[0-9]+$", "", upstream.version()))
   if base_version >= left_version:
     cleanup(output_dir)
     if left_version < right_version:
       tree.ensure("%s/%s" % (output_dir, "REPORT"))
예제 #8
0
 def merge(ours, upstream, base, output_dir, force=False):
     """Merge PackageVersion instances @ours (left) and @upstream (right)
     using the common ancestor PackageVersion @base, placing the result in
     @output_dir.
     """
     mergedVersion = Version(upstream.version() + "co1")
     base_version = Version(re.sub("build[0-9]+$", "", base.version()))
     left_version = Version(re.sub("build[0-9]+$", "", ours.version()))
     right_version = Version(re.sub("build[0-9]+$", "", upstream.version()))
     if base_version >= left_version:
         cleanup(output_dir)
         if left_version < right_version:
             tree.ensure("%s/%s" % (output_dir, "REPORT"))
예제 #9
0
  def _getFile(self, url, filename, size=None):
    if os.path.isfile(filename):
        if size is None or os.path.getsize(filename) == int(size):
            return

    logging.debug("Downloading %s", url)
    tree.ensure(filename)
    try:
        urllib.URLopener().retrieve(url, filename)
    except IOError as e:
        logging.error("Downloading %s failed: %s", url, e.args)
        raise
    logging.info("Saved %s", filename)
예제 #10
0
  def updatePool(self, dist, component, package=None):
    """Populate the 'pool' directory by downloading Debian source packages
    from the given release and component.

    :param str dist: a release codename such as "wheezy" or "precise"
    :param str component: a component (archive area) such as "main" or "contrib"
    :param package: a source package name, or None to download all of them
    :type package: str or None
    :return: True if anything actually changed, False otherwise
    :rtype: bool
    """
    if package is None:
      logger.debug('Downloading all packages from %s/%s/%s into %s pool',
          self, dist, component, self)
    else:
      logger.debug('Downloading package "%s" from %s/%s/%s into %s pool',
          package, self, dist, component, self)

    mirror = self.mirrorURL(dist, component)
    sources = self.getSources(dist, component)

    changed = False

    for source in sources:
      if package != source["Package"] and not (package is None):
        continue
      sourcedir = source["Directory"]

      pooldir = PoolDirectory(self, component, source["Package"]).path

      for md5sum, size, name in files(source):
          url = "%s/%s/%s" % (mirror, sourcedir, name)
          filename = "%s/%s/%s" % (config.get('ROOT'), pooldir, name)

          if os.path.isfile(filename):
              if os.path.getsize(filename) == int(size):
                  logger.debug("Skipping %s, already downloaded.", filename)
                  continue

          logger.debug("Downloading %s", url)
          changed = True
          tree.ensure(filename)
          try:
              urllib.URLopener().retrieve(url, filename)
          except IOError:
              logger.error("Downloading %s failed", url)
              raise
          logger.debug("Saved %s", tree.subdir(config.get('ROOT'), filename))

    return changed
예제 #11
0
def save_patch_file(filename, last, this):
    """Save a diff or patch file for the difference between two versions."""
    lastdir = unpack_directory(last)
    thisdir = unpack_directory(this)

    diffdir = os.path.commonprefix((lastdir, thisdir))
    diffdir = diffdir[:diffdir.rindex("/")]

    lastdir = tree.subdir(diffdir, lastdir)
    thisdir = tree.subdir(diffdir, thisdir)

    tree.ensure(filename)
    with open(filename, "w") as diff:
        shell.run(("diff", "-pruN", lastdir, thisdir),
                  chdir=diffdir, stdout=diff, okstatus=(0, 1, 2))
예제 #12
0
def save_patch_file(filename, last, this):
    """Save a diff or patch file for the difference between two versions."""
    lastdir = unpack_directory(last)
    thisdir = unpack_directory(this)

    diffdir = os.path.commonprefix((lastdir, thisdir))
    diffdir = diffdir[:diffdir.rindex("/")]

    lastdir = tree.subdir(diffdir, lastdir)
    thisdir = tree.subdir(diffdir, thisdir)

    tree.ensure(filename)
    with open(filename, "w") as diff:
        shell.run(("diff", "-pruN", lastdir, thisdir),
                  chdir=diffdir, stdout=diff, okstatus=(0, 1, 2))
예제 #13
0
  def updateSources(self):
    """Update the Sources file at sourcesFilename() to contain every
    package/version in this pool directory.
    """
    pooldir = self.path
    filename = self.sourcesFilename

    if not os.path.isdir(pooldir):
      return

    tree.ensure(pooldir)
    logger.debug("Updating %s", filename)
    with open(filename, "w") as sources:
      shell.run(("apt-ftparchive", "sources", pooldir),
          chdir=config.get('ROOT'),
          stdout=sources)
예제 #14
0
def merge_pot(left_dir, right_dir, merged_dir, filename):
    """Update a .po file using msgcat."""
    merged_pot = "%s/%s" % (merged_dir, filename)

    left_pot = "%s/%s" % (left_dir, filename)
    right_pot = "%s/%s" % (right_dir, filename)

    logger.debug("Merging POT file %s", filename)
    try:
        tree.ensure(merged_pot)
        shell.run(("msgcat", "--force-po", "--use-first", "-o", merged_pot,
                   right_pot, left_pot))
    except (ValueError, OSError):
        logger.error("POT file merge failed: %s", filename)
        return False

    return True
예제 #15
0
def merge_file(left_dir, left_name, left_distro, base_dir,
               right_dir, right_name, right_distro, merged_dir, filename):
    """Merge a file using diff3."""
    dest = "%s/%s" % (merged_dir, filename)
    tree.ensure(dest)

    with open(dest, "w") as output:
        status = shell.run(("diff3", "-E", "-m",
                            "-L", left_name, "%s/%s" % (left_dir, filename),
                            "-L", "BASE", "%s/%s" % (base_dir, filename),
                            "-L", right_name, "%s/%s" % (right_dir, filename)),
                           stdout=output, okstatus=(0,1,2))

    if status != 0:
        if not tree.exists(dest) or os.stat(dest).st_size == 0:
            # Probably binary
            if same_file(os.stat("%s/%s" % (left_dir, filename)), left_dir,
                         os.stat("%s/%s" % (right_dir, filename)), right_dir,
                         filename):
                logger.debug("binary files are the same: %s", filename)
                tree.copyfile("%s/%s" % (left_dir, filename),
                              "%s/%s" % (merged_dir, filename))
            elif same_file(os.stat("%s/%s" % (base_dir, filename)), base_dir,
                           os.stat("%s/%s" % (left_dir, filename)), left_dir,
                           filename):
                logger.debug("preserving binary change in %s: %s",
                              right_distro, filename)
                tree.copyfile("%s/%s" % (right_dir, filename),
                              "%s/%s" % (merged_dir, filename))
            elif same_file(os.stat("%s/%s" % (base_dir, filename)), base_dir,
                           os.stat("%s/%s" % (right_dir, filename)), right_dir,
                           filename):
                logger.debug("preserving binary change in %s: %s",
                              left_distro, filename)
                tree.copyfile("%s/%s" % (left_dir, filename),
                              "%s/%s" % (merged_dir, filename))
            else:
                logger.debug("binary file conflict: %s", filename)
                conflict_file(left_dir, left_distro, right_dir, right_distro,
                              merged_dir, filename)
                return True
        else:
            logger.debug("Conflict in %s", filename)
            return True
    else:
        return False
예제 #16
0
def merge_pot(left_dir, right_dir, merged_dir, filename):
    """Update a .po file using msgcat."""
    merged_pot = "%s/%s" % (merged_dir, filename)

    left_pot = "%s/%s" % (left_dir, filename)
    right_pot = "%s/%s" % (right_dir, filename)

    logger.debug("Merging POT file %s", filename)
    try:
        tree.ensure(merged_pot)
        shell.run(("msgcat", "--force-po", "--use-first", "-o", merged_pot,
                   right_pot, left_pot))
    except (ValueError, OSError):
        logger.error("POT file merge failed: %s", filename)
        return True

    return False
예제 #17
0
def save_changes_file(filename, pv, previous=None):
    """Save a changes file for the given source."""
    srcdir = unpack_directory(pv)

    tree.ensure(filename)
    with open(filename, "w") as changes:
        cmd = ("dpkg-genchanges", "-S", "-u%s" % pv.package.poolPath)
        orig_cmd = cmd
        if previous is not None:
            cmd += ("-v%s" % previous.version,)

        try:
            shell.run(cmd, chdir=srcdir, stdout=changes)
        except (ValueError, OSError):
            shell.run(orig_cmd, chdir=srcdir, stdout=changes)

    return filename
예제 #18
0
def diff3_merge(left_dir, left_name, left_distro, base_dir, right_dir,
                right_name, right_distro, merged_dir, filename):
    """Merge a file using diff3."""
    dest = "%s/%s" % (merged_dir, filename)
    tree.ensure(dest)

    with open(dest, "w") as output:
        status = shell.run(("diff3", "-E", "-m", "-L", left_name, "%s/%s" %
                            (left_dir, filename), "-L", "BASE", "%s/%s" %
                            (base_dir, filename), "-L", right_name, "%s/%s" %
                            (right_dir, filename)),
                           stdout=output,
                           okstatus=(0, 1, 2))

    if status != 0:
        if not tree.exists(dest) or os.stat(dest).st_size == 0:
            # Probably binary
            if same_file(os.stat("%s/%s" % (left_dir, filename)), left_dir,
                         os.stat("%s/%s" % (right_dir, filename)), right_dir,
                         filename):
                logger.debug("binary files are the same: %s", filename)
                tree.copyfile("%s/%s" % (left_dir, filename),
                              "%s/%s" % (merged_dir, filename))
            elif same_file(os.stat("%s/%s" % (base_dir, filename)), base_dir,
                           os.stat("%s/%s" % (left_dir, filename)), left_dir,
                           filename):
                logger.debug("preserving binary change in %s: %s",
                             right_distro, filename)
                tree.copyfile("%s/%s" % (right_dir, filename),
                              "%s/%s" % (merged_dir, filename))
            elif same_file(os.stat("%s/%s" % (base_dir, filename)), base_dir,
                           os.stat("%s/%s" % (right_dir, filename)), right_dir,
                           filename):
                logger.debug("preserving binary change in %s: %s", left_distro,
                             filename)
                tree.copyfile("%s/%s" % (left_dir, filename),
                              "%s/%s" % (merged_dir, filename))
            else:
                logger.debug("binary file conflict: %s", filename)
                return False
        else:
            logger.debug("Conflict in %s", filename)
            return False
    else:
        return True
예제 #19
0
    def write_report(self, output_dir):
        self.check()
        report = self.to_dict()

        filename = "%s/REPORT.json" % output_dir
        tree.ensure(filename)
        json_report = json.dumps(report, indent=2, sort_keys=False)
        with open(filename + '.tmp', "w") as fh:
            fh.write(json_report + '\n')
        os.rename(filename + '.tmp', filename)

        filename = "%s/REPORT.html" % output_dir
        tree.ensure(filename)
        template = jinja_env.get_template('merge_report.html')

        if self.left_changelog is None:
            left_changelog_text = u''
        else:
            # Use a unicode object to avoid errors when decoding
            # with implicit ascii codec for inclusion in Jinja
            left_changelog_text = codecs.open(output_dir + '/' +
                                              self.left_changelog,
                                              encoding='utf-8',
                                              errors='replace').read()

        if self.right_changelog is None:
            right_changelog_text = u''
        else:
            right_changelog_text = codecs.open(output_dir + '/' +
                                               self.right_changelog,
                                               encoding='utf-8',
                                               errors='replace').read()

        with open(filename + '.tmp', "w") as fh:
            # we decode the JSON report and pass that in, rather than
            # using this object directly, so that the values are
            # consistently unicode as expected by jinja
            template.stream(
                report=json.loads(json_report, encoding='utf-8'),
                left_changelog_text=left_changelog_text,
                right_changelog_text=right_changelog_text,
            ).dump(fh, encoding='utf-8')
        os.rename(filename + '.tmp', filename)
예제 #20
0
def save_changes_file(filename, source, previous=None):
    """Save a changes file for the given source."""
    srcdir = unpack_directory(source)

    filesdir = "%s/%s" % (ROOT, source["Directory"])

    tree.ensure(filename)
    with open(filename, "w") as changes:
        cmd = ("dpkg-genchanges", "-S", "-u%s" % filesdir)
        orig_cmd = cmd
        if previous is not None:
            cmd += ("-v%s" % previous["Version"],)

        try:
            shell.run(cmd, chdir=srcdir, stdout=changes)
        except (ValueError, OSError):
            shell.run(orig_cmd, chdir=srcdir, stdout=changes)

    return filename
예제 #21
0
def main(options, args):
    logger.info('Comparing target packages with source distros...')

    if options.target:
        targets = [options.target]
    else:
        targets = config.get('DISTRO_TARGETS').keys()

    # Write to a new list
    list_filename = patch_list_file()
    tree.ensure(list_filename)
    list_file = open(list_filename + ".new", "w")
    try:
        # For latest version of each package in the distribution, check for a patch for the
        # current version; publish if it exists, clean up if not
        for target in targets:
            our_distro, our_dist, our_component = get_target_distro_dist_component(
                target)
            d = Distro.get(our_distro)
            for pv in d.newestPackageVersions(our_dist, our_component):
                if options.package and pv.package.name not in options.package:
                    continue

                if not PackageLists().check_target(target, None,
                                                   pv.package.name):
                    continue

                # Publish slipped patches in preference to true-base ones
                slip_filename = patch_file(our_distro, pv, True)
                filename = patch_file(our_distro, pv, False)

                if os.path.isfile(slip_filename):
                    publish_patch(our_distro, pv, slip_filename, list_file)
                elif os.path.isfile(filename):
                    publish_patch(our_distro, pv, filename, list_file)
                else:
                    unpublish_patch(our_distro, pv)
    finally:
        list_file.close()

    # Move the new list over the old one
    os.rename(list_filename + ".new", list_filename)
예제 #22
0
def generate_patch(base, distro, ours,
                   slipped=False, force=False, unpacked=False):
    """Generate a patch file for the given comparison."""
    base_source = base.getSources()
    our_source = ours.getSources()
    our_version = Version(our_source["Version"])
    base_version = Version(base_source["Version"])

    if base_version > our_version:
        # Allow comparison of source -1 against our -0coX (slipped)
        if not slipped:
            return
        elif our_version.revision is None:
            return
        elif not our_version.revision.startswith("0co"):
            return
        elif base_version.revision != "1":
            return
        elif base_version.upstream != our_version.upstream:
            return
        elif base_version.epoch != our_version.epoch:
            return

        logging.debug("Allowing comparison of -1 against -0coX")
    elif base_version == our_version:
        return

    filename = patch_file(distro, our_source, slipped)
    if not force:
        basis = read_basis(filename)
        if basis is not None and basis == base_version:
            return

    if not os.path.exists(filename):
        if not unpacked:
            unpack_source(base)
            unpack_source(ours)

        tree.ensure(filename)
        save_patch_file(filename, base_source, our_source)
        save_basis(filename, base_version)
        logging.info("Saved patch file: %s", tree.subdir(ROOT, filename))
예제 #23
0
def create_source(package, version, since, output_dir, merged_dir):
    """Create a source package without conflicts."""
    contained = "%s-%s" % (package, version.upstream)
    dsc_filename = "%s_%s.dsc" % (package, version.without_epoch)

    tree.ensure("%s/tmp/" % config.get('ROOT'))
    parent = tempfile.mkdtemp(dir="%s/tmp/" % config.get('ROOT'))
    try:
        tree.copytree(merged_dir, "%s/%s" % (parent, contained))

        match = '%s_%s.orig(-\w+)?.tar.(gz|bz2|xz)$' \
                % (re.escape(package), re.escape(version.upstream))
        for filename in os.listdir(output_dir):
            if re.match(match, filename) is None:
                continue
            path = os.path.join(output_dir, filename)
            if os.path.isfile(path):
                os.link(path, "%s/%s" % (parent, filename))

        cmd = ("dpkg-source", )
        if version.revision is not None and since.upstream != version.upstream:
            cmd += ("-sa", )
        cmd += ("-b", contained)

        try:
            dpkg_source_output = subprocess.check_output(
                cmd, cwd=parent, stderr=subprocess.STDOUT)
        except subprocess.CalledProcessError, e:
            logger.warning("dpkg-source failed with code %d:\n%s\n",
                           e.returncode, e.output)
            # for the message in the JSON report, just take the last line
            # and hope it's relevant...
            lastline = re.sub(r'.*\n',
                              '',
                              e.output.rstrip('\n'),
                              flags=re.DOTALL)
            return (MergeResult.FAILED,
                    "unable to build merged source package: "
                    "dpkg-source failed with %d (%s)" %
                    (e.returncode, lastline),
                    create_tarball(package, version, output_dir, merged_dir))
        else:
예제 #24
0
def generate_patch(base,
                   distro,
                   ours,
                   slipped=False,
                   force=False,
                   unpacked=False):
    """Generate a patch file for the given comparison."""
    if base.version > ours.version:
        # Allow comparison of source -1 against our -0coX (slipped)
        if not slipped:
            return
        elif ours.version.revision is None:
            return
        elif not ours.version.revision.startswith("0co"):
            return
        elif base.version.revision != "1":
            return
        elif base.version.upstream != ours.version.upstream:
            return
        elif base.version.epoch != ours.version.epoch:
            return

        logging.debug("Allowing comparison of -1 against -0coX")
    elif base.version == ours.version:
        return

    filename = patch_file(distro, ours, slipped)
    if not force:
        basis = read_basis(filename)
        if basis is not None and basis == base.version:
            return

    if not os.path.exists(filename):
        if not unpacked:
            unpack_source(base)
            unpack_source(ours)

        tree.ensure(filename)
        save_patch_file(filename, base, ours)
        save_basis(filename, base.version)
        logging.info("Saved patch file: %s",
                     tree.subdir(config.get('ROOT'), filename))
예제 #25
0
def merge_po(left_dir, right_dir, merged_dir, filename):
    """Update a .po file using msgcat or msgmerge."""
    merged_po = "%s/%s" % (merged_dir, filename)
    closest_pot = find_closest_pot(merged_po)
    if closest_pot is None:
        return merge_pot(left_dir, right_dir, merged_dir, filename)

    left_po = "%s/%s" % (left_dir, filename)
    right_po = "%s/%s" % (right_dir, filename)

    logger.debug("Merging PO file %s", filename)
    try:
        tree.ensure(merged_po)
        shell.run(("msgmerge", "--force-po", "-o", merged_po, "-C", left_po,
                   right_po, closest_pot))
    except (ValueError, OSError):
        logger.error("PO file merge failed: %s", filename)
        return False

    return True
예제 #26
0
def merge_po(left_dir, right_dir, merged_dir, filename):
    """Update a .po file using msgcat or msgmerge."""
    merged_po = "%s/%s" % (merged_dir, filename)
    closest_pot = find_closest_pot(merged_po)
    if closest_pot is None:
        return merge_pot(left_dir, right_dir, merged_dir, filename)

    left_po = "%s/%s" % (left_dir, filename)
    right_po = "%s/%s" % (right_dir, filename)

    logger.debug("Merging PO file %s", filename)
    try:
        tree.ensure(merged_po)
        shell.run(("msgmerge", "--force-po", "-o", merged_po,
                   "-C", left_po, right_po, closest_pot))
    except (ValueError, OSError):
        logger.error("PO file merge failed: %s", filename)
        return True

    return False
예제 #27
0
def main(options, args):
    logger.info('Comparing target packages with source distros...')

    if options.target:
        targets = [options.target]
    else:
        targets = DISTRO_TARGETS.keys()

    # Write to a new list
    list_filename = patch_list_file()
    tree.ensure(list_filename)
    list_file = open(list_filename + ".new", "w")
    try:
        # For latest version of each package in the distribution, check for a patch for the
        # current version; publish if it exists, clean up if not
        for target in targets:
            our_distro, our_dist, our_component = get_target_distro_dist_component(target)
            d = Distro.get(our_distro)
            for source in d.newestSources(our_dist, our_component):
                package = source["Package"]
                if options.package and source['Package'] not in options.package:
                    continue

                if not PACKAGELISTS.check_target(target, None, source["Package"]):
                    continue

                # Publish slipped patches in preference to true-base ones
                slip_filename = patch_file(our_distro, source, True)
                filename = patch_file(our_distro, source, False)

                if os.path.isfile(slip_filename):
                    publish_patch(our_distro, source, slip_filename, list_file)
                elif os.path.isfile(filename):
                    publish_patch(our_distro, source, filename, list_file)
                else:
                    unpublish_patch(our_distro, source)
    finally:
        list_file.close()

    # Move the new list over the old one
    os.rename(list_filename + ".new", list_filename)
예제 #28
0
def create_tarball(package, version, output_dir, merged_dir):
    """Create a tarball of a merge with conflicts."""
    filename = "%s/%s_%s.src.tar.gz" % (output_dir, package,
                                        version.without_epoch)
    contained = "%s-%s" % (package, version.without_epoch)

    tree.ensure("%s/tmp/" % ROOT)
    parent = tempfile.mkdtemp(dir="%s/tmp/" % ROOT)
    try:
        tree.copytree(merged_dir, "%s/%s" % (parent, contained))

        debian_rules = "%s/%s/debian/rules" % (parent, contained)
        if os.path.isfile(debian_rules):
            os.chmod(debian_rules, os.stat(debian_rules).st_mode | 0111)

        shell.run(("tar", "czf", filename, contained), chdir=parent)

        logger.info("Created %s", tree.subdir(ROOT, filename))
        return os.path.basename(filename)
    finally:
        tree.remove(parent)
예제 #29
0
def create_tarball(package, version, output_dir, merged_dir):
    """Create a tarball of a merge with conflicts."""
    filename = "%s/%s_%s.src.tar.gz" % (output_dir, package,
                                        version.without_epoch)
    contained = "%s-%s" % (package, version.without_epoch)

    tree.ensure("%s/tmp/" % config.get('ROOT'))
    parent = tempfile.mkdtemp(dir="%s/tmp/" % config.get('ROOT'))
    try:
        tree.copytree(merged_dir, "%s/%s" % (parent, contained))

        debian_rules = "%s/%s/debian/rules" % (parent, contained)
        if os.path.isfile(debian_rules):
            os.chmod(debian_rules, os.stat(debian_rules).st_mode | 0111)

        shell.run(("tar", "czf", filename, contained), chdir=parent)

        logger.info("Created %s", tree.subdir(config.get('ROOT'), filename))
        return os.path.basename(filename)
    finally:
        tree.remove(parent)
예제 #30
0
def publish_patch(distro, pv, filename, list_file):
    """Publish the latest version of the patch for all to see."""
    publish_filename = published_file(distro, pv)

    tree.ensure(publish_filename)
    if os.path.isfile(publish_filename):
        os.unlink(publish_filename)
    os.link(filename, publish_filename)

    logger.info("Published %s",
                tree.subdir(config.get('ROOT'), publish_filename))
    print >> list_file, "%s %s" % (
        pv.package,
        tree.subdir("%s/published" % config.get('ROOT'), publish_filename))

    # Remove older patches
    for junk in os.listdir(os.path.dirname(publish_filename)):
        junkpath = "%s/%s" % (os.path.dirname(publish_filename), junk)
        if os.path.isfile(junkpath) \
                and junk != os.path.basename(publish_filename):
            os.unlink(junkpath)

    # Publish extracted patches
    output = "%s/extracted" % os.path.dirname(publish_filename)
    if os.path.isdir(output):
        tree.remove(output)

    dpatch_dir = dpatch_directory(distro, pv)
    if os.path.isdir(dpatch_dir):
        for dpatch in tree.walk(dpatch_dir):
            if not len(dpatch):
                continue

            src_filename = "%s/%s" % (dpatch_dir, dpatch)
            dest_filename = "%s/%s" % (output, dpatch)

            logger.info("Published %s",
                        tree.subdir(config.get('ROOT'), dest_filename))
            tree.ensure(dest_filename)
            tree.copyfile(src_filename, dest_filename)
예제 #31
0
    def write_report(self, output_dir):
        self.check()
        report = self.to_dict()

        filename = "%s/REPORT.json" % output_dir
        tree.ensure(filename)
        json_report = json.dumps(report, indent=2, sort_keys=False)
        with open(filename + '.tmp', "w") as fh:
            fh.write(json_report + '\n')
        os.rename(filename + '.tmp', filename)

        filename = "%s/REPORT.html" % output_dir
        tree.ensure(filename)
        template = jinja_env.get_template('merge_report.html')

        if self.left_changelog is None:
            left_changelog_text = u''
        else:
            # Use a unicode object to avoid errors when decoding
            # with implicit ascii codec for inclusion in Jinja
            left_changelog_text = codecs.open(output_dir + '/' +
                    self.left_changelog, encoding='utf-8',
                    errors='replace').read()

        if self.right_changelog is None:
            right_changelog_text = u''
        else:
            right_changelog_text = codecs.open(output_dir + '/' +
                    self.right_changelog, encoding='utf-8',
                    errors='replace').read()

        with open(filename + '.tmp', "w") as fh:
            # we decode the JSON report and pass that in, rather than
            # using this object directly, so that the values are
            # consistently unicode as expected by jinja
            template.stream(report=json.loads(json_report, encoding='utf-8'),
                    left_changelog_text=left_changelog_text,
                    right_changelog_text=right_changelog_text,
                    ).dump(fh, encoding='utf-8')
        os.rename(filename + '.tmp', filename)
예제 #32
0
def create_source(package, version, since, output_dir, merged_dir):
    """Create a source package without conflicts."""
    contained = "%s-%s" % (package, version.upstream)
    filename = "%s_%s.dsc" % (package, version.without_epoch)

    tree.ensure("%s/tmp/" % ROOT)
    parent = tempfile.mkdtemp(dir="%s/tmp/" % ROOT)
    try:
        tree.copytree(merged_dir, "%s/%s" % (parent, contained))

        for ext in ['gz', 'bz2', 'xz']:
            orig_filename = "%s_%s.orig.tar.%s" % (package, version.upstream,
                                                   ext)
            if os.path.isfile("%s/%s" % (output_dir, orig_filename)):
                os.link("%s/%s" % (output_dir, orig_filename),
                        "%s/%s" % (parent, orig_filename))
                break

        cmd = ("dpkg-source",)
        if version.revision is not None and since.upstream != version.upstream:
            cmd += ("-sa",)
        cmd += ("-b", contained)

        try:
            dpkg_source_output = subprocess.check_output(cmd, cwd=parent,
                    stderr=subprocess.STDOUT)
        except subprocess.CalledProcessError, e:
            logger.warning("dpkg-source failed with code %d:\n%s\n",
                e.returncode, e.output)
            # for the message in the JSON report, just take the last line
            # and hope it's relevant...
            lastline = re.sub(r'.*\n', '', e.output.rstrip('\n'),
                flags=re.DOTALL)
            return (MergeResult.FAILED,
                    "unable to build merged source package: "
                    "dpkg-source failed with %d (%s)" % (
                        e.returncode, lastline),
                    create_tarball(package, version, output_dir, merged_dir))
        else:
def extract_dpatches(dirname, source):
    """Extract patches from debian/patches."""
    srcdir = unpack_directory(source)
    patchdir = "%s/debian/patches" % srcdir

    if not os.path.isdir(patchdir):
        logger.debug("No debian/patches")
        return

    for patch in tree.walk(patchdir):
        if os.path.basename(patch) in ["00list", "series", "README",
                                       ".svn", "CVS", ".bzr", ".git"]:
            continue
        elif not len(patch):
            continue

        logger.debug("%s", patch)
        src_filename = "%s/%s" % (patchdir, patch)
        dest_filename = "%s/%s" % (dirname, patch)

        tree.ensure(dest_filename)
        tree.copyfile(src_filename, dest_filename)
예제 #34
0
def publish_patch(distro, source, filename, list_file):
    """Publish the latest version of the patch for all to see."""
    publish_filename = published_file(distro, source)

    tree.ensure(publish_filename)
    if os.path.isfile(publish_filename):
        os.unlink(publish_filename)
    os.link(filename, publish_filename)

    logger.info("Published %s", tree.subdir(ROOT, publish_filename))
    print >>list_file, "%s %s" % (source["Package"],
                                  tree.subdir("%s/published" % ROOT,
                                              publish_filename))

    # Remove older patches
    for junk in os.listdir(os.path.dirname(publish_filename)):
        junkpath = "%s/%s" % (os.path.dirname(publish_filename), junk)
        if os.path.isfile(junkpath) \
                and junk != os.path.basename(publish_filename):
            os.unlink(junkpath)

    # Publish extracted patches
    output = "%s/extracted" % os.path.dirname(publish_filename)
    if os.path.isdir(output):
        tree.remove(output)

    dpatch_dir = dpatch_directory(distro, source)
    if os.path.isdir(dpatch_dir):
        for dpatch in tree.walk(dpatch_dir):
            if not len(dpatch):
                continue

            src_filename = "%s/%s" % (dpatch_dir, dpatch)
            dest_filename = "%s/%s" % (output, dpatch)

            logger.info("Published %s", tree.subdir(ROOT, dest_filename))
            tree.ensure(dest_filename)
            tree.copyfile(src_filename, dest_filename)
예제 #35
0
def extract_dpatches(dirname, pv):
    """Extract patches from debian/patches."""
    srcdir = unpack_directory(pv)
    patchdir = "%s/debian/patches" % srcdir

    if not os.path.isdir(patchdir):
        logger.debug("No debian/patches")
        return

    for patch in tree.walk(patchdir):
        if os.path.basename(patch) in [
                "00list", "series", "README", ".svn", "CVS", ".bzr", ".git"
        ]:
            continue
        elif not len(patch):
            continue

        logger.debug("%s", patch)
        src_filename = "%s/%s" % (patchdir, patch)
        dest_filename = "%s/%s" % (dirname, patch)

        tree.ensure(dest_filename)
        tree.copyfile(src_filename, dest_filename)
예제 #36
0
def merge_changelog(left_dir, right_dir, merged_dir, filename):
    """Merge a changelog file."""
    logger.debug("Knitting %s", filename)

    left_cl = read_changelog("%s/%s" % (left_dir, filename))
    right_cl = read_changelog("%s/%s" % (right_dir, filename))
    tree.ensure(filename)

    with open("%s/%s" % (merged_dir, filename), "w") as output:
        for right_ver, right_text in right_cl:
            while len(left_cl) and left_cl[0][0] > right_ver:
                (left_ver, left_text) = left_cl.pop(0)
                print >> output, left_text

            while len(left_cl) and left_cl[0][0] == right_ver:
                (left_ver, left_text) = left_cl.pop(0)

            print >> output, right_text

        for left_ver, left_text in left_cl:
            print >> output, left_text

    return False
예제 #37
0
def merge_changelog(left_dir, right_dir, merged_dir, filename):
    """Merge a changelog file."""
    logger.debug("Knitting %s", filename)

    left_cl = read_changelog("%s/%s" % (left_dir, filename))
    right_cl = read_changelog("%s/%s" % (right_dir, filename))
    tree.ensure(filename)

    with open("%s/%s" % (merged_dir, filename), "w") as output:
        for right_ver, right_text in right_cl:
            while len(left_cl) and left_cl[0][0] > right_ver:
                (left_ver, left_text) = left_cl.pop(0)
                print >>output, left_text

            while len(left_cl) and left_cl[0][0] == right_ver:
                (left_ver, left_text) = left_cl.pop(0)

            print >>output, right_text

        for left_ver, left_text in left_cl:
            print >>output, left_text

    return False
예제 #38
0
def unpack_source(pv):
    """Unpack the given source and return location."""
    source = pv.getSources()
    destdir = unpack_directory(source)
    if os.path.isdir(destdir):
        return destdir

    srcdir = "%s/%s" % (ROOT, pv.poolDirectory().path)
    for md5sum, size, name in files(source):
        if name.endswith(".dsc"):
            dsc_file = name
            break
    else:
        raise ValueError, "Missing dsc file"

    logger.info("Unpacking %s from %s/%s", pv, srcdir, dsc_file)

    tree.ensure(destdir)
    try:
        # output directory for "dpkg-source -x" must not exist
        if (os.path.isdir(destdir)):
            os.rmdir(destdir)
        shell.run(("dpkg-source", "--skip-patches", "-x", dsc_file, destdir), chdir=srcdir, stdout=sys.stdout, stderr=sys.stderr)
        # Make sure we can at least read everything under .pc, which isn't
        # automatically true with dpkg-dev 1.15.4.
        pc_dir = os.path.join(destdir, ".pc")
        for filename in tree.walk(pc_dir):
            pc_filename = os.path.join(pc_dir, filename)
            pc_stat = os.lstat(pc_filename)
            if pc_stat is not None and stat.S_IMODE(pc_stat.st_mode) == 0:
                os.chmod(pc_filename, 0400)
    except:
        cleanup(destdir)
        raise

    return destdir
예제 #39
0
def produce_merge(target, left, upstream, output_dir):

  left_dir = unpack_source(left)
  upstream_dir = unpack_source(upstream)

  report = MergeReport(left=left, right=upstream)
  report.target = target.name
  report.mom_version = str(VERSION)
  report.merge_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())

  cleanup(output_dir)

  # Try to find the newest common ancestor
  tried_bases = set()
  downstream_versions = None
  upstream_versions = None
  try:
    downstream_versions = read_changelog(left_dir + '/debian/changelog')
    upstream_versions = read_changelog(upstream_dir + '/debian/changelog')
    base, base_dir = get_common_ancestor(target,
            left, downstream_versions, upstream, upstream_versions, tried_bases)
  except Exception as e:
    report.bases_not_found = sorted(tried_bases, reverse=True)

    if isinstance(e, NoBase):
      report.result = MergeResult.NO_BASE
      logger.info('%s', e)
    else:
      report.result = MergeResult.FAILED
      report.message = 'error finding base version: %s' % e
      logger.exception('error finding base version:\n')

    if downstream_versions:
      report.left_changelog = save_changelog(output_dir, downstream_versions,
          left, set(), 1)

    if upstream_versions:
      report.right_changelog = save_changelog(output_dir, upstream_versions,
          upstream, set(), 1)

    report.write_report(output_dir)
    return

  stop_at = set([base.version]).union(tried_bases)
  report.left_changelog = save_changelog(output_dir, downstream_versions,
      left, stop_at)
  report.right_changelog = save_changelog(output_dir, upstream_versions,
      upstream, stop_at)

  report.set_base(base)
  report.bases_not_found = sorted(tried_bases, reverse=True)

  logger.info('base version: %s', base.version)

  generate_patch(base, left.package.distro, left, slipped=False, force=False,
          unpacked=True)
  generate_patch(base, upstream.package.distro, upstream, slipped=False,
          force=False, unpacked=True)

  report.merged_version = Version(str(upstream.version)+config.get('LOCAL_SUFFIX'))

  if base >= upstream:
    logger.info("Nothing to be done: %s >= %s", base, upstream)
    report.result = MergeResult.KEEP_OURS
    report.merged_version = left.version
    report.write_report(output_dir)
    return

  # Careful: MergeReport.merged_dir is the output directory for the .dsc or
  # tarball, whereas our local variable merged_dir (below) is a temporary
  # directory containing unpacked source code. Don't mix them up.
  report.merged_dir = output_dir

  if base.version == left.version:
    logger.info("Syncing %s to %s", left, upstream)
    if not os.path.isdir(output_dir):
        os.makedirs(output_dir)

    report.result = MergeResult.SYNC_THEIRS
    report.build_metadata_changed = False
    report.right_patch = copy_in(output_dir, upstream)
    report.merged_version = upstream.version
    report.merged_patch = report.right_patch
    report.merged_files = report.right_files

    write_report(report,
        left=left,
        base=base,
        right=upstream,
        src_file=None,
        # this is MergeReport.merged_dir...
        output_dir=output_dir,
        # ... and for a SYNC_THEIRS merge, we don't need to look at the
        # unpacked source code
        merged_dir=None)
    return

  merged_dir = work_dir(left.package.name, report.merged_version)

  logger.info("Merging %s..%s onto %s", upstream, base, left)

  try:
    conflicts = do_merge(left_dir, left, base_dir,
                         upstream_dir, upstream, merged_dir)
  except OSError as e:
    cleanup(merged_dir)
    logger.exception("Could not merge %s, probably bad files?", left)
    report.result = MergeResult.FAILED
    report.message = 'Could not merge: %s' % e
    report.write_report(output_dir)
    return

  # Hack. Create a temporary merged patch to see what's changed. It
  # would be better to track the changes through do_merge and return
  # them.
  if len(conflicts) == 0:
    changelog_only = False
    tree.ensure("%s/tmp/" % ROOT)
    with tempfile.NamedTemporaryFile(suffix=".patch",
                                     dir="%s/tmp/" % ROOT) as tmp_patch:
      create_patch(report.merged_version, tmp_patch.name, merged_dir,
                   upstream.getSources(), upstream_dir)
      cmd = ["diffstat", "-qlkp1", tmp_patch.name]
      diffstat_output = subprocess.check_output(cmd)
      diff_files = diffstat_output.splitlines()
      logger.debug("Files differing from upstream:\n%s",
                   "\n".join(diff_files))
      if len(diff_files) == 0 or diff_files == ["debian/changelog"]:
        changelog_only = True

    if changelog_only:
      # Sync to upstream since this is just noise
      logger.info("Syncing %s to %s since only changes are in changelog",
                  left, upstream)
      if not os.path.isdir(output_dir):
        os.makedirs(output_dir)

      report.result = MergeResult.SYNC_THEIRS
      report.build_metadata_changed = False
      report.right_patch = copy_in(output_dir, upstream)
      report.merged_version = upstream.version
      report.merged_patch = report.right_patch
      report.merged_files = report.right_files

      write_report(report,
                   left=left,
                   base=base,
                   right=upstream,
                   src_file=None,
                   # this is MergeReport.merged_dir...
                   output_dir=output_dir,
                   # ... and for a SYNC_THEIRS merge, we don't need to
                   # look at the unpacked source code
                   merged_dir=None)

      cleanup(merged_dir)
      cleanup_source(upstream.getSources())
      cleanup_source(base.getSources())
      cleanup_source(left.getSources())

      return

  if 'debian/changelog' not in conflicts:
    try:
      add_changelog(left.package.name, report.merged_version, left.package.distro.name, left.package.dist,
                    upstream.package.distro.name, upstream.package.dist, merged_dir)
    except IOError as e:
      logger.exception("Could not update changelog for %s!", left)
      report.result = MergeResult.FAILED
      report.message = 'Could not update changelog: %s' % e
      report.write_report(output_dir)
      return

  if not os.path.isdir(output_dir):
    os.makedirs(output_dir)
  copy_in(output_dir, base)
  report.left_patch = copy_in(output_dir, left)
  report.right_patch = copy_in(output_dir, upstream)
  report.build_metadata_changed = False
  report.merged_dir = output_dir

  if len(conflicts):
    src_file = create_tarball(left.package.name, report.merged_version, output_dir, merged_dir)
    report.result = MergeResult.CONFLICTS
    report.conflicts = sorted(conflicts)
    report.merge_failure_tarball = src_file
    report.merged_dir = None
  else:
    result, message, src_file = create_source(left.package.name,
        report.merged_version, left.version, output_dir, merged_dir)
    report.result = result

    if result == MergeResult.MERGED:
      assert src_file.endswith('.dsc'), src_file
      dsc = ControlFile("%s/%s" % (output_dir, src_file), signed=True).para
      report.build_metadata_changed = is_build_metadata_changed(left.getSources(), dsc)
      report.merged_files = [src_file] + [f[2] for f in files(dsc)]
      report.merged_patch = create_patch(report.merged_version,
              "%s/%s_%s_from-theirs.patch" % (output_dir, left.package.name,
                  report.merged_version),
              merged_dir,
              upstream.getSources(),
              upstream_dir)
      report.proposed_patch = create_patch(report.merged_version,
              "%s/%s_%s_from-ours.patch" % (output_dir, left.package.name,
                  report.merged_version),
              merged_dir,
              left.getSources(),
              left_dir)
    else:
      report.result = result
      report.message = message
      report.merged_dir = ""
      report.merge_failure_tarball = src_file

  write_report(report,
               left,
               base,
               upstream,
               src_file=src_file,
               output_dir=output_dir,
               merged_dir=merged_dir)
  logger.info("Wrote output to %s", src_file)
  cleanup(merged_dir)
  cleanup_source(upstream.getSources())
  cleanup_source(base.getSources())
  cleanup_source(left.getSources())
예제 #40
0
def write_text_report(left, left_patch, base, tried_bases, right, right_patch,
                      merged_version, conflicts, src_file, patch_file,
                      output_dir, merged_dir, merged_is_right,
                      build_metadata_changed):
    """Write the merge report."""

    package = left.package.name
    assert package == right.package.name, (package, right.package.name)

    assert isinstance(left, PackageVersion)
    left_distro = left.package.distro.name

    assert isinstance(right, PackageVersion)
    right_distro = right.package.distro.name

    filename = "%s/REPORT" % output_dir
    tree.ensure(filename)
    with open(filename, "w") as report:
        # Package and time
        print >> report, "%s" % package
        print >> report, "%s" % time.ctime()
        print >> report

        # General rambling
        print >> report, fill(
            "Below now follows the report of the automated "
            "merge of the %s changes to the %s source "
            "package against the new %s version." %
            (left_distro.title(), package, right_distro.title()))
        print >> report
        print >> report, fill(
            "This file is designed to be both human readable "
            "and machine-parseable.  Any line beginning with "
            "four spaces is a file that should be downloaded "
            "for the complete merge set.")
        print >> report
        print >> report

        print >> report, fill(
            "Here are the particulars of the three versions "
            "of %s that were chosen for the merge.  The base "
            "is the newest version that is a common ancestor "
            "of both the %s and %s packages.  It may be of "
            "a different upstream version, but that's not "
            "usually a problem." %
            (package, left_distro.title(), right_distro.title()))
        print >> report
        print >> report, fill(
            "The files are the source package itself, and "
            "the patch from the common base to that version.")
        print >> report

        # Base version and files
        if tried_bases:
            # We print this even if base is not None: we want to
            # record the better base versions we tried and failed to find
            print >> report, "missing base version(s):"
            for v in tried_bases:
                print >> report, " %s" % v

        if base is not None:
            print >> report, "base: %s" % base.version
            for md5sum, size, name in files(base.getDscContents()):
                print >> report, "    %s" % name
        print >> report

        # Left version and files
        print >> report, "our distro (%s): %s" % (left_distro, left.version)
        for md5sum, size, name in files(left.getDscContents()):
            print >> report, "    %s" % name
        print >> report
        if left_patch is not None:
            print >> report, "base -> %s" % left_distro
            print >> report, "    %s" % left_patch
            print >> report

        # Right version and files
        print >> report, "source distro (%s): %s" % (right_distro,
                                                     right.version)
        for md5sum, size, name in files(right.getDscContents()):
            print >> report, "    %s" % name
        print >> report
        if right_patch is not None:
            print >> report, "base -> %s" % right_distro
            print >> report, "    %s" % right_patch
            print >> report

        # Generated section
        print >> report
        print >> report, "Generated Result"
        print >> report, "================"
        print >> report
        if base is None:
            print >> report, fill(
                "Failed to merge because the base version "
                "required for a 3-way diff is missing from "
                "%s pool. Uou will need to either merge "
                "manually; or add the missing base version "
                "sources to '%s/%s/*/%s/' and run "
                "update_sources.py." %
                (right_distro, config.get('ROOT'), right_distro, package))
            print >> report
        elif merged_is_right:
            print >> report, fill("The %s version supercedes the %s version "
                                  "and can be added to %s with no changes." %
                                  (right_distro.title(), left_distro.title(),
                                   left_distro.title()))
            print >> report
            print >> report, "Merged without changes: YES"
            print >> report
            if build_metadata_changed:
                print >> report, "Build-time metadata changed: NO"
                print >> report
        else:
            if src_file.endswith(".dsc"):
                print >> report, fill(
                    "No problems were encountered during the "
                    "merge, so a source package has been "
                    "produced along with a patch containing "
                    "the differences from the %s version to "
                    "the new version." % right_distro.title())
                print >> report
                print >> report, fill(
                    "You should compare the generated patch "
                    "against the patch for the %s version "
                    "given above and ensure that there are "
                    "no unexpected changes.  You should also "
                    "sanity check the source package." % left_distro.title())
                print >> report

                print >> report, "generated: %s" % merged_version

                # Files from the dsc
                dsc = ControlFile("%s/%s" % (output_dir, src_file),
                                  multi_para=False,
                                  signed=True).para
                print >> report, "    %s" % src_file
                for md5sum, size, name in files(dsc):
                    print >> report, "    %s" % name
                print >> report
                if patch_file is not None:
                    print >> report, "%s -> generated" % right_distro
                    print >> report, "    %s" % patch_file
                    print >> report
                if build_metadata_changed:
                    print >> report, "Build-time metadata changed: NO"
                    print >> report
            else:
                print >> report, fill("Due to conflict or error, it was not "
                                      "possible to automatically create a "
                                      "source package.  Instead the result of "
                                      "the mergehas been placed into the "
                                      "following tar file which you will need "
                                      "to turn into a source package once the "
                                      "problems have been resolved.")
                print >> report
                print >> report, "    %s" % src_file
                print >> report

            if len(conflicts):
                print >> report
                print >> report, "Conflicts"
                print >> report, "========="
                print >> report
                print >> report, fill(
                    "In one or more cases, there were "
                    "different changes made in both %s and "
                    "%s to the same file; these are known as "
                    "conflicts." % (left_distro.title(), right_distro.title()))
                print >> report
                print >> report, fill("It is not possible for these to be "
                                      "automatically resolved, so this source "
                                      "needs human attention.")
                print >> report
                print >> report, fill(
                    "Those files marked with 'C ' contain "
                    "diff3 conflict markers, which can be "
                    "resolved using the text editor of your "
                    "choice. Those marked with 'C*' could "
                    "not be merged that way, so you will "
                    "find .%s and .%s files instead and "
                    "should chose one of them or a "
                    "combination of both, moving it to the "
                    "real filename and deleting the other." %
                    (left_distro.upper(), right_distro.upper()))
                print >> report

                conflicts.sort()
                for name in conflicts:
                    if os.path.isfile("%s/%s" % (merged_dir, name)):
                        print >> report, "  C  %s" % name
                    else:
                        print >> report, "  C* %s" % name
                print >> report

            if merged_version.revision is not None \
                    and left.version.upstream != merged_version.upstream:
                sa_arg = " -sa"
            else:
                sa_arg = ""

            print >> report
            print >> report, fill(
                "Once you have a source package you are "
                "happy to upload, you should make sure you "
                "include the orig.tar.gz if appropriate and "
                "information about all the versions included "
                "in the merge.")
            print >> report
            print >> report, fill("Use the following command to generate a "
                                  "correct .changes file:")
            print >> report
            print >> report, "  $ dpkg-genchanges -S -v%s%s" % (left.version,
                                                                sa_arg)
예제 #41
0
def write_text_report(left, left_patch,
                 base, tried_bases,
                 right, right_patch,
                 merged_version, conflicts, src_file, patch_file, output_dir,
                 merged_dir, merged_is_right, build_metadata_changed):
    """Write the merge report."""

    package = left.package.name
    assert package == right.package.name, (package, right.package.name)

    assert isinstance(left, PackageVersion)
    left_source = left.getSources()
    left_distro = left.package.distro.name

    if base is None:
        base_source = None
    else:
        assert isinstance(base, PackageVersion)
        base_source = base.getSources()

    assert isinstance(right, PackageVersion)
    right_source = right.getSources()
    right_distro = right.package.distro.name

    filename = "%s/REPORT" % output_dir
    tree.ensure(filename)
    with open(filename, "w") as report:
        # Package and time
        print >>report, "%s" % package
        print >>report, "%s" % time.ctime()
        print >>report

        # General rambling
        print >>report, fill("Below now follows the report of the automated "
                             "merge of the %s changes to the %s source "
                             "package against the new %s version."
                             % (left_distro.title(), package,
                                right_distro.title()))
        print >>report
        print >>report, fill("This file is designed to be both human readable "
                             "and machine-parseable.  Any line beginning with "
                             "four spaces is a file that should be downloaded "
                             "for the complete merge set.")
        print >>report
        print >>report

        print >>report, fill("Here are the particulars of the three versions "
                             "of %s that were chosen for the merge.  The base "
                             "is the newest version that is a common ancestor "
                             "of both the %s and %s packages.  It may be of "
                             "a different upstream version, but that's not "
                             "usually a problem."
                             % (package, left_distro.title(),
                                right_distro.title()))
        print >>report
        print >>report, fill("The files are the source package itself, and "
                             "the patch from the common base to that version.")
        print >>report

        # Base version and files
        if tried_bases:
            # We print this even if base_source is not None: we want to
            # record the better base versions we tried and failed to find
            print >>report, "missing base version(s):"
            for v in tried_bases:
                print >>report, " %s" % v

        if base_source is not None:
            print >>report, "base: %s" % base_source["Version"]
            for md5sum, size, name in files(base_source):
                print >>report, "    %s" % name
        print >>report

        # Left version and files
        print >>report, "our distro (%s): %s" % (left_distro, left_source["Version"])
        for md5sum, size, name in files(left_source):
            print >>report, "    %s" % name
        print >>report
        if left_patch is not None:
            print >>report, "base -> %s" % left_distro
            print >>report, "    %s" % left_patch
            print >>report

        # Right version and files
        print >>report, "source distro (%s): %s" % (right_distro, right_source["Version"])
        for md5sum, size, name in files(right_source):
            print >>report, "    %s" % name
        print >>report
        if right_patch is not None:
            print >>report, "base -> %s" % right_distro
            print >>report, "    %s" % right_patch
            print >>report

        # Generated section
        print >>report
        print >>report, "Generated Result"
        print >>report, "================"
        print >>report
        if base_source is None:
            print >>report, fill("Failed to merge because the base version "
                                 "required for a 3-way diff is missing from %s pool. "
                                 "You will need to either merge manually; or add the "
                                 "missing base version sources to '%s/%s/*/%s/' and run "
                                 "update_sources.py."
                                 % (right_distro, config.get('ROOT'), right_distro, package))
            print >>report
        elif merged_is_right:
            print >>report, fill("The %s version supercedes the %s version "
                                 "and can be added to %s with no changes." %
                                 (right_distro.title(), left_distro.title(),
                                  left_distro.title()))
            print >>report
            print >>report, "Merged without changes: YES"
            print >>report
            if build_metadata_changed:
                print >>report, "Build-time metadata changed: NO"
                print >>report
        else:
            if src_file.endswith(".dsc"):
                print >>report, fill("No problems were encountered during the "
                                    "merge, so a source package has been "
                                    "produced along with a patch containing "
                                    "the differences from the %s version to the "
                                    "new version." % right_distro.title())
                print >>report
                print >>report, fill("You should compare the generated patch "
                                    "against the patch for the %s version "
                                    "given above and ensure that there are no "
                                    "unexpected changes.  You should also "
                                    "sanity check the source package."
                                    % left_distro.title())
                print >>report

                print >>report, "generated: %s" % merged_version

                # Files from the dsc
                dsc = ControlFile("%s/%s" % (output_dir, src_file),
                                multi_para=False, signed=True).para
                print >>report, "    %s" % src_file
                for md5sum, size, name in files(dsc):
                    print >>report, "    %s" % name
                print >>report
                if patch_file is not None:
                    print >>report, "%s -> generated" % right_distro
                    print >>report, "    %s" % patch_file
                    print >>report
                if build_metadata_changed:
                    print >>report, "Build-time metadata changed: NO"
                    print >>report
            else:
                print >>report, fill("Due to conflict or error, it was not "
                                    "possible to automatically create a source "
                                    "package.  Instead the result of the merge "
                                    "has been placed into the following tar file "
                                    "which you will need to turn into a source "
                                    "package once the problems have been "
                                    "resolved.")
                print >>report
                print >>report, "    %s" % src_file
                print >>report

            if len(conflicts):
                print >>report
                print >>report, "Conflicts"
                print >>report, "========="
                print >>report
                print >>report, fill("In one or more cases, there were different "
                                    "changes made in both %s and %s to the same "
                                    "file; these are known as conflicts."
                                    % (left_distro.title(), right_distro.title()))
                print >>report
                print >>report, fill("It is not possible for these to be "
                                    "automatically resolved, so this source "
                                    "needs human attention.")
                print >>report
                print >>report, fill("Those files marked with 'C ' contain diff3 "
                                    "conflict markers, which can be resolved "
                                    "using the text editor of your choice.  "
                                    "Those marked with 'C*' could not be merged "
                                    "that way, so you will find .%s and .%s "
                                    "files instead and should chose one of them "
                                    "or a combination of both, moving it to the "
                                    "real filename and deleting the other."
                                    % (left_distro.upper(), right_distro.upper()))
                print >>report

                conflicts.sort()
                for name in conflicts:
                    if os.path.isfile("%s/%s" % (merged_dir, name)):
                        print >>report, "  C  %s" % name
                    else:
                        print >>report, "  C* %s" % name
                print >>report

            if merged_version.revision is not None \
                and Version(left_source["Version"]).upstream != merged_version.upstream:
                sa_arg = " -sa"
            else:
                sa_arg = ""

            print >>report
            print >>report, fill("Once you have a source package you are happy "
                                "to upload, you should make sure you include "
                                "the orig.tar.gz if appropriate and information "
                                "about all the versions included in the merge.")
            print >>report
            print >>report, fill("Use the following command to generate a "
                                "correct .changes file:")
            print >>report
            print >>report, "  $ dpkg-genchanges -S -v%s%s" \
                % (left_source["Version"], sa_arg)