diff --git a/SOURCES/0001-Consistently-produce-valid-URLs-by-prepending-protocol-RhBug1632121.patch b/SOURCES/0001-Consistently-produce-valid-URLs-by-prepending-protocol-RhBug1632121.patch new file mode 100644 index 0000000..72cdf63 --- /dev/null +++ b/SOURCES/0001-Consistently-produce-valid-URLs-by-prepending-protocol-RhBug1632121.patch @@ -0,0 +1,121 @@ +From f67be7b6045fb4be42ad226d0054edd685d33b9f Mon Sep 17 00:00:00 2001 +From: Aleš Matěj +Date: Tue, 12 Feb 2019 12:59:14 +0100 +Subject: [PATCH] Consistently produce valid URLs by prepending protocol. (RhBug:1632121) + +--- + src/mergerepo_c.c | 17 ++--------------- + src/xml_dump.c | 8 ++++++++ + src/xml_dump.h | 8 ++++++++ + src/xml_dump_primary.c | 6 ++++-- + 4 files changed, 22 insertions(+), 17 deletions(-) + +diff --git a/src/mergerepo_c.c b/src/mergerepo_c.c +index 94b929c..1ae3a9b 100644 +--- a/src/mergerepo_c.c ++++ b/src/mergerepo_c.c +@@ -496,17 +496,6 @@ cr_srpm_val_destroy(gpointer data) + } + + +-/** Prepend protocol if necessary +- */ +-static gchar * +-prepend_protocol(const gchar *url) +-{ +- if (url && *url == '/') +- return g_strconcat("file://", url, NULL); +- return g_strdup(url); +-} +- +- + int + koji_stuff_prepare(struct KojiMergedReposStuff **koji_stuff_ptr, + struct CmdOptions *cmd_options, +@@ -820,9 +809,7 @@ add_package(cr_Package *pkg, + if (!list) { + list = g_slist_prepend(list, pkg); + if ((!pkg->location_base || *pkg->location_base == '\0') && repopath) { +- _cleanup_free_ gchar *repopath_with_protocol = NULL; +- repopath_with_protocol = prepend_protocol(repopath); +- pkg->location_base = cr_safe_string_chunk_insert(pkg->chunk, repopath_with_protocol); ++ pkg->location_base = cr_safe_string_chunk_insert(pkg->chunk, repopath); + } + g_hash_table_insert (merged, (gpointer) pkg->name, (gpointer) list); + return 1; +@@ -1039,7 +1026,7 @@ merge_repos(GHashTable *merged, + // Koji-mergerepos specific behaviour ----------- + if (koji_stuff && koji_stuff->pkgorigins) { + _cleanup_free_ gchar *nvra = cr_package_nvra(pkg); +- _cleanup_free_ gchar *url = prepend_protocol(ml->original_url); ++ _cleanup_free_ gchar *url = cr_prepend_protocol(ml->original_url); + + cr_printf(NULL, + koji_stuff->pkgorigins, +diff --git a/src/xml_dump.c b/src/xml_dump.c +index 3fbb422..7a93231 100644 +--- a/src/xml_dump.c ++++ b/src/xml_dump.c +@@ -53,6 +53,14 @@ gboolean cr_hascontrollchars(const unsigned char *str) + return FALSE; + } + ++gchar * ++cr_prepend_protocol(const gchar *url) ++{ ++ if (url && *url == '/') ++ return g_strconcat("file://", url, NULL); ++ return g_strdup(url); ++} ++ + void + cr_latin1_to_utf8(const unsigned char *in, unsigned char *out) + { +diff --git a/src/xml_dump.h b/src/xml_dump.h +index 8bb11ed..4289fc2 100644 +--- a/src/xml_dump.h ++++ b/src/xml_dump.h +@@ -167,6 +167,14 @@ void cr_latin1_to_utf8(const unsigned char *in, + */ + gboolean cr_hascontrollchars(const unsigned char *str); + ++/** ++ * Prepend protocol if necessary ++ * ++ * @param url input url ++ * @return output string, must be freed ++ */ ++gchar *cr_prepend_protocol(const gchar *url); ++ + /** @} */ + + #ifdef __cplusplus +diff --git a/src/xml_dump_primary.c b/src/xml_dump_primary.c +index 5695e06..1f0292b 100644 +--- a/src/xml_dump_primary.c ++++ b/src/xml_dump_primary.c +@@ -30,7 +30,6 @@ + + #define ERR_DOMAIN CREATEREPO_C_ERROR + +- + typedef enum { + PCO_TYPE_PROVIDES, + PCO_TYPE_CONFLICTS, +@@ -279,9 +278,12 @@ cr_xml_dump_primary_base_items(xmlNodePtr root, cr_Package *package) + + // Write location attribute base + if (package->location_base && package->location_base[0] != '\0') { ++ gchar *location_base_with_protocol = NULL; ++ location_base_with_protocol = cr_prepend_protocol(package->location_base); + cr_xmlNewProp(location, + BAD_CAST "xml:base", +- BAD_CAST package->location_base); ++ BAD_CAST location_base_with_protocol); ++ g_free(location_base_with_protocol); + } + + // Write location attribute href +-- +libgit2 0.27.8 + diff --git a/SOURCES/0002-modifyrepo_c-Prevent-doubling-of-compression-testgzgz-RhBug1639287.patch b/SOURCES/0002-modifyrepo_c-Prevent-doubling-of-compression-testgzgz-RhBug1639287.patch new file mode 100644 index 0000000..97a5599 --- /dev/null +++ b/SOURCES/0002-modifyrepo_c-Prevent-doubling-of-compression-testgzgz-RhBug1639287.patch @@ -0,0 +1,130 @@ +From a535b3f25e4391f23d1cee46028827285e221de3 Mon Sep 17 00:00:00 2001 +From: Aleš Matěj +Date: Tue, 18 Jun 2019 13:49:27 +0200 +Subject: [PATCH] modifyrepo_c: Prevent doubling of compression (test.gz.gz) (RhBug:1639287) + +--- + src/compression_wrapper.c | 3 ++- + src/misc.c | 22 +++++++--------------- + src/modifyrepo_shared.c | 22 +++++++++++++++++++++- + 3 files changed, 30 insertions(+), 17 deletions(-) + +diff --git a/src/compression_wrapper.c b/src/compression_wrapper.c +index adc2f39..efb075c 100644 +--- a/src/compression_wrapper.c ++++ b/src/compression_wrapper.c +@@ -148,7 +148,8 @@ cr_detect_compression(const char *filename, GError **err) + } else if (g_str_has_suffix(filename, ".xz")) + { + return CR_CW_XZ_COMPRESSION; +- } else if (g_str_has_suffix(filename, ".xml")) ++ } else if (g_str_has_suffix(filename, ".xml") || ++ g_str_has_suffix(filename, ".sqlite")) + { + return CR_CW_NO_COMPRESSION; + } +diff --git a/src/misc.c b/src/misc.c +index 9937480..c5ccd12 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -437,7 +437,7 @@ cr_compress_file_with_stat(const char *src, + int ret = CRE_OK; + int readed; + char buf[BUFFER_SIZE]; +- FILE *orig = NULL; ++ CR_FILE *orig = NULL; + CR_FILE *new = NULL; + gchar *dst = (gchar *) in_dst; + GError *tmp_err = NULL; +@@ -466,7 +466,7 @@ cr_compress_file_with_stat(const char *src, + NULL); + } + +- orig = fopen(src, "rb"); ++ orig = cr_open(src, CR_CW_MODE_READ, CR_CW_AUTO_DETECT_COMPRESSION, &tmp_err); + if (orig == NULL) { + g_debug("%s: Cannot open source file %s (%s)", __func__, src, + g_strerror(errno)); +@@ -484,21 +484,13 @@ cr_compress_file_with_stat(const char *src, + goto compress_file_cleanup; + } + +- while ((readed = fread(buf, 1, BUFFER_SIZE, orig)) > 0) { +- if (readed != BUFFER_SIZE && ferror(orig)) { +- g_debug("%s: Error while copy %s -> %s (%s)", __func__, src, +- dst, g_strerror(errno)); +- g_set_error(err, ERR_DOMAIN, CRE_IO, +- "Error while read %s: %s", src, g_strerror(errno)); +- ret = CRE_IO; +- goto compress_file_cleanup; +- } +- +- cr_write(new, buf, readed, &tmp_err); ++ while ((readed = cr_read(orig, buf, BUFFER_SIZE, &tmp_err)) > 0) { ++ if (!tmp_err) ++ cr_write(new, buf, readed, &tmp_err); + if (tmp_err) { + g_debug("%s: Error while copy %s -> %s", __func__, src, dst); + g_propagate_prefixed_error(err, tmp_err, +- "Error while read %s: ", dst); ++ "Error while copy to %s: ", dst); + ret = CRE_IO; + goto compress_file_cleanup; + } +@@ -510,7 +502,7 @@ compress_file_cleanup: + g_free(dst); + + if (orig) +- fclose(orig); ++ cr_close(orig, NULL); + + if (new) + cr_close(new, NULL); +diff --git a/src/modifyrepo_shared.c b/src/modifyrepo_shared.c +index 805c894..91e56e8 100644 +--- a/src/modifyrepo_shared.c ++++ b/src/modifyrepo_shared.c +@@ -50,6 +50,23 @@ cr_modifyrepotask_free(cr_ModifyRepoTask *task) + g_free(task); + } + ++gchar * ++remove_compression_suffix_if_present(gchar* name, GError **err) ++{ ++ cr_CompressionType src_fn_com_type = cr_detect_compression(name, err); ++ if (src_fn_com_type != CR_CW_NO_COMPRESSION && src_fn_com_type != CR_CW_UNKNOWN_COMPRESSION){ ++ const gchar *src_suffix = cr_compression_suffix(src_fn_com_type); ++ if (src_suffix){ ++ if (g_str_has_suffix(name, src_suffix)){ ++ int name_len = strlen(name); ++ int suffix_len = strlen(src_suffix); ++ return g_strndup(name, name_len - suffix_len); ++ } ++ } ++ } ++ return g_strdup(name); ++} ++ + gboolean + cr_modifyrepo(GSList *modifyrepotasks, gchar *repopath, GError **err) + { +@@ -192,12 +209,15 @@ cr_modifyrepo(GSList *modifyrepotasks, gchar *repopath, GError **err) + suffix = cr_compression_suffix(compress_type); + } + ++ char* sufixless_src_fn = remove_compression_suffix_if_present(task->path, err); ++ + // Prepare dst filename - Get basename + _cleanup_free_ gchar *filename = NULL; + if (task->new_name) + filename = g_path_get_basename(task->new_name); + else +- filename = g_path_get_basename(src_fn); ++ filename = g_path_get_basename(sufixless_src_fn); ++ g_free(sufixless_src_fn); + + // Prepare dst filename - Add suffix + if (suffix) { +-- +libgit2 0.27.8 + diff --git a/SOURCES/0003-Correct-pkg-count-in-headers-if-there-were-invalid-pkgs-RhBug1596211.patch b/SOURCES/0003-Correct-pkg-count-in-headers-if-there-were-invalid-pkgs-RhBug1596211.patch new file mode 100644 index 0000000..e546c4b --- /dev/null +++ b/SOURCES/0003-Correct-pkg-count-in-headers-if-there-were-invalid-pkgs-RhBug1596211.patch @@ -0,0 +1,466 @@ +From dfe7218f07ffa70b73c51c71b0f051be926b6d92 Mon Sep 17 00:00:00 2001 +From: Aleš Matěj +Date: Tue, 14 May 2019 16:48:13 +0200 +Subject: [PATCH] Correct pkg count in headers if there were invalid pkgs (RhBug:1596211) + +--- + src/createrepo_c.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- + src/dumper_thread.c | 4 +++- + src/dumper_thread.h | 3 ++- + src/threads.c | 23 +++++++++++++++++++++++ + src/threads.h | 5 +++++ + src/xml_file.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + src/xml_file.h | 15 +++++++++++++++ + 7 files changed, 260 insertions(+), 16 deletions(-) + +diff --git a/src/createrepo_c.c b/src/createrepo_c.c +index e16ae34..67c2752 100644 +--- a/src/createrepo_c.c ++++ b/src/createrepo_c.c +@@ -124,7 +124,7 @@ fill_pool(GThreadPool *pool, + struct CmdOptions *cmd_options, + GSList **current_pkglist, + FILE *output_pkg_list, +- long *package_count, ++ long *task_count, + int media_id) + { + GQueue queue = G_QUEUE_INIT; +@@ -259,13 +259,13 @@ fill_pool(GThreadPool *pool, + + // Push sorted tasks into the thread pool + while ((task = g_queue_pop_head(&queue)) != NULL) { +- task->id = *package_count; ++ task->id = *task_count; + task->media_id = media_id; + g_thread_pool_push(pool, task, NULL); +- ++*package_count; ++ ++*task_count; + } + +- return *package_count; ++ return *task_count; + } + + +@@ -321,6 +321,27 @@ prepare_cache_dir(struct CmdOptions *cmd_options, + return TRUE; + } + ++/** Check if task finished without error, if yes ++ * use content stats of the new file ++ * ++ * @param task Rewrite pkg count task ++ * @param filename Name of file with wrong package count ++ * @param exit_val If errors occured set createrepo_c exit value ++ * @param content_stat Content stats for filename ++ * ++ */ ++static void ++error_check_and_set_content_stat(cr_CompressionTask *task, char *filename, int *exit_val, cr_ContentStat **content_stat){ ++ if (task->err) { ++ g_critical("Cannot rewrite pkg count in %s: %s", ++ filename, task->err->message); ++ *exit_val = 2; ++ }else{ ++ cr_contentstat_free(*content_stat, NULL); ++ *content_stat = task->stat; ++ task->stat = NULL; ++ } ++} + + int + main(int argc, char **argv) +@@ -478,7 +499,7 @@ main(int argc, char **argv) + NULL); + g_debug("Thread pool ready"); + +- long package_count = 0; ++ long task_count = 0; + GSList *current_pkglist = NULL; + /* ^^^ List with basenames of files which will be processed */ + +@@ -490,26 +511,26 @@ main(int argc, char **argv) + cmd_options, + ¤t_pkglist, + output_pkg_list, +- &package_count, ++ &task_count, + media_id); + g_free(tmp_in_dir); + } + +- g_debug("Package count: %ld", package_count); +- g_message("Directory walk done - %ld packages", package_count); ++ g_debug("Package count: %ld", task_count); ++ g_message("Directory walk done - %ld packages", task_count); + + if (output_pkg_list) + fclose(output_pkg_list); + + + // Load old metadata if --update + cr_Metadata *old_metadata = NULL; + struct cr_MetadataLocation *old_metadata_location = NULL; + +- if (!package_count) ++ if (!task_count) + g_debug("No packages found - skipping metadata loading"); + +- if (package_count && cmd_options->update) { ++ if (task_count && cmd_options->update) { + int ret; + old_metadata = cr_metadata_new(CR_HT_KEY_FILENAME, 1, current_pkglist); + cr_metadata_set_dupaction(old_metadata, CR_HT_DUPACT_REMOVEALL); +@@ -741,9 +762,9 @@ main(int argc, char **argv) + + // Set number of packages + g_debug("Setting number of packages"); +- cr_xmlfile_set_num_of_pkgs(pri_cr_file, package_count, NULL); +- cr_xmlfile_set_num_of_pkgs(fil_cr_file, package_count, NULL); +- cr_xmlfile_set_num_of_pkgs(oth_cr_file, package_count, NULL); ++ cr_xmlfile_set_num_of_pkgs(pri_cr_file, task_count, NULL); ++ cr_xmlfile_set_num_of_pkgs(fil_cr_file, task_count, NULL); ++ cr_xmlfile_set_num_of_pkgs(oth_cr_file, task_count, NULL); + + // Open sqlite databases + gchar *pri_db_filename = NULL; +@@ -832,7 +853,8 @@ main(int argc, char **argv) + user_data.checksum_cachedir = cmd_options->checksum_cachedir; + user_data.skip_symlinks = cmd_options->skip_symlinks; + user_data.repodir_name_len = strlen(in_dir); +- user_data.package_count = package_count; ++ user_data.task_count = task_count; ++ user_data.package_count = 0; + user_data.skip_stat = cmd_options->skip_stat; + user_data.old_metadata = old_metadata; + user_data.mutex_pri = g_mutex_new(); +@@ -876,6 +898,59 @@ main(int argc, char **argv) + cr_xmlfile_close(fil_cr_file, NULL); + cr_xmlfile_close(oth_cr_file, NULL); + ++ ++ /* At the time of writing xml metadata headers we haven't yet parsed all ++ * the packages and we don't know whether there were some invalid ones, ++ * therefore we write the task count into the headers instead of the actual package count. ++ * If there actually were some invalid packages we have to correct this value ++ * that unfortunately means we have to decompress metadata files change package ++ * count value and compress them again. ++ */ ++ if (user_data.package_count != user_data.task_count){ ++ g_message("Warning: There were some invalid packages: we have to recompress other, filelists and primary xml metadata files in order to have correct package counts"); ++ ++ GThreadPool *rewrite_pkg_count_pool = g_thread_pool_new(cr_rewrite_pkg_count_thread, ++ &user_data, 3, FALSE, NULL); ++ ++ cr_CompressionTask *pri_rewrite_pkg_count_task; ++ cr_CompressionTask *fil_rewrite_pkg_count_task; ++ cr_CompressionTask *oth_rewrite_pkg_count_task; ++ ++ pri_rewrite_pkg_count_task = cr_compressiontask_new(pri_xml_filename, ++ NULL, ++ xml_compression, ++ cmd_options->repomd_checksum_type, ++ 1, ++ &tmp_err); ++ g_thread_pool_push(rewrite_pkg_count_pool, pri_rewrite_pkg_count_task, NULL); ++ ++ fil_rewrite_pkg_count_task = cr_compressiontask_new(fil_xml_filename, ++ NULL, ++ xml_compression, ++ cmd_options->repomd_checksum_type, ++ 1, ++ &tmp_err); ++ g_thread_pool_push(rewrite_pkg_count_pool, fil_rewrite_pkg_count_task, NULL); ++ ++ oth_rewrite_pkg_count_task = cr_compressiontask_new(oth_xml_filename, ++ NULL, ++ xml_compression, ++ cmd_options->repomd_checksum_type, ++ 1, ++ &tmp_err); ++ g_thread_pool_push(rewrite_pkg_count_pool, oth_rewrite_pkg_count_task, NULL); ++ ++ g_thread_pool_free(rewrite_pkg_count_pool, FALSE, TRUE); ++ ++ error_check_and_set_content_stat(pri_rewrite_pkg_count_task, pri_xml_filename, &exit_val, &pri_stat); ++ error_check_and_set_content_stat(fil_rewrite_pkg_count_task, fil_xml_filename, &exit_val, &fil_stat); ++ error_check_and_set_content_stat(oth_rewrite_pkg_count_task, oth_xml_filename, &exit_val, &oth_stat); ++ ++ cr_compressiontask_free(pri_rewrite_pkg_count_task, NULL); ++ cr_compressiontask_free(fil_rewrite_pkg_count_task, NULL); ++ cr_compressiontask_free(oth_rewrite_pkg_count_task, NULL); ++ } ++ + g_queue_free(user_data.buffer); + g_mutex_free(user_data.mutex_buffer); + g_cond_free(user_data.cond_pri); +diff --git a/src/dumper_thread.c b/src/dumper_thread.c +index fbaa5be..e282f96 100644 +--- a/src/dumper_thread.c ++++ b/src/dumper_thread.c +@@ -74,6 +74,8 @@ write_pkg(long id, + g_mutex_lock(udata->mutex_pri); + while (udata->id_pri != id) + g_cond_wait (udata->cond_pri, udata->mutex_pri); ++ ++ udata->package_count++; + ++udata->id_pri; + cr_xmlfile_add_chunk(udata->pri_f, (const char *) res.primary, &tmp_err); + if (tmp_err) { +@@ -476,7 +478,7 @@ cr_dumper_thread(gpointer data, gpointer user_data) + + if (g_queue_get_length(udata->buffer) < MAX_TASK_BUFFER_LEN + && udata->id_pri != task->id +- && udata->package_count > (task->id + 1)) ++ && udata->task_count > (task->id + 1)) + { + // If: + // * this isn't our turn +diff --git a/src/dumper_thread.h b/src/dumper_thread.h +index ed21053..4e18869 100644 +--- a/src/dumper_thread.h ++++ b/src/dumper_thread.h +@@ -61,7 +61,8 @@ struct UserData { + cr_ChecksumType checksum_type; // Constant representing selected checksum + const char *checksum_cachedir; // Dir with cached checksums + gboolean skip_symlinks; // Skip symlinks +- long package_count; // Total number of packages to process ++ long task_count; // Total number of task to process ++ long package_count; // Total number of packages processed + + // Update stuff + gboolean skip_stat; // Skip stat() while updating +diff --git a/src/threads.c b/src/threads.c +index aee07d1..844e900 100644 +--- a/src/threads.c ++++ b/src/threads.c +@@ -21,6 +21,7 @@ + #include "threads.h" + #include "error.h" + #include "misc.h" ++#include "dumper_thread.h" + + #define ERR_DOMAIN CREATEREPO_C_ERROR + +@@ -108,6 +109,28 @@ cr_compressing_thread(gpointer data, G_GNUC_UNUSED gpointer user_data) + } + } + ++void ++cr_rewrite_pkg_count_thread(gpointer data, gpointer user_data) ++{ ++ cr_CompressionTask *task = data; ++ struct UserData *ud = user_data; ++ GError *tmp_err = NULL; ++ ++ assert(task); ++ ++ cr_rewrite_header_package_count(task->src, ++ task->type, ++ ud->package_count, ++ ud->task_count, ++ task->stat, ++ &tmp_err); ++ ++ if (tmp_err) { ++ // Error encountered ++ g_propagate_error(&task->err, tmp_err); ++ } ++} ++ + /** Parallel Repomd Record Fill */ + + cr_RepomdRecordFillTask * +diff --git a/src/threads.h b/src/threads.h +index 2d554cd..19ba917 100644 +--- a/src/threads.h ++++ b/src/threads.h +@@ -150,6 +150,11 @@ cr_repomdrecordfilltask_free(cr_RepomdRecordFillTask *task, GError **err); + void + cr_repomd_record_fill_thread(gpointer data, gpointer user_data); + ++/** Function for GThread Pool. ++ */ ++void ++cr_rewrite_pkg_count_thread(gpointer data, gpointer user_data); ++ + /** @} */ + + #ifdef __cplusplus +diff --git a/src/xml_file.c b/src/xml_file.c +index 65fb945..1d670ae 100644 +--- a/src/xml_file.c ++++ b/src/xml_file.c +@@ -18,8 +18,10 @@ + */ + + #include ++#include + #include + #include "xml_file.h" ++#include + #include "error.h" + #include "xml_dump.h" + #include "compression_wrapper.h" +@@ -40,6 +42,9 @@ + #define XML_PRESTODELTA_HEADER XML_HEADER"\n" + #define XML_UPDATEINFO_HEADER XML_HEADER"\n" + ++#define XML_MAX_HEADER_SIZE 300 ++#define XML_RECOMPRESS_BUFFER_SIZE 8192 ++ + #define XML_PRIMARY_FOOTER "" + #define XML_FILELISTS_FOOTER "" + #define XML_OTHER_FOOTER "" +@@ -317,3 +322,121 @@ cr_xmlfile_close(cr_XmlFile *f, GError **err) + + return CRE_OK; + } ++ ++static int ++write_modified_header(int task_count, ++ int package_count, ++ cr_XmlFile *cr_file, ++ gchar *header_buf, ++ int header_len, ++ GError **err) ++{ ++ GError *tmp_err = NULL; ++ gchar *package_count_string; ++ gchar *task_count_string; ++ int bytes_written = 0; ++ int package_count_string_len = rasprintf(&package_count_string, "packages=\"%i\"", package_count); ++ int task_count_string_len = rasprintf(&task_count_string, "packages=\"%i\"", task_count); ++ ++ gchar *pointer_to_pkgs = strstr(header_buf, task_count_string); ++ if (!pointer_to_pkgs){ ++ g_free(package_count_string); ++ g_free(task_count_string); ++ return 0; ++ } ++ gchar *pointer_to_pkgs_end = pointer_to_pkgs + task_count_string_len; ++ ++ bytes_written += cr_write(cr_file->f, header_buf, pointer_to_pkgs - header_buf, &tmp_err); ++ if (!tmp_err) ++ bytes_written += cr_write(cr_file->f, package_count_string, package_count_string_len, &tmp_err); ++ if (!tmp_err) ++ bytes_written += cr_write(cr_file->f, pointer_to_pkgs_end, header_len - (pointer_to_pkgs_end - header_buf), &tmp_err); ++ if (tmp_err) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while writing header part:"); ++ g_free(package_count_string); ++ g_free(task_count_string); ++ return 0; ++ } ++ g_free(package_count_string); ++ g_free(task_count_string); ++ return bytes_written; ++} ++ ++void ++cr_rewrite_header_package_count(gchar *original_filename, ++ cr_CompressionType xml_compression, ++ int package_count, ++ int task_count, ++ cr_ContentStat *file_stat, ++ GError **err) ++{ ++ GError *tmp_err = NULL; ++ CR_FILE *original_file = cr_open(original_filename, CR_CW_MODE_READ, CR_CW_AUTO_DETECT_COMPRESSION, &tmp_err); ++ if (tmp_err) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while reopening for reading:"); ++ return; ++ } ++ ++ gchar *tmp_xml_filename = g_strconcat(original_filename, ".tmp", NULL); ++ cr_XmlFile *new_file = cr_xmlfile_sopen_primary(tmp_xml_filename, ++ xml_compression, ++ file_stat, ++ &tmp_err); ++ if (tmp_err) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while opening for writing:"); ++ cr_close(original_file, NULL); ++ g_free(tmp_xml_filename); ++ return; ++ } ++ ++ gchar header_buf[XML_MAX_HEADER_SIZE]; ++ int len_read = cr_read(original_file, header_buf, XML_MAX_HEADER_SIZE, &tmp_err); ++ if (!tmp_err) ++ write_modified_header(task_count, package_count, new_file, header_buf, len_read, &tmp_err); ++ if (tmp_err) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while recompressing:"); ++ cr_xmlfile_close(new_file, NULL); ++ cr_close(original_file, NULL); ++ g_free(tmp_xml_filename); ++ return; ++ } ++ //Copy the rest of the file ++ gchar copy_buf[XML_RECOMPRESS_BUFFER_SIZE]; ++ while(len_read) ++ { ++ len_read = cr_read(original_file, copy_buf, XML_RECOMPRESS_BUFFER_SIZE, &tmp_err); ++ if (!tmp_err) ++ cr_write(new_file->f, copy_buf, len_read, &tmp_err); ++ if (tmp_err) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while recompressing:"); ++ cr_xmlfile_close(new_file, NULL); ++ cr_close(original_file, NULL); ++ g_free(tmp_xml_filename); ++ return; ++ } ++ } ++ ++ new_file->header = 1; ++ new_file->footer = 1; ++ ++ cr_xmlfile_close(new_file, &tmp_err); ++ if (tmp_err) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while writing:"); ++ cr_close(original_file, NULL); ++ g_free(tmp_xml_filename); ++ return; ++ } ++ cr_close(original_file, &tmp_err); ++ if (tmp_err) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while writing:"); ++ g_free(tmp_xml_filename); ++ return; ++ } ++ ++ if (g_rename(tmp_xml_filename, original_filename) == -1) { ++ g_propagate_prefixed_error(err, tmp_err, "Error encountered while renaming:"); ++ g_free(tmp_xml_filename); ++ return; ++ } ++ g_free(tmp_xml_filename); ++} +diff --git a/src/xml_file.h b/src/xml_file.h +index 96ef5e3..6ac4c97 100644 +--- a/src/xml_file.h ++++ b/src/xml_file.h +@@ -221,6 +221,21 @@ int cr_xmlfile_add_chunk(cr_XmlFile *f, const char *chunk, GError **err); + */ + int cr_xmlfile_close(cr_XmlFile *f, GError **err); + ++/** Rewrite package count field in repodata header in xml file. ++ * In order to do this we have to decompress and after the change ++ * compress the whole file again, so entirely new file is created. ++ * @param original_filename Current file with wrong value in header ++ * @param package_count Actual package count (desired value in header) ++ * @param task_count Task count (current value in header) ++ * @param file_stat cr_ContentStat for stats of the new file, it will be modified ++ * @param err **GError ++ */ ++void cr_rewrite_header_package_count(gchar *original_filename, ++ cr_CompressionType xml_compression, ++ int package_count, ++ int task_count, ++ cr_ContentStat *file_stat, ++ GError **err); + + /** @} */ + +-- +libgit2 0.27.8 + diff --git a/SOURCES/0004-Add-support-for-modular-errata-RhBug1656584.patch b/SOURCES/0004-Add-support-for-modular-errata-RhBug1656584.patch new file mode 100644 index 0000000..3765350 --- /dev/null +++ b/SOURCES/0004-Add-support-for-modular-errata-RhBug1656584.patch @@ -0,0 +1,1329 @@ +From a48db44b73785b5d5fbe8ae827522695fa0fd9ce Mon Sep 17 00:00:00 2001 +From: Aleš Matěj +Date: Tue, 8 Jan 2019 15:44:55 +0100 +Subject: [PATCH] Add support for modular errata (RhBug:1656584) + +--- + src/python/CMakeLists.txt | 1 + + src/python/__init__.py | 3 +++ + src/python/createrepo_cmodule.c | 8 ++++++++ + src/python/updatecollection-py.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + src/python/updatecollectionmodule-py.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + src/python/updatecollectionmodule-py.h | 33 +++++++++++++++++++++++++++++++++ + src/updateinfo.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ + src/updateinfo.h | 24 ++++++++++++++++++++++++ + src/xml_dump_updateinfo.c | 20 ++++++++++++++++++++ + src/xml_parser_internal.h | 2 ++ + src/xml_parser_updateinfo.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + tests/fixtures.h | 1 + + tests/python/tests/test_updatecollection.py | 16 ++++++++++++++++ + tests/python/tests/test_updatecollectionmodule.py | 31 +++++++++++++++++++++++++++++++ + tests/python/tests/test_updateinfo.py | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + tests/test_xml_parser_updateinfo.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + tests/testdata/updateinfo_files/updateinfo_03.xml | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 17 files changed, 957 insertions(+) + create mode 100644 src/python/updatecollectionmodule-py.c + create mode 100644 src/python/updatecollectionmodule-py.h + create mode 100644 tests/python/tests/test_updatecollectionmodule.py + create mode 100644 tests/testdata/updateinfo_files/updateinfo_03.xml + +diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt +index 9f1ac64..ebf4d4c 100644 +--- a/src/python/CMakeLists.txt ++++ b/src/python/CMakeLists.txt +@@ -50,6 +50,7 @@ SET (createrepo_cmodule_SRCS + sqlite-py.c + typeconversion.c + updatecollection-py.c ++ updatecollectionmodule-py.c + updatecollectionpackage-py.c + updateinfo-py.c + updaterecord-py.c +diff --git a/src/python/__init__.py b/src/python/__init__.py +index 6c29e74..65d7f82 100644 +--- a/src/python/__init__.py ++++ b/src/python/__init__.py +@@ -206,6 +206,9 @@ class OtherSqlite(Sqlite): + + UpdateCollection = _createrepo_c.UpdateCollection + ++# UpdateCollectionModule class ++ ++UpdateCollectionModule = _createrepo_c.UpdateCollectionModule + + # UpdateCollectionPackage class + +diff --git a/src/python/createrepo_cmodule.c b/src/python/createrepo_cmodule.c +index fe4d2ad..9be5f46 100644 +--- a/src/python/createrepo_cmodule.c ++++ b/src/python/createrepo_cmodule.c +@@ -35,6 +35,7 @@ + #include "repomdrecord-py.h" + #include "sqlite-py.h" + #include "updatecollection-py.h" ++#include "updatecollectionmodule-py.h" + #include "updatecollectionpackage-py.h" + #include "updateinfo-py.h" + #include "updaterecord-py.h" +@@ -185,6 +186,13 @@ init_createrepo_c(void) + PyModule_AddObject(m, "UpdateCollection", + (PyObject *)&UpdateCollection_Type); + ++ /* _createrepo_c.UpdateCollectionModule */ ++ if (PyType_Ready(&UpdateCollectionModule_Type) < 0) ++ return FAILURE; ++ Py_INCREF(&UpdateCollectionModule_Type); ++ PyModule_AddObject(m, "UpdateCollectionModule", ++ (PyObject *)&UpdateCollectionModule_Type); ++ + /* _createrepo_c.UpdateCollectionPackage */ + if (PyType_Ready(&UpdateCollectionPackage_Type) < 0) + return FAILURE; +diff --git a/src/python/updatecollection-py.c b/src/python/updatecollection-py.c +index 3a791be..ca97657 100644 +--- a/src/python/updatecollection-py.c ++++ b/src/python/updatecollection-py.c +@@ -22,6 +22,7 @@ + #include + + #include "updatecollection-py.h" ++#include "updatecollectionmodule-py.h" + #include "updatecollectionpackage-py.h" + #include "exception-py.h" + #include "typeconversion.h" +@@ -188,6 +189,13 @@ typedef int (*ConversionToCheckFunc)(PyObject *); + typedef void *(*ConversionToFunc)(PyObject *, GStringChunk *); + + PyObject * ++PyObject_FromUpdateCollectionModule(cr_UpdateCollectionModule *module) ++{ ++ return Object_FromUpdateCollectionModule( ++ cr_updatecollectionmodule_copy(module)); ++} ++ ++PyObject * + PyObject_FromUpdateCollectionPackage(cr_UpdateCollectionPackage *pkg) + { + return Object_FromUpdateCollectionPackage( +@@ -249,6 +257,23 @@ get_list(_UpdateCollectionObject *self, void *conv) + return list; + } + ++static PyObject * ++get_module(_UpdateCollectionObject *self, void *member_offset) ++{ ++ if (check_UpdateCollectionStatus(self)) ++ return NULL; ++ ++ cr_UpdateCollection *collection = self->collection; ++ ++ cr_UpdateCollectionModule *module = *((cr_UpdateCollectionModule **) ((size_t) collection + (size_t) member_offset)); ++ if (module == NULL) ++ Py_RETURN_NONE; ++ ++ PyObject *py_module = PyObject_FromUpdateCollectionModule(module); ++ ++ return py_module; ++} ++ + static int + set_str(_UpdateCollectionObject *self, PyObject *value, void *member_offset) + { +@@ -265,11 +290,29 @@ set_str(_UpdateCollectionObject *self, PyObject *value, void *member_offset) + return 0; + } + ++static int ++set_module(_UpdateCollectionObject *self, PyObject *value, void *member_offset) ++{ ++ if (check_UpdateCollectionStatus(self)) ++ return -1; ++ if (!UpdateCollectionModuleObject_Check(value) && value != Py_None) { ++ PyErr_SetString(PyExc_TypeError, "Module or None expected!"); ++ return -1; ++ } ++ cr_UpdateCollectionModule *module = UpdateCollectionModule_FromPyObject(value); ++ cr_UpdateCollection *collection = self->collection; ++ *((cr_UpdateCollectionModule **) ((size_t) collection + (size_t) member_offset)) = module; ++ ++ return 0; ++} ++ + static PyGetSetDef updatecollection_getsetters[] = { + {"shortname", (getter)get_str, (setter)set_str, + "Short name", OFFSET(shortname)}, + {"name", (getter)get_str, (setter)set_str, + "Name of the collection", OFFSET(name)}, ++ {"module", (getter)get_module, (setter)set_module, ++ "Module information", OFFSET(module)}, + {"packages", (getter)get_list, (setter)NULL, + "List of packages", &(list_convertors[0])}, + {NULL, NULL, NULL, NULL, NULL} /* sentinel */ +diff --git a/src/python/updatecollectionmodule-py.c b/src/python/updatecollectionmodule-py.c +new file mode 100644 +index 0000000..20b2d99 +--- /dev/null ++++ b/src/python/updatecollectionmodule-py.c +@@ -0,0 +1,274 @@ ++/* createrepo_c - Library of routines for manipulation with repodata ++ * Copyright (C) 2013 Tomas Mlcoch ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, ++ * USA. ++ */ ++ ++#include ++#include ++#include ++ ++#include "updatecollectionmodule-py.h" ++#include "exception-py.h" ++#include "typeconversion.h" ++#include "contentstat-py.h" ++ ++typedef struct { ++ PyObject_HEAD ++ cr_UpdateCollectionModule *module; ++} _UpdateCollectionModuleObject; ++ ++PyObject * ++Object_FromUpdateCollectionModule(cr_UpdateCollectionModule *mod) ++{ ++ PyObject *py_rec; ++ ++ if (!mod) { ++ PyErr_SetString(PyExc_ValueError, "Expected a cr_UpdateCollectionModule pointer not NULL."); ++ return NULL; ++ } ++ ++ py_rec = PyObject_CallObject((PyObject *) &UpdateCollectionModule_Type, NULL); ++ cr_updatecollectionmodule_free(((_UpdateCollectionModuleObject *)py_rec)->module); ++ ((_UpdateCollectionModuleObject *)py_rec)->module = mod; ++ ++ return py_rec; ++} ++ ++cr_UpdateCollectionModule * ++UpdateCollectionModule_FromPyObject(PyObject *o) ++{ ++ if (!UpdateCollectionModuleObject_Check(o)) { ++ PyErr_SetString(PyExc_TypeError, "Expected a UpdateCollectionModule object."); ++ return NULL; ++ } ++ return ((_UpdateCollectionModuleObject *)o)->module; ++} ++ ++static int ++check_UpdateCollectionModuleStatus(const _UpdateCollectionModuleObject *self) ++{ ++ assert(self != NULL); ++ assert(UpdateCollectionModuleObject_Check(self)); ++ if (self->module == NULL) { ++ PyErr_SetString(CrErr_Exception, "Improper createrepo_c UpdateCollectionModule object."); ++ return -1; ++ } ++ return 0; ++} ++ ++/* Function on the type */ ++ ++static PyObject * ++updatecollectionmodule_new(PyTypeObject *type, ++ G_GNUC_UNUSED PyObject *args, ++ G_GNUC_UNUSED PyObject *kwds) ++{ ++ _UpdateCollectionModuleObject *self = (_UpdateCollectionModuleObject *)type->tp_alloc(type, 0); ++ if (self) { ++ self->module = NULL; ++ } ++ return (PyObject *)self; ++} ++ ++PyDoc_STRVAR(updatecollectionmodule_init__doc__, ++".. method:: __init__()\n\n"); ++ ++static int ++updatecollectionmodule_init(_UpdateCollectionModuleObject *self, ++ G_GNUC_UNUSED PyObject *args, ++ G_GNUC_UNUSED PyObject *kwds) ++{ ++ /* Free all previous resources when reinitialization */ ++ if (self->module) ++ cr_updatecollectionmodule_free(self->module); ++ ++ /* Init */ ++ self->module = cr_updatecollectionmodule_new(); ++ if (self->module == NULL) { ++ PyErr_SetString(CrErr_Exception, "UpdateCollectionModule initialization failed"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static void ++updatecollectionmodule_dealloc(_UpdateCollectionModuleObject *self) ++{ ++ if (self->module) ++ cr_updatecollectionmodule_free(self->module); ++ Py_TYPE(self)->tp_free(self); ++} ++ ++static PyObject * ++updatecollectionmodule_repr(G_GNUC_UNUSED _UpdateCollectionModuleObject *self) ++{ ++ return PyUnicode_FromFormat(""); ++} ++ ++/* UpdateCollectionModule methods */ ++ ++PyDoc_STRVAR(copy__doc__, ++"copy() -> UpdateCollectionModule\n\n" ++"Return copy of the UpdateCollectionModule object"); ++ ++static PyObject * ++copy_updatecollectionmodule(_UpdateCollectionModuleObject *self, ++ G_GNUC_UNUSED void *nothing) ++{ ++ if (check_UpdateCollectionModuleStatus(self)) ++ return NULL; ++ return Object_FromUpdateCollectionModule(cr_updatecollectionmodule_copy(self->module)); ++} ++ ++static struct PyMethodDef updatecollectionmodule_methods[] = { ++ {"copy", (PyCFunction)copy_updatecollectionmodule, METH_NOARGS, ++ copy__doc__}, ++ {NULL} /* sentinel */ ++}; ++ ++/* getsetters */ ++ ++#define OFFSET(member) (void *) offsetof(cr_UpdateCollectionModule, member) ++ ++static PyObject * ++get_str(_UpdateCollectionModuleObject *self, void *member_offset) ++{ ++ if (check_UpdateCollectionModuleStatus(self)) ++ return NULL; ++ cr_UpdateCollectionModule *module = self->module; ++ char *str = *((char **) ((size_t) module + (size_t) member_offset)); ++ if (str == NULL) ++ Py_RETURN_NONE; ++ return PyUnicode_FromString(str); ++} ++ ++static PyObject * ++get_uint(_UpdateCollectionModuleObject *self, void *member_offset) ++{ ++ if (check_UpdateCollectionModuleStatus(self)) ++ return NULL; ++ cr_UpdateCollectionModule *module = self->module; ++ guint64 val = *((guint64 *) ((size_t) module + (size_t) member_offset)); ++ return PyLong_FromUnsignedLongLong((guint64) val); ++} ++ ++static int ++set_str(_UpdateCollectionModuleObject *self, PyObject *value, void *member_offset) ++{ ++ if (check_UpdateCollectionModuleStatus(self)) ++ return -1; ++ if (!PyUnicode_Check(value) && !PyBytes_Check(value) && value != Py_None) { ++ PyErr_SetString(PyExc_TypeError, "Unicode, bytes, or None expected!"); ++ return -1; ++ } ++ ++ if (PyUnicode_Check(value)) { ++ value = PyUnicode_AsUTF8String(value); ++ } ++ ++ cr_UpdateCollectionModule *module = self->module; ++ char *str = cr_safe_string_chunk_insert(module->chunk, ++ PyObject_ToStrOrNull(value)); ++ ++ *((char **) ((size_t) module + (size_t) member_offset)) = str; ++ return 0; ++} ++ ++static int ++set_uint(_UpdateCollectionModuleObject *self, PyObject *value, void *member_offset) ++{ ++ if (check_UpdateCollectionModuleStatus(self)) ++ return -1; ++ guint64 val; ++ ++ if (PyLong_Check(value)) { ++ val = PyLong_AsUnsignedLongLong(value); ++ } else if (PyFloat_Check(value)) { ++ val = (guint64) PyFloat_AS_DOUBLE(value); ++#if PY_MAJOR_VERSION < 3 ++ } else if (PyInt_Check(value)) { ++ val = PyInt_AS_LONG(value); ++#endif ++ } else { ++ PyErr_SetString(PyExc_TypeError, "Number expected!"); ++ return -1; ++ } ++ ++ cr_UpdateCollectionModule *module = self->module; ++ *((guint64 *) ((size_t) module + (size_t) member_offset)) = (guint64) val; ++ return 0; ++} ++ ++static PyGetSetDef updatecollectionmodule_getsetters[] = { ++ {"name", (getter)get_str, (setter)set_str, ++ "Name", OFFSET(name)}, ++ {"stream", (getter)get_str, (setter)set_str, ++ "Stream", OFFSET(stream)}, ++ {"version", (getter)get_uint, (setter)set_uint, ++ "Version", OFFSET(version)}, ++ {"context", (getter)get_str, (setter)set_str, ++ "Context", OFFSET(context)}, ++ {"arch", (getter)get_str, (setter)set_str, ++ "Arch", OFFSET(arch)}, ++ {NULL, NULL, NULL, NULL, NULL} /* sentinel */ ++}; ++ ++/* Object */ ++ ++PyTypeObject UpdateCollectionModule_Type = { ++ PyVarObject_HEAD_INIT(NULL, 0) ++ "createrepo_c.UpdateCollectionModule", /* tp_name */ ++ sizeof(_UpdateCollectionModuleObject), /* tp_basicsize */ ++ 0, /* tp_itemsize */ ++ (destructor) updatecollectionmodule_dealloc, /* tp_dealloc */ ++ 0, /* tp_print */ ++ 0, /* tp_getattr */ ++ 0, /* tp_setattr */ ++ 0, /* tp_compare */ ++ (reprfunc) updatecollectionmodule_repr,/* tp_repr */ ++ 0, /* tp_as_number */ ++ 0, /* tp_as_sequence */ ++ 0, /* tp_as_mapping */ ++ 0, /* tp_hash */ ++ 0, /* tp_call */ ++ 0, /* tp_str */ ++ 0, /* tp_getattro */ ++ 0, /* tp_setattro */ ++ 0, /* tp_as_buffer */ ++ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ ++ updatecollectionmodule_init__doc__, /* tp_doc */ ++ 0, /* tp_traverse */ ++ 0, /* tp_clear */ ++ 0, /* tp_richcompare */ ++ 0, /* tp_weaklistoffset */ ++ PyObject_SelfIter, /* tp_iter */ ++ 0, /* tp_iternext */ ++ updatecollectionmodule_methods, /* tp_methods */ ++ 0, /* tp_members */ ++ updatecollectionmodule_getsetters, /* tp_getset */ ++ 0, /* tp_base */ ++ 0, /* tp_dict */ ++ 0, /* tp_descr_get */ ++ 0, /* tp_descr_set */ ++ 0, /* tp_dictoffset */ ++ (initproc) updatecollectionmodule_init,/* tp_init */ ++ 0, /* tp_alloc */ ++ updatecollectionmodule_new, /* tp_new */ ++ 0, /* tp_free */ ++ 0, /* tp_is_gc */ ++}; +diff --git a/src/python/updatecollectionmodule-py.h b/src/python/updatecollectionmodule-py.h +new file mode 100644 +index 0000000..5847259 +--- /dev/null ++++ b/src/python/updatecollectionmodule-py.h +@@ -0,0 +1,33 @@ ++/* createrepo_c - Library of routines for manipulation with repodata ++ * Copyright (C) 2013 Tomas Mlcoch ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, ++ * USA. ++ */ ++ ++#ifndef CR_UPDATECOLLECTIONMODULE_PY_H ++#define CR_UPDATECOLLECTIONMODULE_PY_H ++ ++#include "src/createrepo_c.h" ++ ++extern PyTypeObject UpdateCollectionModule_Type; ++ ++#define UpdateCollectionModuleObject_Check(o) \ ++ PyObject_TypeCheck(o, &UpdateCollectionModule_Type) ++ ++PyObject *Object_FromUpdateCollectionModule(cr_UpdateCollectionModule *rec); ++cr_UpdateCollectionModule *UpdateCollectionModule_FromPyObject(PyObject *o); ++ ++#endif +diff --git a/src/updateinfo.c b/src/updateinfo.c +index 6e45229..cdc4747 100644 +--- a/src/updateinfo.c ++++ b/src/updateinfo.c +@@ -74,6 +74,46 @@ cr_updatecollectionpackage_free(cr_UpdateCollectionPackage *pkg) + + + /* ++ * cr_UpdateCollectionModule ++ */ ++ ++cr_UpdateCollectionModule * ++cr_updatecollectionmodule_new(void) ++{ ++ cr_UpdateCollectionModule *module = g_malloc0(sizeof(*module)); ++ module->chunk = g_string_chunk_new(0); ++ return module; ++} ++ ++cr_UpdateCollectionModule * ++cr_updatecollectionmodule_copy(const cr_UpdateCollectionModule *orig) ++{ ++ cr_UpdateCollectionModule *module; ++ ++ if (!orig) return NULL; ++ ++ module = cr_updatecollectionmodule_new(); ++ ++ module->name = cr_safe_string_chunk_insert(module->chunk, orig->name); ++ module->stream = cr_safe_string_chunk_insert(module->chunk, orig->stream); ++ module->version = orig->version; ++ module->context = cr_safe_string_chunk_insert(module->chunk, orig->context); ++ module->arch = cr_safe_string_chunk_insert(module->chunk, orig->arch); ++ ++ return module; ++} ++ ++void ++cr_updatecollectionmodule_free(cr_UpdateCollectionModule *module) ++{ ++ if (!module) ++ return; ++ g_string_chunk_free(module->chunk); ++ g_free(module); ++} ++ ++ ++/* + * cr_UpdateCollection + */ + +@@ -97,6 +137,10 @@ cr_updatecollection_copy(const cr_UpdateCollection *orig) + col->shortname = cr_safe_string_chunk_insert(col->chunk, orig->shortname); + col->name = cr_safe_string_chunk_insert(col->chunk, orig->name); + ++ if (orig->module) { ++ col->module = cr_updatecollectionmodule_copy(orig->module); ++ } ++ + if (orig->packages) { + GSList *newlist = NULL; + for (GSList *elem = orig->packages; elem; elem = g_slist_next(elem)) { +diff --git a/src/updateinfo.h b/src/updateinfo.h +index dbf7807..38883e0 100644 +--- a/src/updateinfo.h ++++ b/src/updateinfo.h +@@ -51,8 +51,19 @@ typedef struct { + } cr_UpdateCollectionPackage; + + typedef struct { ++ gchar *name; ++ gchar *stream; ++ guint64 version; ++ gchar *context; ++ gchar *arch; ++ ++ GStringChunk *chunk; ++} cr_UpdateCollectionModule; ++ ++typedef struct { + gchar *shortname; /*!< e.g. rhn-tools-rhel-x86_64-server-6.5.aus */ + gchar *name; /*!< e.g. RHN Tools for RHEL AUS (v. 6.5 for 64-bit x86_64) */ ++ cr_UpdateCollectionModule *module; + GSList *packages; /*!< List of cr_UpdateCollectionPackage */ + GStringChunk *chunk; + } cr_UpdateCollection; +@@ -106,6 +117,19 @@ void + cr_updatecollectionpackage_free(cr_UpdateCollectionPackage *pkg); + + /* ++ * cr_UpdateCollectionModule ++ */ ++ ++cr_UpdateCollectionModule * ++cr_updatecollectionmodule_new(void); ++ ++cr_UpdateCollectionModule * ++cr_updatecollectionmodule_copy(const cr_UpdateCollectionModule *orig); ++ ++void ++cr_updatecollectionmodule_free(cr_UpdateCollectionModule *pkg); ++ ++/* + * cr_UpdateCollection + */ + +diff --git a/src/xml_dump_updateinfo.c b/src/xml_dump_updateinfo.c +index 4fb5720..fafe686 100644 +--- a/src/xml_dump_updateinfo.c ++++ b/src/xml_dump_updateinfo.c +@@ -66,6 +66,24 @@ cr_xml_dump_updatecollectionpackages(xmlNodePtr collection, GSList *packages) + } + + void ++cr_xml_dump_updatecollectionmodule(xmlNodePtr collection, cr_UpdateCollectionModule *module) ++{ ++ if (!module) ++ return; ++ ++ xmlNodePtr xml_module; ++ xml_module = xmlNewChild(collection, NULL, BAD_CAST "module", NULL); ++ ++ cr_xmlNewProp_c(xml_module, BAD_CAST "name", BAD_CAST module->name); ++ cr_xmlNewProp_c(xml_module, BAD_CAST "stream", BAD_CAST module->stream); ++ gchar buf[21]; //20 + '\0' is max number of chars of guint64: G_MAXUINT64 (= 18,446,744,073,709,551,615) ++ snprintf(buf, 21, "%" G_GUINT64_FORMAT, module->version); ++ cr_xmlNewProp_c(xml_module, BAD_CAST "version", BAD_CAST buf); ++ cr_xmlNewProp_c(xml_module, BAD_CAST "context", BAD_CAST module->context); ++ cr_xmlNewProp_c(xml_module, BAD_CAST "arch", BAD_CAST module->arch); ++} ++ ++void + cr_xml_dump_updateinforecord_pkglist(xmlNodePtr update, GSList *collections) + { + xmlNodePtr pkglist; +@@ -83,6 +101,8 @@ cr_xml_dump_updateinforecord_pkglist(xmlNodePtr update, GSList *collections) + BAD_CAST "name", + BAD_CAST col->name); + ++ cr_xml_dump_updatecollectionmodule(collection, col->module); ++ + cr_xml_dump_updatecollectionpackages(collection, col->packages); + } + } +diff --git a/src/xml_parser_internal.h b/src/xml_parser_internal.h +index 6b400eb..e079ece 100644 +--- a/src/xml_parser_internal.h ++++ b/src/xml_parser_internal.h +@@ -151,6 +151,8 @@ typedef struct _cr_ParserData { + Update record object */ + cr_UpdateCollection *updatecollection; /*!< + Update collection object */ ++ cr_UpdateCollectionModule *updatecollectionmodule; /*!< ++ Update collection module object */ + cr_UpdateCollectionPackage *updatecollectionpackage; /*!< + Update collection package object */ + +diff --git a/src/xml_parser_updateinfo.c b/src/xml_parser_updateinfo.c +index 18e5277..c6c6503 100644 +--- a/src/xml_parser_updateinfo.c ++++ b/src/xml_parser_updateinfo.c +@@ -54,6 +54,7 @@ typedef enum { + STATE_PKGLIST, // ---------------------------- + STATE_COLLECTION, + STATE_NAME, ++ STATE_MODULE, + STATE_PACKAGE, + STATE_FILENAME, + STATE_SUM, +@@ -89,6 +90,7 @@ static cr_StatesSwitch stateswitches[] = { + { STATE_PKGLIST, "collection", STATE_COLLECTION, 0 }, + { STATE_COLLECTION, "package", STATE_PACKAGE, 0 }, + { STATE_COLLECTION, "name", STATE_NAME, 1 }, ++ { STATE_COLLECTION, "module", STATE_MODULE, 0 }, + { STATE_PACKAGE, "filename", STATE_FILENAME, 1 }, + { STATE_PACKAGE, "sum", STATE_SUM, 1 }, + { STATE_PACKAGE, "reboot_suggested", STATE_REBOOTSUGGESTED, 0 }, +@@ -141,6 +143,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + // Shortcuts + cr_UpdateRecord *rec = pd->updaterecord; + cr_UpdateCollection *collection = pd->updatecollection; ++ cr_UpdateCollectionModule *module = pd->updatecollectionmodule; + cr_UpdateCollectionPackage *package = pd->updatecollectionpackage; + + switch(pd->state) { +@@ -173,6 +176,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + assert(pd->updateinfo); + assert(!pd->updaterecord); + assert(!pd->updatecollection); ++ assert(!pd->updatecollectionmodule); + assert(!pd->updatecollectionpackage); + + rec = cr_updaterecord_new(); +@@ -201,6 +205,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + assert(pd->updateinfo); + assert(pd->updaterecord); + assert(!pd->updatecollection); ++ assert(!pd->updatecollectionmodule); + assert(!pd->updatecollectionpackage); + val = cr_find_attr("date", attr); + if (val) +@@ -211,6 +216,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + assert(pd->updateinfo); + assert(pd->updaterecord); + assert(!pd->updatecollection); ++ assert(!pd->updatecollectionmodule); + assert(!pd->updatecollectionpackage); + val = cr_find_attr("date", attr); + if (val) +@@ -223,6 +229,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + assert(pd->updateinfo); + assert(pd->updaterecord); + assert(!pd->updatecollection); ++ assert(!pd->updatecollectionmodule); + assert(!pd->updatecollectionpackage); + + ref = cr_updatereference_new(); +@@ -251,6 +258,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + assert(pd->updateinfo); + assert(pd->updaterecord); + assert(!pd->updatecollection); ++ assert(!pd->updatecollectionmodule); + assert(!pd->updatecollectionpackage); + + collection = cr_updatecollection_new(); +@@ -263,6 +271,49 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + + break; + ++ case STATE_MODULE: ++ assert(pd->updateinfo); ++ assert(pd->updaterecord); ++ assert(pd->updatecollection); ++ assert(!pd->updatecollectionmodule); ++ assert(!pd->updatecollectionpackage); ++ ++ module = cr_updatecollectionmodule_new(); ++ if (module) ++ collection->module = module; ++ ++ val = cr_find_attr("name", attr); ++ if (val) ++ module->name = g_string_chunk_insert(module->chunk, val); ++ ++ val = cr_find_attr("stream", attr); ++ if (val) ++ module->stream = g_string_chunk_insert(module->chunk, val); ++ ++ val = cr_find_attr("version", attr); ++ if (val){ ++ gchar *endptr; ++ errno = 0; ++ module->version = strtoull(val, &endptr, 10); ++ if ((errno == ERANGE && (module->version == ULLONG_MAX)) ++ || (errno != 0 && module->version == 0)) { ++ perror("strtoull error when parsing module version"); ++ module->version = 0; ++ } ++ if (endptr == val) ++ module->version = 0; ++ } ++ ++ val = cr_find_attr("context", attr); ++ if (val) ++ module->context = g_string_chunk_insert(module->chunk, val); ++ ++ val = cr_find_attr("arch", attr); ++ if (val) ++ module->arch = g_string_chunk_insert(module->chunk, val); ++ ++ break; ++ + case STATE_PACKAGE: + assert(pd->updateinfo); + assert(pd->updaterecord); +@@ -303,6 +354,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + assert(pd->updateinfo); + assert(pd->updaterecord); + assert(pd->updatecollection); ++ assert(pd->updatecollectionmodule); + assert(pd->updatecollectionpackage); + val = cr_find_attr("type", attr); + if (val) +@@ -313,6 +365,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr) + assert(pd->updateinfo); + assert(pd->updaterecord); + assert(pd->updatecollection); ++ assert(pd->updatecollectionmodule); + assert(pd->updatecollectionpackage); + package->reboot_suggested = TRUE; + break; +@@ -352,6 +405,7 @@ cr_end_handler(void *pdata, G_GNUC_UNUSED const char *element) + case STATE_UPDATED: + case STATE_REFERENCES: + case STATE_REFERENCE: ++ case STATE_MODULE: + case STATE_PKGLIST: + case STATE_REBOOTSUGGESTED: + // All elements with no text data and without need of any +diff --git a/tests/fixtures.h b/tests/fixtures.h +index ee374f5..8567714 100644 +--- a/tests/fixtures.h ++++ b/tests/fixtures.h +@@ -83,5 +83,6 @@ + #define TEST_UPDATEINFO_00 TEST_UPDATEINFO_FILES_PATH"updateinfo_00.xml" + #define TEST_UPDATEINFO_01 TEST_UPDATEINFO_FILES_PATH"updateinfo_01.xml" + #define TEST_UPDATEINFO_02 TEST_UPDATEINFO_FILES_PATH"updateinfo_02.xml.xz" ++#define TEST_UPDATEINFO_03 TEST_UPDATEINFO_FILES_PATH"updateinfo_03.xml" + + #endif +diff --git a/tests/python/tests/test_updatecollection.py b/tests/python/tests/test_updatecollection.py +index f3433c0..71ac7dd 100644 +--- a/tests/python/tests/test_updatecollection.py ++++ b/tests/python/tests/test_updatecollection.py +@@ -16,6 +16,13 @@ class TestCaseUpdateCollection(unittest.TestCase): + self.assertEqual(col.name, None) + self.assertEqual(col.packages, []) + ++ module = cr.UpdateCollectionModule() ++ module.name = "kangaroo" ++ module.stream = "0" ++ module.version = 20180730223407 ++ module.context = "deadbeef" ++ module.arch = "noarch" ++ + pkg = cr.UpdateCollectionPackage() + pkg.name = "foo" + pkg.version = "1.2" +@@ -30,12 +37,21 @@ class TestCaseUpdateCollection(unittest.TestCase): + + col.shortname = "short name" + col.name = "long name" ++ col.module = module + col.append(pkg) + + self.assertEqual(col.shortname, "short name") + self.assertEqual(col.name, "long name") + self.assertEqual(len(col.packages), 1) + ++ # Check if the appended module was appended properly ++ module = col.module ++ self.assertEqual(module.name, "kangaroo") ++ self.assertEqual(module.stream, "0") ++ self.assertEqual(module.version, 20180730223407) ++ self.assertEqual(module.context, "deadbeef") ++ self.assertEqual(module.arch, "noarch") ++ + # Also check if the appended package was appended properly + pkg = col.packages[0] + self.assertEqual(pkg.name, "foo") +diff --git a/tests/python/tests/test_updatecollectionmodule.py b/tests/python/tests/test_updatecollectionmodule.py +new file mode 100644 +index 0000000..1e92b12 +--- /dev/null ++++ b/tests/python/tests/test_updatecollectionmodule.py +@@ -0,0 +1,31 @@ ++import unittest ++import shutil ++import tempfile ++import os.path ++import createrepo_c as cr ++ ++from .fixtures import * ++ ++class TestCaseUpdateCollectionModule(unittest.TestCase): ++ ++ def test_updatecollectionmodule_setters(self): ++ module = cr.UpdateCollectionModule() ++ self.assertTrue(module) ++ ++ self.assertEqual(module.name, None) ++ self.assertEqual(module.stream, None) ++ self.assertEqual(module.version, 0) ++ self.assertEqual(module.context, None) ++ self.assertEqual(module.arch, None) ++ ++ module.name = "foo" ++ module.stream = "0" ++ module.version = 20180730223407 ++ module.context = "deadbeef" ++ module.arch = "noarch" ++ ++ self.assertEqual(module.name, "foo") ++ self.assertEqual(module.stream, "0") ++ self.assertEqual(module.version, 20180730223407) ++ self.assertEqual(module.context, "deadbeef") ++ self.assertEqual(module.arch, "noarch") +diff --git a/tests/python/tests/test_updateinfo.py b/tests/python/tests/test_updateinfo.py +index f3b88e1..727b707 100644 +--- a/tests/python/tests/test_updateinfo.py ++++ b/tests/python/tests/test_updateinfo.py +@@ -123,6 +123,100 @@ class TestCaseUpdateInfo(unittest.TestCase): + now = datetime(now.year, now.month, now.day, now.hour, now.minute, + now.second, 0) + ++ mod = cr.UpdateCollectionModule() ++ mod.name = "kangaroo" ++ mod.stream = "0" ++ mod.version = 18446744073709551615 ++ mod.context = "deadbeef" ++ mod.arch = "x86" ++ ++ pkg = cr.UpdateCollectionPackage() ++ pkg.name = "foo" ++ pkg.version = "1.2" ++ pkg.release = "3" ++ pkg.epoch = "0" ++ pkg.arch = "x86" ++ pkg.src = "foo.src.rpm" ++ pkg.filename = "foo.rpm" ++ pkg.sum = "abcdef" ++ pkg.sum_type = cr.SHA1 ++ pkg.reboot_suggested = True ++ ++ col = cr.UpdateCollection() ++ col.shortname = "short name" ++ col.name = "long name" ++ col.module = mod ++ col.append(pkg) ++ ++ ref = cr.UpdateReference() ++ ref.href = "href" ++ ref.id = "id" ++ ref.type = "type" ++ ref.title = "title" ++ ++ rec = cr.UpdateRecord() ++ rec.fromstr = "from" ++ rec.status = "status" ++ rec.type = "type" ++ rec.version = "version" ++ rec.id = "id" ++ rec.title = "title" ++ rec.issued_date = now ++ rec.updated_date = now ++ rec.rights = "rights" ++ rec.release = "release" ++ rec.pushcount = "pushcount" ++ rec.severity = "severity" ++ rec.summary = "summary" ++ rec.description = "description" ++ rec.solution = "solution" ++ rec.append_collection(col) ++ rec.append_reference(ref) ++ ++ ui = cr.UpdateInfo() ++ ui.append(rec) ++ ++ xml = ui.xml_dump() ++ ++ self.assertEqual(xml, ++""" ++ ++ ++ id ++ title ++ ++ ++ rights ++ release ++ pushcount ++ severity ++ summary ++ description ++ solution ++ ++ ++ ++ ++ ++ long name ++ ++ ++ foo.rpm ++ abcdef ++ ++ ++ ++ ++ ++ ++""" % {"now": now.strftime("%Y-%m-%d %H:%M:%S")}) ++ ++ def test_updateinfo_xml_dump_04(self): ++ now = datetime.now() ++ # Microseconds are always 0 in updateinfo ++ now = datetime(now.year, now.month, now.day, now.hour, now.minute, ++ now.second, 0) ++ + pkg = cr.UpdateCollectionPackage() + pkg.name = "foo" + pkg.version = "1.2" +@@ -135,6 +229,7 @@ class TestCaseUpdateInfo(unittest.TestCase): + pkg.sum_type = cr.SHA1 + pkg.reboot_suggested = True + ++ # Collection without module + col = cr.UpdateCollection() + col.shortname = "short name" + col.name = "long name" +@@ -167,6 +262,99 @@ class TestCaseUpdateInfo(unittest.TestCase): + + ui = cr.UpdateInfo() + ui.append(rec) ++ ++ xml = ui.xml_dump() ++ ++ self.assertEqual(xml, ++""" ++ ++ ++ id ++ title ++ ++ ++ rights ++ release ++ pushcount ++ severity ++ summary ++ description ++ solution ++ ++ ++ ++ ++ ++ long name ++ ++ foo.rpm ++ abcdef ++ ++ ++ ++ ++ ++ ++""" % {"now": now.strftime("%Y-%m-%d %H:%M:%S")}) ++ ++ def test_updateinfo_xml_dump_05(self): ++ now = datetime.now() ++ # Microseconds are always 0 in updateinfo ++ now = datetime(now.year, now.month, now.day, now.hour, now.minute, ++ now.second, 0) ++ ++ # Collection module with unset fields ++ mod = cr.UpdateCollectionModule() ++ mod.version = 18446744073709551615 ++ mod.context = "deadbeef" ++ mod.arch = "x86" ++ ++ pkg = cr.UpdateCollectionPackage() ++ pkg.name = "foo" ++ pkg.version = "1.2" ++ pkg.release = "3" ++ pkg.epoch = "0" ++ pkg.arch = "x86" ++ pkg.src = "foo.src.rpm" ++ pkg.filename = "foo.rpm" ++ pkg.sum = "abcdef" ++ pkg.sum_type = cr.SHA1 ++ pkg.reboot_suggested = True ++ ++ col = cr.UpdateCollection() ++ col.shortname = "short name" ++ col.name = "long name" ++ col.module = mod ++ col.append(pkg) ++ ++ ref = cr.UpdateReference() ++ ref.href = "href" ++ ref.id = "id" ++ ref.type = "type" ++ ref.title = "title" ++ ++ rec = cr.UpdateRecord() ++ rec.fromstr = "from" ++ rec.status = "status" ++ rec.type = "type" ++ rec.version = "version" ++ rec.id = "id" ++ rec.title = "title" ++ rec.issued_date = now ++ rec.updated_date = now ++ rec.rights = "rights" ++ rec.release = "release" ++ rec.pushcount = "pushcount" ++ rec.severity = "severity" ++ rec.summary = "summary" ++ rec.description = "description" ++ rec.solution = "solution" ++ rec.append_collection(col) ++ rec.append_reference(ref) ++ ++ ui = cr.UpdateInfo() ++ ui.append(rec) ++ + xml = ui.xml_dump() + + self.assertEqual(xml, +@@ -190,6 +378,7 @@ class TestCaseUpdateInfo(unittest.TestCase): + + + long name ++ + + foo.rpm + abcdef +diff --git a/tests/test_xml_parser_updateinfo.c b/tests/test_xml_parser_updateinfo.c +index 94768ce..3f0cfee 100644 +--- a/tests/test_xml_parser_updateinfo.c ++++ b/tests/test_xml_parser_updateinfo.c +@@ -168,6 +168,90 @@ test_cr_xml_parse_updateinfo_02(void) + cr_updateinfo_free(ui); + } + ++//Test for module support ++static void ++test_cr_xml_parse_updateinfo_03(void) ++{ ++ GError *tmp_err = NULL; ++ cr_UpdateInfo *ui = cr_updateinfo_new(); ++ cr_UpdateRecord *update; ++ cr_UpdateReference *ref; ++ cr_UpdateCollection *col; ++ cr_UpdateCollectionModule *module; ++ cr_UpdateCollectionPackage *pkg; ++ ++ int ret = cr_xml_parse_updateinfo(TEST_UPDATEINFO_03, ui, ++ NULL, NULL, &tmp_err); ++ ++ g_assert(tmp_err == NULL); ++ g_assert_cmpint(ret, ==, CRE_OK); ++ ++ g_assert_cmpint(g_slist_length(ui->updates), ==, 6); ++ update = g_slist_nth_data(ui->updates, 3); ++ ++ g_assert_cmpstr(update->from, ==, "errata@redhat.com"); ++ g_assert_cmpstr(update->status, ==, "stable"); ++ g_assert_cmpstr(update->type, ==, "enhancement"); ++ g_assert_cmpstr(update->version, ==, "1"); ++ g_assert_cmpstr(update->id, ==, "RHEA-2012:0058"); ++ g_assert_cmpstr(update->title, ==, "Gorilla_Erratum"); ++ g_assert_cmpstr(update->description, ==, "Gorilla_Erratum"); ++ ++ update = g_slist_nth_data(ui->updates, 4); ++ ++ g_assert_cmpstr(update->id, ==, "RHEA-2012:0059"); ++ g_assert_cmpstr(update->title, ==, "Duck_Kangaroo_Erratum"); ++ g_assert_cmpstr(update->description, ==, "Duck_Kangaro_Erratum description"); ++ g_assert_cmpstr(update->issued_date, ==, "2018-01-27 16:08:09"); ++ g_assert_cmpstr(update->updated_date, ==, "2018-07-20 06:00:01 UTC"); ++ g_assert_cmpstr(update->release, ==, "1"); ++ ++ g_assert_cmpint(g_slist_length(update->references), ==, 0); ++ ++ g_assert_cmpint(g_slist_length(update->collections), ==, 2); ++ col = g_slist_nth_data(update->collections, 0); ++ g_assert_cmpstr(col->shortname, ==, ""); ++ g_assert_cmpstr(col->name, ==, "coll_name1"); ++ ++ module = col->module; ++ g_assert_cmpstr(module->name, ==, "kangaroo"); ++ g_assert_cmpstr(module->stream, ==, "0"); ++ g_assert_cmpuint(module->version, ==, 20180730223407); ++ g_assert_cmpstr(module->context, ==, "deadbeef"); ++ g_assert_cmpstr(module->arch, ==, "noarch"); ++ ++ g_assert_cmpint(g_slist_length(col->packages), ==, 1); ++ pkg = col->packages->data; ++ g_assert_cmpstr(pkg->name, ==, "kangaroo"); ++ g_assert_cmpstr(pkg->version, ==, "0.3"); ++ g_assert_cmpstr(pkg->release, ==, "1"); ++ g_assert(!pkg->epoch); ++ g_assert_cmpstr(pkg->arch, ==, "noarch"); ++ g_assert_cmpstr(pkg->src, ==, "http://www.fedoraproject.org"); ++ g_assert_cmpstr(pkg->filename, ==, "kangaroo-0.3-1.noarch.rpm"); ++ g_assert(!pkg->sum); ++ g_assert(!pkg->sum_type); ++ ++ col = g_slist_nth_data(update->collections, 1); ++ g_assert_cmpstr(col->shortname, ==, ""); ++ g_assert_cmpstr(col->name, ==, "coll_name2"); ++ ++ module = col->module; ++ g_assert_cmpstr(module->name, ==, "duck"); ++ g_assert_cmpstr(module->stream, ==, "0"); ++ g_assert_cmpuint(module->version, ==, 20180730233102); ++ g_assert_cmpstr(module->context, ==, "deadbeef"); ++ g_assert_cmpstr(module->arch, ==, "noarch"); ++ ++ g_assert_cmpint(g_slist_length(col->packages), ==, 1); ++ pkg = col->packages->data; ++ g_assert_cmpstr(pkg->name, ==, "duck"); ++ g_assert_cmpstr(pkg->version, ==, "0.7"); ++ g_assert_cmpstr(pkg->filename, ==, "duck-0.7-1.noarch.rpm"); ++ ++ cr_updateinfo_free(ui); ++} ++ + int + main(int argc, char *argv[]) + { +@@ -179,6 +263,8 @@ main(int argc, char *argv[]) + test_cr_xml_parse_updateinfo_01); + g_test_add_func("/xml_parser_updateinfo/test_cr_xml_parse_updateinfo_02", + test_cr_xml_parse_updateinfo_02); ++ g_test_add_func("/xml_parser_updateinfo/test_cr_xml_parse_updateinfo_03", ++ test_cr_xml_parse_updateinfo_03); + + return g_test_run(); + } +diff --git a/tests/testdata/updateinfo_files/updateinfo_03.xml b/tests/testdata/updateinfo_files/updateinfo_03.xml +new file mode 100644 +index 0000000..ddbd99b +--- /dev/null ++++ b/tests/testdata/updateinfo_files/updateinfo_03.xml +@@ -0,0 +1,128 @@ ++ ++ ++ ++ RHEA-2012:0055 ++ Sea_Erratum ++ 1 ++ ++ ++ Sea_Erratum ++ ++ ++ 1 ++ ++ walrus-5.21-1.noarch.rpm ++ ++ ++ penguin-0.9.1-1.noarch.rpm ++ ++ ++ shark-0.1-1.noarch.rpm ++ ++ ++ ++ ++ ++ ++ RHEA-2012:0056 ++ Bird_Erratum ++ 1 ++ ++ ++ ParthaBird_Erratum ++ ++ ++ 1 ++ ++ crow-0.8-1.noarch.rpm ++ ++ ++ stork-0.12-2.noarch.rpm ++ ++ ++ duck-0.6-1.noarch.rpm ++ ++ ++ ++ ++ ++ ++ RHEA-2012:0057 ++ Bear_ErratumPARTHA ++ 1 ++ ++ ++ Bear_Erratum ++ ++ ++ 1 ++ ++ bear-4.1-1.noarch.rpm ++ ++ ++ ++ ++ ++ ++ RHEA-2012:0058 ++ Gorilla_Erratum ++ 1 ++ ++ ++ Gorilla_Erratum ++ ++ ++ 1 ++ ++ gorilla-0.62-1.noarch.rpm ++ ++ ++ ++ ++ ++ ++ RHEA-2012:0059 ++ Duck_Kangaroo_Erratum ++ 1 ++ ++ ++ Duck_Kangaro_Erratum description ++ ++ ++ coll_name1 ++ ++ ++ kangaroo-0.3-1.noarch.rpm ++ ++ ++ ++ coll_name2 ++ ++ ++ duck-0.7-1.noarch.rpm ++ ++ ++ ++ ++ ++ ++ RHEA-2012:0060 ++ Duck_0.8_Erratum ++ 1 ++ ++ ++ Duck_0.8_Erratum description ++ ++ ++ coll_name ++ ++ ++ duck-0.8-1.noarch.rpm ++ ++ ++ ++ ++ +-- +libgit2 0.27.8 + diff --git a/SOURCES/0005-Switch-off-html-timestamps-on-documentation-RhBug1731050.patch b/SOURCES/0005-Switch-off-html-timestamps-on-documentation-RhBug1731050.patch new file mode 100644 index 0000000..c7e5228 --- /dev/null +++ b/SOURCES/0005-Switch-off-html-timestamps-on-documentation-RhBug1731050.patch @@ -0,0 +1,32 @@ +From df4145ae17972b51a2f4e3ccb10c7ed0a6799209 Mon Sep 17 00:00:00 2001 +From: Marek Blaha +Date: Mon, 29 Jul 2019 12:09:07 +0200 +Subject: [PATCH] Switch off html timestamps on documentation (RhBug:1731050) + +This change avoids file conflicts when installing createrepo_c-devel +package for i686 and x86_64 architectures in parallel. +After switching the timestamps off the documentation html files are +identical for both builds and rpm does not consider them conflicting any +more. + +https://bugzilla.redhat.com/show_bug.cgi?id=1731050 +--- + doc/Doxyfile.in.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/doc/Doxyfile.in.in b/doc/Doxyfile.in.in +index bd6b5a7..35a5e75 100644 +--- a/doc/Doxyfile.in.in ++++ b/doc/Doxyfile.in.in +@@ -952,7 +952,7 @@ HTML_COLORSTYLE_GAMMA = 80 + # page will contain the date and time when the page was generated. Setting + # this to NO can help when comparing the output of multiple runs. + +-HTML_TIMESTAMP = YES ++HTML_TIMESTAMP = NO + + # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML + # documentation will contain sections that can be hidden and shown after the +-- +libgit2 0.28.2 + diff --git a/SPECS/createrepo_c.spec b/SPECS/createrepo_c.spec index c664178..ff08f69 100644 --- a/SPECS/createrepo_c.spec +++ b/SPECS/createrepo_c.spec @@ -7,8 +7,6 @@ %global bash_completion %{_datadir}/bash-completion/completions/* %endif -%{!?python2_sitearch:%global python2_sitearch %{python_sitearch}} - %if 0%{?rhel} && 0%{?rhel} <= 7 %bcond_with python3 %bcond_with drpm @@ -26,10 +24,15 @@ Summary: Creates a common metadata repository Name: createrepo_c Version: 0.11.0 -Release: 1%{?dist} +Release: 3%{?dist} License: GPLv2+ URL: https://github.com/rpm-software-management/createrepo_c Source0: %{url}/archive/%{version}/%{name}-%{version}.tar.gz +Patch0: 0001-Consistently-produce-valid-URLs-by-prepending-protocol-RhBug1632121.patch +Patch1: 0002-modifyrepo_c-Prevent-doubling-of-compression-testgzgz-RhBug1639287.patch +Patch2: 0003-Correct-pkg-count-in-headers-if-there-were-invalid-pkgs-RhBug1596211.patch +Patch3: 0004-Add-support-for-modular-errata-RhBug1656584.patch +Patch4: 0005-Switch-off-html-timestamps-on-documentation-RhBug1731050.patch BuildRequires: cmake BuildRequires: gcc @@ -223,6 +226,16 @@ ln -sr %{buildroot}%{_bindir}/modifyrepo_c %{buildroot}%{_bindir}/modifyrepo %endif %changelog +* Thu Aug 08 2019 Pavla Kratochvilova - 0.11.0-3 +- Backport patch to switch off timestamps on documentation in order to remove + file conflicts (RhBug:1738788) + +* Mon Jul 22 2019 Pavla Kratochvilova - 0.11.0-2 +- Consistently produce valid URLs by prepending protocol. (RhBug:1632121) +- modifyrepo_c: Prevent doubling of compression (test.gz.gz) (RhBug:1639287) +- Correct pkg count in headers if there were invalid pkgs (RhBug:1596211) +- Add support for modular errata (RhBug:1656584) + * Wed Jun 27 2018 Marek Blaha - 0.11.0-1 - Update to 0.11.0