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
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)
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)
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
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
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
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"))
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"))
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)
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
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))
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))
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)
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
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
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
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
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
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)
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
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)
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))
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:
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))
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
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
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)
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)
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)
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)
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)
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)
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)
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)
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
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
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
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())
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)
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)