+++ /dev/null
-diff --git a/pkgset-dep.c b/pkgset-dep.c
-index ddaf74e..f035cd9 100644
---- a/pkgset-dep.c
-+++ b/pkgset-dep.c
-@@ -293,6 +293,8 @@ tn_array *get_conflicted(int indent, struct pkgset *ps,
- const struct capreq_idx_ent *ent;
- const char *cnflname = capreq_name(cnfl);
-
-+ pkgset__index_caps(ps);
-+
- if ((ent = capreq_idx_lookup(&ps->cap_idx, cnflname, capreq_name_len(cnfl)))) {
- struct pkg **suspkgs = (struct pkg **)ent->pkgs;
- int nmatch = 0;
+++ /dev/null
-diff -ur poldek-0.44.0.orig/conf.c poldek-0.44.0/conf.c
---- poldek-0.44.0.orig/conf.c 2025-02-26 12:40:22.000000000 +0100
-+++ poldek-0.44.0/conf.c 2025-08-10 22:26:33.098247697 +0200
-@@ -1458,33 +1458,33 @@
- int poldek_conf_get_bool(const tn_hash *htconf, const char *name, int default_v)
- {
- const char *v;
-- int bool;
-+ int bit;
-
- if ((v = poldek_conf_get(htconf, name, NULL)) == NULL)
- return default_v;
-
-- if ((bool = poldek_util_parse_bool(v)) < 0) {
-+ if ((bit = poldek_util_parse_bool(v)) < 0) {
- logn(LOGERR, _("invalid value ('%s') of boolean option '%s'"), v, name);
-- bool = default_v;
-+ bit = default_v;
- }
-
-- return bool;
-+ return bit;
- }
-
- int poldek_conf_get_bool3(const tn_hash *htconf, const char *name, int default_v)
- {
- const char *v;
-- int bool;
-+ int bit;
-
- if ((v = poldek_conf_get(htconf, name, NULL)) == NULL)
- return default_v;
-
-- if ((bool = poldek_util_parse_bool3(v)) < 0) {
-+ if ((bit = poldek_util_parse_bool3(v)) < 0) {
- logn(LOGERR, _("invalid value ('%s') of option '%s'"), v, name);
-- bool = default_v;
-+ bit = default_v;
- }
-
-- return bool;
-+ return bit;
- }
-
-
+++ /dev/null
-From 9a91c58a282617a536a99d86be4eaa2da7e433ed Mon Sep 17 00:00:00 2001
-From: Jan Palus <jpalus@fastmail.com>
-Date: Thu, 26 Jun 2025 00:30:42 +0200
-Subject: [PATCH] list available translations in po/LINGUAS file
-
-fixes compatibility with gettext >= 0.24
----
- configure.ac | 2 --
- po/LINGUAS | 1 +
- 2 files changed, 1 insertion(+), 2 deletions(-)
- create mode 100644 po/LINGUAS
-
-diff --git a/configure.ac b/configure.ac
-index fab947b..9afc747 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -46,8 +46,6 @@ fi
- AC_SUBST(VERSION_CVSTAG)
- AC_DEFINE_UNQUOTED([VERSION_YEAR], "$VERSION_YEAR", [version year])
-
--ALL_LINGUAS="pl de"
--
- dnl cond. building NFY
- dnl AC_DEFINE([ENABLE_VFILE_TRURLIO],1,[defined if trurlio is used for vfile operations])
-
-diff --git a/po/LINGUAS b/po/LINGUAS
-new file mode 100644
-index 0000000..78b66c3
---- /dev/null
-+++ b/po/LINGUAS
-@@ -0,0 +1 @@
-+de pl
---
-2.50.0
-
+++ /dev/null
-From cfb03a18a51ed409a143ef56624fc67b12b58073 Mon Sep 17 00:00:00 2001
-From: Jan Palus <jpalus@fastmail.com>
-Date: Wed, 25 Jun 2025 22:11:12 +0200
-Subject: [PATCH] pkgiter: don't skip reqs which are both pre and preun
-
----
- pkgiter.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/pkgiter.c b/pkgiter.c
-index bc085a4..4e0b810 100644
---- a/pkgiter.c
-+++ b/pkgiter.c
-@@ -147,7 +147,7 @@ const struct capreq *pkg_req_iter_get(struct pkg_req_iter *it)
- if ((it->flags & PKG_ITER_REQUN) && !capreq_is_prereq_un(req))
- return pkg_req_iter_get(it);
-
-- else if ((it->flags & PKG_ITER_REQUN) == 0 && capreq_is_prereq_un(req))
-+ else if ((it->flags & PKG_ITER_REQUN) == 0 && !capreq_is_prereq(req) && capreq_is_prereq_un(req))
- return pkg_req_iter_get(it);
-
- /* set type of returned (current) req */
---
-2.50.0
-
--- /dev/null
+commit bd97788fa990b4b2cd957c540399cec35afab526
+Author: Arkadiusz Miśkiewicz <arekm@maven.pl>
+Date: Tue Apr 14 16:26:11 2026 +0200
+
+ Show config file origin when removing duplicated sources
+
+ Print which config file:line each duplicated source was defined at,
+ making it easy to find and fix the duplication.
+
+ Also fix conf.c filemark formatting: remove trailing colon from
+ stored __file__line value and format strings to avoid double-colon
+ artifacts in error messages.
+
+diff --git a/conf.c b/conf.c
+index 83e625f6..f9f775b0 100644
+--- a/conf.c
++++ b/conf.c
+@@ -505,7 +505,7 @@ static int verify_param_presence(tn_hash *ht_sect, const char *section,
+
+ if (overwrite || (tag->flags & CONF_TYPE_F_MULTI_EXCL)) {
+ if (!overwrite || poldek_VERBOSE > 1)
+- logn(LOGWARN, _("%s %s::%s redefined"), filemark, section, name);
++ logn(LOGWARN, _("%s: %s::%s redefined"), filemark, section, name);
+ n_hash_remove(ht_sect, name);
+
+ } else if ((tag->flags & CONF_TYPE_F_MULTI) == 0) {
+@@ -541,9 +541,9 @@ static int add_param(tn_hash *ht_sect, const char *section,
+ validate = (flags & ADD_PARAM_VALIDATE);
+
+ if (path)
+- n_snprintf(filemark, sizeof(filemark), "%s:%d:", path, nline);
++ n_snprintf(filemark, sizeof(filemark), "%s:%d", path, nline);
+ else
+- n_snprintf(filemark, sizeof(filemark), "config:");
++ n_snprintf(filemark, sizeof(filemark), "config");
+
+ if ((tagindex = find_tag(section, name, §)) == -1) {
+ if (*name == '_') /* internal or _macro */
+@@ -554,7 +554,7 @@ static int add_param(tn_hash *ht_sect, const char *section,
+ tag = &unknown_tag;
+
+ } else {
+- logn(LOGWARN, _("%s unknown option '%s::%s'"), filemark,
++ logn(LOGWARN, _("%s: unknown option '%s::%s'"), filemark,
+ section, name);
+ return 0;
+ }
+@@ -604,7 +604,7 @@ static int add_param(tn_hash *ht_sect, const char *section,
+ val = "";
+
+ if (val == NULL) {
+- logn(LOGERR, _("%s invalid value of '%s::%s'"), filemark, section, name);
++ logn(LOGERR, _("%s: invalid value of '%s::%s'"), filemark, section, name);
+ return 0;
+ }
+
+@@ -618,7 +618,7 @@ static int add_param(tn_hash *ht_sect, const char *section,
+ }
+
+ if (!valid) {
+- logn(LOGWARN, _("%s invalid value '%s' of '%s::%s'"), filemark,
++ logn(LOGWARN, _("%s: invalid value '%s' of '%s::%s'"), filemark,
+ val, section, name);
+ return 0;
+ }
+@@ -826,7 +826,7 @@ static tn_hash *open_section_ht(tn_hash *htconf,
+ char filemark[PATH_MAX];
+ struct copt *opt;
+
+- n_snprintf(filemark, sizeof(filemark), "%s:%d:", path, nline);
++ n_snprintf(filemark, sizeof(filemark), "%s:%d", path, nline);
+ opt = copt_new("__file__line");
+ opt->val = n_strdup(filemark);
+ n_hash_insert(ht_sect, opt->name, opt);
+diff --git a/pkgdir/source.c b/pkgdir/source.c
+index 28c0049f..8d23592d 100644
+--- a/pkgdir/source.c
++++ b/pkgdir/source.c
+@@ -176,6 +176,7 @@ struct source *source_malloc(void)
+ //src->flags |= PKGSOURCE_PRI;
+ src->name = src->path = src->pkg_prefix = NULL;
+ src->group = src->dscr = NULL;
++ src->config_origin = NULL;
+ src->lc_lang = NULL;
+ src->_refcnt = 0;
+ src->exclude_path = n_array_new(4, free, (tn_fn_cmp)strcmp);
+@@ -213,6 +214,7 @@ struct source *source_clone(const struct source *src)
+ cp_str_ifnotnull(&nsrc->group, src->group);
+ cp_str_ifnotnull(&nsrc->lc_lang, src->lc_lang);
+ cp_str_ifnotnull(&nsrc->original_type, src->original_type);
++ cp_str_ifnotnull(&nsrc->config_origin, src->config_origin);
+
+ n_array_free(nsrc->exclude_path);
+ nsrc->exclude_path = n_ref(src->exclude_path);
+@@ -241,6 +243,7 @@ void source_free(struct source *src)
+ n_cfree(&src->group);
+ n_cfree(&src->lc_lang);
+ n_cfree(&src->original_type);
++ n_cfree(&src->config_origin);
+
+ if (src->exclude_path)
+ n_array_free(src->exclude_path);
+@@ -662,6 +665,10 @@ struct source *source_new_htcnf(const tn_hash *htcnf)
+ if (vs)
+ src->original_type = n_strdup(vs);
+
++ vs = poldek_conf_get(htcnf, "__file__line", NULL);
++ if (vs)
++ src->config_origin = n_strdup(vs);
++
+ get_conf_opt_list(htcnf, "exclude path", src->exclude_path);
+ get_conf_opt_list(htcnf, "ignore", src->ign_patterns);
+ return src;
+@@ -687,11 +694,16 @@ int source_cmp_uniq(const struct source *s1, const struct source *s2)
+ rc = strcmp(n1, n2);
+ }
+
+- if (rc == 0)
++ if (rc == 0) {
+ logn(LOGWARN, _("removed duplicated source %s%s%s"),
+ (s2->flags & PKGSOURCE_NAMED) ? s2->name : "",
+ (s2->flags & PKGSOURCE_NAMED) ? " -- " : "",
+ s2->path);
++ if (s1->config_origin || s2->config_origin)
++ logn(LOGWARN, _(" defined at %s and %s"),
++ s1->config_origin ? s1->config_origin : "(unknown)",
++ s2->config_origin ? s2->config_origin : "(unknown)");
++ }
+
+ return rc;
+ }
+diff --git a/pkgdir/source.h b/pkgdir/source.h
+index 6052ec25..b93639b5 100644
+--- a/pkgdir/source.h
++++ b/pkgdir/source.h
+@@ -55,6 +55,7 @@ struct source {
+ tn_array *exclude_path;
+ tn_array *ign_patterns; /* ignore package patterns */
+ char *original_type; /* type of source repo for this source */
++ char *config_origin; /* config file:line where source was defined */
+ unsigned subopt_flags;
+ int _refcnt;
+ char *group;
--- /dev/null
+commit bcf31cc1653155c93ad4b2b1a17d2301ac70d36b
+Author: Arkadiusz Miśkiewicz <arekm@maven.pl>
+Date: Tue Apr 14 17:45:51 2026 +0200
+
+ Use $COLUMNS as fallback when ioctl fails to get terminal width
+
+ When stdout is not a tty (e.g. piped output), ioctl TIOCGWINSZ fails
+ and terminal width falls back to 80 columns, causing unnecessary URL
+ truncation. Check $COLUMNS environment variable before falling back
+ to the default.
+
+diff --git a/poldek_term.c b/poldek_term.c
+index 9d4f9a87..6fbf7d82 100644
+--- a/poldek_term.c
++++ b/poldek_term.c
+@@ -245,15 +245,34 @@ static void update_term_width(void)
+
+ if (winch_reached) {
+ char tmp[256];
++ term_width = TERM_DEFAULT_WIDTH;
++ term_height = TERM_DEFAULT_HEIGHT;
++
+ if (ioctl(1, TIOCGWINSZ, &ws) == 0) {
+ term_width = ws.ws_col;
+ term_height = ws.ws_row;
+
+ } else {
+- term_width = TERM_DEFAULT_WIDTH;
+- term_height = TERM_DEFAULT_HEIGHT;
++ const char *cols = getenv("COLUMNS");
++ if (cols) {
++ int n = atoi(cols);
++ if (n > 0)
++ term_width = n;
++ }
++
++ const char *rows = getenv("LINES");
++ if (rows) {
++ int n = atoi(rows);
++ if (n > 0)
++ term_height = n;
++ }
+ }
+
++ if (term_width < TERM_MIN_WIDTH)
++ term_width = TERM_MIN_WIDTH;
++ if (term_height < TERM_MIN_HEIGHT)
++ term_height = TERM_MIN_HEIGHT;
++
+ //https://www.gnu.org/software/libc/manual/html_node/Argp-User-Customization.html
+ snprintf(tmp, sizeof(tmp), "no-dup-args-note,rmargin=%d", term_width - 1);
+ setenv("ARGP_HELP_FMT", tmp, 1);
+diff --git a/poldek_term.h b/poldek_term.h
+index 80e6d5a6..2b1db740 100644
+--- a/poldek_term.h
++++ b/poldek_term.h
+@@ -25,6 +25,8 @@
+
+ #define TERM_DEFAULT_WIDTH 80
+ #define TERM_DEFAULT_HEIGHT 24
++#define TERM_MIN_WIDTH 40
++#define TERM_MIN_HEIGHT 4
+
+ #include <stddef.h> /* for size_t */
+
--- /dev/null
+commit aea999f2f1cb86579bc8fef9f5393120d12f863b
+Author: Arkadiusz Miśkiewicz <arekm@maven.pl>
+Date: Wed Apr 22 09:50:32 2026 +0200
+
+ fix: apply [global] + per-source ignore across all pkg paths
+
+ [global] ignore = ... was silently replaced by any per-source
+ ignore = ... entry rather than extended, so global patterns were
+ lost on every source with its own ignore line. And the stub-index
+ load path (poldek --cmd "ls", search, etc.) bypassed ignore
+ filtering entirely for PKGDIR_CAP_HANDLEIGNORE modules -- that cap
+ only covers the full do_load path, not the separate stub .zst
+ cache loaded by load_stubindex.
+
+ - lib_init.c: always concat [global] ign_patterns into src's.
+ - lib_pkgset.c: in poldek_load_stubs, post-filter per-source stubs
+ via src->ign_patterns, symmetric with the full-load path and
+ gated by POLDEK_OP_IGNORE.
+ - pkgdir/pkgdir_stubindex.c: source_stubload is now a pure loader;
+ the HANDLEIGNORE guard there was dropping filtering entirely for
+ pndir sources in the stub path.
+
+diff --git a/doc/poldek.conf.xml b/doc/poldek.conf.xml
+index 86cb545b..828bb683 100644
+--- a/doc/poldek.conf.xml
++++ b/doc/poldek.conf.xml
+@@ -395,7 +395,10 @@ and [filename]*-source.conf[/filename], file getters are declared in
+
+ <option name="ignore" type="string" list="yes" default="vserver-packages" multiple="yes">
+ <description>
+- Ignore package list - packages fits given mask will be invisible.
++ Ignore package list - packages matching given masks will be invisible.
++ Patterns listed here apply to every [option]source[/option]; any
++ per-source [option]ignore[/option] entry adds more masks on top of this
++ global list rather than replacing it.
+ [screen]
+ ignore = *-smp-* foo*
+ ignore = vserver-packages
+@@ -634,7 +637,9 @@ Every repository is configured in its own [ source ] section.
+
+ <option name="ignore" type="string" list="yes" default="" multiple="yes">
+ <description>
+- Have the same meaning as [ global ] parameter. Example:
++ Additional ignore masks for this source, merged with the [ global ]
++ [option]ignore[/option] list (global patterns always apply; per-source
++ entries add more). Example:
+ [screen]
+ ignore = kernel*smp* dev
+ [/screen]
+diff --git a/lib_init.c b/lib_init.c
+index 297ec37e..88684654 100644
+--- a/lib_init.c
++++ b/lib_init.c
+@@ -245,12 +245,11 @@ struct source *do_source_new_htcnf(struct poldek_ctx *ctx,
+ (tn_fn_dup)strdup);
+ }
+
+- if (n_array_size(src->ign_patterns) == 0 && /* take global */
+- n_array_size(ctx->ts->ign_patterns) > 0) {
+-
+- n_array_free(src->ign_patterns);
+- src->ign_patterns = n_array_dup(ctx->ts->ign_patterns,
+- (tn_fn_dup)strdup);
++ if (n_array_size(ctx->ts->ign_patterns) > 0) {
++ n_array_concat_ex(src->ign_patterns, ctx->ts->ign_patterns,
++ (tn_fn_dup)strdup);
++ n_array_sort(src->ign_patterns);
++ n_array_uniq(src->ign_patterns);
+ }
+
+ return src;
+diff --git a/lib_pkgset.c b/lib_pkgset.c
+index 1b19752e..0b676fc9 100644
+--- a/lib_pkgset.c
++++ b/lib_pkgset.c
+@@ -129,6 +129,7 @@ tn_array *poldek_load_stubs(struct poldek_ctx *ctx)
+
+ n_array_isort_ex(sources, (tn_fn_cmp)source_cmp_pri);
+ tn_array *stubpkgs = pkgs_array_new(4096);
++ int do_ignore = ctx->ts->getop(ctx->ts, POLDEK_OP_IGNORE);
+
+ for (i=0; i < n_array_size(sources); i++) {
+ struct source *src = n_array_nth(sources, i);
+@@ -145,6 +146,9 @@ tn_array *poldek_load_stubs(struct poldek_ctx *ctx)
+ return 0;
+ }
+
++ if (do_ignore && n_array_size(src->ign_patterns) > 0)
++ packages_score_ignore(pkgs, src->ign_patterns, 1);
++
+ while (n_array_size(pkgs) > 0) {
+ struct pkg *pkg = n_array_shift(pkgs);
+
+diff --git a/pkgdir/pkgdir_stubindex.c b/pkgdir/pkgdir_stubindex.c
+index 7707378b..a7532896 100644
+--- a/pkgdir/pkgdir_stubindex.c
++++ b/pkgdir/pkgdir_stubindex.c
+@@ -204,13 +204,6 @@ tn_array *source_stubload(struct source *src)
+ if (pkgs == NULL)
+ return NULL;
+
+- if (src->ign_patterns) {
+- const struct pkgdir_module *mod = pkgdir_mod_find(src->type);
+- /* module does not handle "ignore" itself */
+- if (mod && (mod->cap_flags & PKGDIR_CAP_HANDLEIGNORE) == 0)
+- packages_score_ignore(pkgs, src->ign_patterns, 1);
+- }
+-
+ return pkgs;
+ }
+
+diff --git a/tests/Makefile.am b/tests/Makefile.am
+index 04654c67..ff08ccb9 100644
+--- a/tests/Makefile.am
++++ b/tests/Makefile.am
+@@ -2,7 +2,7 @@ CPPFLAGS = @CPPFLAGS@ @CHECK_CFLAGS@
+ LDADD = $(top_builddir)/libpoldek.la @CHECK_LIBS@
+
+ check_PROGRAMS = test_match test_env test_pmdb test_op test_config \
+- test_store test_cmp test_booldeps
++ test_store test_cmp test_booldeps test_ignore_merge
+
+ TESTS = $(check_PROGRAMS)
+
+diff --git a/tests/test_ignore_merge.c b/tests/test_ignore_merge.c
+new file mode 100644
+index 00000000..97e061ed
+--- /dev/null
++++ b/tests/test_ignore_merge.c
+@@ -0,0 +1,63 @@
++#include "test.h"
++#include "poldek.h"
++#include "pkgdir/source.h"
++
++static int list_contains(tn_array *list, const char *s)
++{
++ int i;
++ for (i = 0; i < n_array_size(list); i++)
++ if (n_str_eq((const char *)n_array_nth(list, i), s))
++ return 1;
++ return 0;
++}
++
++START_TEST (test_ignore_merge) {
++ const char *path = "poldek_test_ignore_merge.conf";
++ FILE *f = fopen(path, "w");
++ fail_if(f == NULL, "cannot open %s for write", path);
++ fprintf(f,
++ "[global]\n"
++ "ignore = foo* baz*\n"
++ "\n"
++ "[source]\n"
++ "name = testsrc\n"
++ "type = pndir\n"
++ "url = file:///tmp/poldek-test-nonexistent\n"
++ "ignore = bar*\n");
++ fclose(f);
++
++ poldeklib_init();
++ struct poldek_ctx *ctx = poldek_new(0);
++ fail_if(ctx == NULL, "poldek_new failed");
++ fail_if(poldek_load_config(ctx, path, NULL, 0) == 0,
++ "load_config failed");
++ fail_if(poldek_setup(ctx) == 0, "poldek_setup failed");
++
++ tn_array *sources = poldek_get_sources(ctx);
++ fail_if(sources == NULL, "no sources?");
++
++ struct source *src = NULL;
++ int i;
++ for (i = 0; i < n_array_size(sources); i++) {
++ struct source *s = n_array_nth(sources, i);
++ if (s->name && n_str_eq(s->name, "testsrc")) {
++ src = s;
++ break;
++ }
++ }
++ fail_if(src == NULL, "testsrc not found in sources");
++
++ fail_unless(list_contains(src->ign_patterns, "foo*"),
++ "global 'foo*' missing from merged ign_patterns");
++ fail_unless(list_contains(src->ign_patterns, "baz*"),
++ "global 'baz*' missing from merged ign_patterns");
++ fail_unless(list_contains(src->ign_patterns, "bar*"),
++ "per-source 'bar*' missing from merged ign_patterns");
++
++ n_array_free(sources);
++ poldek_free(ctx);
++ unlink(path);
++}
++END_TEST
++
++NTEST_RUNNER("ignore_merge", test_ignore_merge);
--- /dev/null
+From 3d5addaec1f408f389f66e3ded712315f734158b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Arkadiusz=20Mi=C5=9Bkiewicz?= <arekm@maven.pl>
+Date: Tue, 14 Apr 2026 13:59:26 +0200
+Subject: [PATCH 1/2] fix: in multilib mode, bare-name install should only
+ install native arch
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Problem: on a multilib system with multiple architecture repos (e.g.
+th, th-i686, th-x86_64), running "install curl" resolves curl from ALL
+architectures and pulls in a massive foreign-arch dependency chain
+(40+ packages). It also emits a misleading "ambiguous name" warning.
+The user only wanted the native-arch package.
+
+Solution: add ARG_PACKAGES_RESOLV_PREFER_NATIVE flag and filter in
+resolve_masks() — the resolution layer where user masks are matched
+to packages. When the flag is set (install path only, not ls/search):
+
+- Bare-name matches (strcmp with pkg->name) skip non-native, non-noarch
+ packages via the multilib_skip_foreign() helper
+- Glob/fnmatch matches are unaffected — "install curl*" installs all
+- Explicit names like "install curl-8.19.0-1.i686" go through fnmatch
+ and are also unaffected
+
+This eliminates the false "ambiguous name" warning (matches_bycmp now
+only counts native-arch matches) and produces a helpful suggestion when
+no native arch exists at all. The no-native-arch case sets rc=0 (fails
+the transaction) like "no such package" does — the user must use an
+explicit name.
+
+The install validation function (poldek_ts_validate_args_with_stubs)
+has no multilib awareness — arch policy lives entirely in the
+resolution layer where it belongs.
+
+Examples:
+ install curl -> installs only curl.x86_64 (native)
+ install curl-8.19.0-1.i686 -> installs curl.i686 (explicit)
+ install curl* -> installs all matching arches (glob)
+ ls curl -> shows all arches (no PREFER_NATIVE)
+---
+ arg_packages.c | 68 +++++++++++++++++++++++++++++++++++++++++++++-----
+ arg_packages.h | 4 +++
+ 2 files changed, 66 insertions(+), 6 deletions(-)
+
+diff --git a/arg_packages.c b/arg_packages.c
+index 8b2d2cdb..d8def6b9 100644
+--- a/arg_packages.c
++++ b/arg_packages.c
+@@ -457,6 +457,31 @@ tn_array *resolve_bycap(struct arg_packages *aps, struct pkgset *ps,
+ return pkgs;
+ }
+
++/*
++ multilib: should this package be skipped for a bare-name mask match?
++
++ Problem: "install curl" on a multilib system resolves to curl.x86_64,
++ curl.i686, curl.x32 — pulling in a massive foreign-arch dependency
++ chain. The user just wanted the native-arch package.
++
++ This only applies to bare-name matches (strcmp with pkg->name).
++ Explicit names ("install curl-1.0-1.i686") and globs ("install curl*")
++ go through the fnmatch path and are not affected.
++
++ Returns true if the package should be skipped:
++ - caller asked for PREFER_NATIVE (install path, not ls/search)
++ - multilib mode is on
++ - package is not noarch (noarch is always installable)
++ - package arch score != 1 (not the native/base architecture)
++*/
++static int multilib_skip_foreign(const struct pkg *pkg, unsigned flags)
++{
++ return (flags & ARG_PACKAGES_RESOLV_PREFER_NATIVE) &&
++ poldek_conf_MULTILIB &&
++ !pkg_is_noarch(pkg) &&
++ pkg_arch_score(pkg) != 1;
++}
++
+ static
+ int resolve_masks(tn_array *re,
+ struct arg_packages *aps, tn_array *avpkgs,
+@@ -465,6 +490,7 @@ int resolve_masks(tn_array *re,
+ {
+ int i, j, nmasks, rc = 1;
+ int *matches, *matches_bycmp;
++ const char **first_foreign_id;
+
+ nmasks = n_array_size(aps->package_masks);
+
+@@ -474,6 +500,12 @@ int resolve_masks(tn_array *re,
+ matches_bycmp = alloca(nmasks * sizeof(*matches_bycmp));
+ memset(matches_bycmp, 0, nmasks * sizeof(*matches_bycmp));
+
++ /* first_foreign_id[j]: when a bare-name mask skips a foreign-arch
++ package, remember its pkg_id (e.g. "curl-8.19.0-1.i686") so we
++ can suggest it in the warning if no native arch exists at all */
++ first_foreign_id = alloca(nmasks * sizeof(*first_foreign_id));
++ memset(first_foreign_id, 0, nmasks * sizeof(*first_foreign_id));
++
+ for (i=0; i < n_array_size(avpkgs); i++) {
+ struct pkg *pkg = n_array_nth(avpkgs, i);
+
+@@ -494,11 +526,16 @@ int resolve_masks(tn_array *re,
+
+ DBGF("%s cmp %s or %s\n", mask, pkg->name, pkg_id(pkg));
+ if (strcmp(mask, pkg->name) == 0) {
+- if (re)
+- n_array_push(re, pkg_link(pkg));
+-
+- matches_bycmp[j]++;
+- matches[j]++;
++ if (multilib_skip_foreign(pkg, flags)) {
++ if (!first_foreign_id[j])
++ first_foreign_id[j] = pkg_id(pkg);
++ } else {
++ if (re)
++ n_array_push(re, pkg_link(pkg));
++
++ matches_bycmp[j]++;
++ matches[j]++;
++ }
+
+ } else if (fnmatch(mask, pkg_id(pkg), 0) == 0) {
+ if (re)
+@@ -521,6 +558,17 @@ int resolve_masks(tn_array *re,
+ }
+ }
+
++ /* multilib: bare name matched only foreign-arch packages;
++ fail like "no such package" — the user must be explicit */
++ if (unmatched && first_foreign_id[j]) {
++ if (!quiet)
++ logn(LOGWARN, _("%s: no native arch package available;"
++ " to install use explicit name, e.g.: %s"),
++ mask, first_foreign_id[j]);
++ rc = 0;
++ continue;
++ }
++
+ if (unmatched && (flags & ARG_PACKAGES_RESOLV_MISSINGOK) == 0) {
+ if (!quiet)
+ logn(LOGERR, _("%s: no such package"), mask);
+@@ -698,7 +746,15 @@ int arg_packages__validate_with_stubs(struct arg_packages *aps, tn_array *stubpk
+ re = *resolved;
+ }
+
+- if (!resolve_masks(re, aps, stubpkgs, NULL, 0, quiet))
++ /* install path: in multilib mode, bare package names (e.g. "install curl")
++ should resolve only to native-arch packages. The ls/search path
++ (arg_packages_resolve) does not set this flag, so "ls curl" still
++ shows all architectures. */
++ unsigned resolve_flags = 0;
++ if (poldek_conf_MULTILIB)
++ resolve_flags |= ARG_PACKAGES_RESOLV_PREFER_NATIVE;
++
++ if (!resolve_masks(re, aps, stubpkgs, NULL, resolve_flags, quiet))
+ return 0;
+
+ if (n_array_size(aps->packages) > 0) {
+diff --git a/arg_packages.h b/arg_packages.h
+index cbeb163a..2de984c4 100644
+--- a/arg_packages.h
++++ b/arg_packages.h
+@@ -50,6 +50,10 @@ EXPORT int arg_packages_setup(struct arg_packages *aps, struct pm_ctx *ctx);
+ #define ARG_PACKAGES_RESOLV_CAPSINLINE (1 << 4)/* add packages found by caps
+ to resolved packages */
+ #define ARG_PACKAGES_RESOLV_WARN_ONLY (1 << 5)/* warn only*/
++#define ARG_PACKAGES_RESOLV_PREFER_NATIVE (1 << 6)/* multilib: "install curl"
++ resolves only to native arch;
++ "install curl-1.0-1.i686" and
++ "install curl*" still match all */
+
+ int arg_packages__validate_with_stubs(struct arg_packages *aps, tn_array *stubpkgs,
+ tn_array **resolved, int quiet);
+--
+2.53.0
+
+From e924bff4180e6fcba25d8c8d6e12ef5fbe582f52 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Arkadiusz=20Mi=C5=9Bkiewicz?= <arekm@maven.pl>
+Date: Tue, 14 Apr 2026 13:03:34 +0200
+Subject: [PATCH 2/2] test: add multilib bare-name vs explicit arch install
+ tests
+
+Verify that bare-name install picks only native arch, explicit
+name-version.arch installs foreign arch, and glob patterns install
+all matching architectures.
+---
+ tests/sh/07-depsolver | 43 +++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 43 insertions(+)
+
+diff --git a/tests/sh/07-depsolver b/tests/sh/07-depsolver
+index 5c95e6b0..a4ca2d85 100755
+--- a/tests/sh/07-depsolver
++++ b/tests/sh/07-depsolver
+@@ -965,6 +965,49 @@ xtestUninstallHold() {
+ try_uninstall a "a-1-1,a-devel-1-1"
+ }
+
++# bare-name install in multilib should only install native arch
++testInstallBareNameMultilibNativeOnly() {
++ ORIG_POLDEK_INSTALL="$POLDEK_INSTALL"
++ POLDEK_INSTALL="$POLDEK_INSTALL -Omultilib=1"
++
++ msgn "Preparing repositories..."
++ build a 1-1 -a x86_64 -f "/hello.x86_64"
++ build a 1-1 -a i686 -f "/hello.i686"
++
++ msgn "Bare-name install should pick only native arch"
++ try_install a "a-1-1.x86_64"
++
++ POLDEK_INSTALL="$ORIG_POLDEK_INSTALL"
++}
++
++# explicit name-version.arch should install that specific arch
++testInstallExplicitArchMultilib() {
++ ORIG_POLDEK_INSTALL="$POLDEK_INSTALL"
++ POLDEK_INSTALL="$POLDEK_INSTALL -Omultilib=1"
++
++ msgn "Preparing repositories..."
++ build a 1-1 -a x86_64 -f "/hello.x86_64"
++ build a 1-1 -a i686 -f "/hello.i686"
+
++ msgn "Explicit arch should install that specific arch"
++ try_install a-1-1.i686 "a-1-1.i686"
++
++ POLDEK_INSTALL="$ORIG_POLDEK_INSTALL"
++}
++
++# glob pattern should install all matching arches
++testInstallGlobMultilibAllArches() {
++ ORIG_POLDEK_INSTALL="$POLDEK_INSTALL"
++ POLDEK_INSTALL="$POLDEK_INSTALL -Omultilib=1"
++
++ msgn "Preparing repositories..."
++ build a 1-1 -a x86_64 -f "/hello.x86_64"
++ build a 1-1 -a i686 -f "/hello.i686"
++
++ msgn "Glob should install all matching arches"
++ try_install_with_ask_allowed "a*" "a-1-1.x86_64,a-1-1.i686"
++
++ POLDEK_INSTALL="$ORIG_POLDEK_INSTALL"
++}
+
+ . ./sh/lib/shunit2
+--
+2.53.0
+
--- /dev/null
+ fix: do not treat uncolored packages from different architectures as interchangeable
+
+ -devel packages typically have HEADERCOLOR=0 (no ELF binaries, only
+ headers, symlinks, pkg-config and libtool files). pkg_is_colored_like()
+ was returning 1 ("same kind") whenever at least one package had color 0,
+ causing poldek to incorrectly obsolete e.g. xz-devel.x32 when installing
+ xz-devel.x86_64, even though they install to different paths
+ (/usr/libx32 vs /usr/lib64) and are not interchangeable.
+
+ The uncolored fallback was changed to a blanket "return 1" in commit
+ dad87fe8 (LP#299685, 2010). The earlier commit 1fd8aa21 (2008) disabled
+ arch-based fallback with the comment "rpm relies on colors only".
+
+ Regardless of what older RPM versions did, RPM 6.0.1's checkAdded() and
+ rpmtsTeIterator() (lib/depends.cc) use arch string matching as a second
+ layer alongside colors in multilib transactions.
+
+ Fix pkg_is_colored_like() to fall back to arch comparison when at least
+ one package is uncolored, and remove the redundant "any uncolored"
+ bypass in obs_filter() which was a second path for the same bug.
+
+diff --git a/install3/obsoletes.c b/install3/obsoletes.c
+index c70c04da..d137aeac 100644
+--- a/install3/obsoletes.c
++++ b/install3/obsoletes.c
+@@ -132,10 +132,6 @@ int obs_filter(struct pkgdb *db, const struct pm_dbrec *dbrec, void *apkg)
+ if (pkg_is_colored_like(&dbpkg, pkg))
+ return 1;
+
+- /* any uncolored -> rpm allows upgrade */
+- if (dbpkg.color == 0 || pkg->color == 0)
+- return 1;
+-
+ return 0;
+ }
+
+diff --git a/pkgcmp.c b/pkgcmp.c
+index aa8af162..e75ddf5b 100644
+--- a/pkgcmp.c
++++ b/pkgcmp.c
+@@ -80,8 +80,13 @@ int pkg_is_colored_like(const struct pkg *candidate, const struct pkg *pkg)
+ if (pkg->color && candidate->color)
+ return (pkg->color & candidate->color) > 0;
+
+- /* either new or old package contains no binary files, let it happen */
+- return 1;
++ /* When at least one package has no ELF binaries (color 0), fall back
++ to arch comparison. This prevents treating uncolored packages from
++ different architectures (e.g. x32 vs x86_64 -devel packages) as
++ interchangeable. RPM 6.0.1 itself uses arch string matching as a
++ second layer alongside colors (see checkAdded() and
++ rpmtsTeIterator() in rpm's lib/depends.cc). */
++ return pkg_cmp_same_arch(candidate, pkg);
+ }
+
+ /* ret : 1 if pkg is cappable to upgrade arch<=>arch, arch<=>noarch */
--- /dev/null
+commit 2d65d2e1063e0f9c7d2196eb9170cd3426192462
+Author: Arkadiusz Miśkiewicz <arekm@maven.pl>
+Date: Wed Apr 15 21:29:46 2026 +0200
+
+ Break ties between equivalent providers by preferring highest EVR
+
+ When multiple packages provide the same capability and all score
+ equally on every signal (satisfiability, name-prefix match, arch,
+ conflicts, upgrade), the winner was determined by alphabetical sort
+ order of the package name. This systematically picked the oldest
+ available version — e.g. php4-program over php85-program when 23
+ packages provide /usr/bin/php.
+
+ Add an EVR (epoch-version-release) comparison as a tiebreaker in
+ do_select_best_pkg(). When two candidates have identical scores,
+ the one with the higher EVR wins. This only fires when all other
+ scoring signals are equal — it never overrides a score-based
+ preference. Version constraints from the requirement are enforced
+ before candidates reach scoring, so the tiebreaker can only choose
+ among packages that already satisfy the requirement.
+
+ Apply the same tiebreaker in prepare_icap() for the --caplookup
+ path: when no provider is installed, pick highest EVR instead of
+ alphabetically first.
+
+ Tested on 133 PHP packages and 49 non-PHP packages against a
+ builder-like chroot (base system, no PHP installed):
+
+ PHP: Before the fix, 44 out of 133 packages pulled in a mix of
+ PHP versions (e.g. php4 + php52 + php53 + php74 simultaneously).
+ After the fix, all 133 resolve to a single consistent version
+ (php85), except 2 packages that depend on extensions only available
+ for old PHP (php-mnogosearch, php-xcache).
+
+ Java: openjdk8-jre (epoch=1) now selected over icedtea8-jre
+ (no epoch) for ant and maven.
+
+ Init system: systemd-init (epoch=1) now selected over SysVinit
+ (no epoch) for packages requiring an init daemon.
+
+diff --git a/install3/misc.c b/install3/misc.c
+index b1940972..c6b7c0cb 100644
+--- a/install3/misc.c
++++ b/install3/misc.c
+@@ -370,6 +370,22 @@ static int do_select_best_pkg(int indent, struct i3ctx *ictx,
+ if (sc->score > best_score) {
+ best_score = sc->score;
+ i_best = i;
++ } else if (sc->score == best_score) {
++ /* EVR tiebreaker: when scores are tied, prefer the
++ candidate with the highest epoch-version-release.
++ Without this, the winner is the first candidate in
++ alphabetical name order, which systematically picks
++ the oldest version (e.g. php4-program over
++ php85-program when 23 packages provide /usr/bin/php).
++ This is safe because version constraints are enforced
++ before candidates reach scoring — only packages that
++ satisfy the requirement are considered here.
++ See also prepare_icap() for the --caplookup path. */
++ struct pkg *pbest = n_array_nth(candidates, i_best);
++ struct pkg *pcand = n_array_nth(candidates, i);
++ if (pkg_cmp_evr(pcand, pbest) > 0) {
++ i_best = i;
++ }
+ }
+
+ if (sc->satscore > best_satscore) {
+diff --git a/install3/preinstall.c b/install3/preinstall.c
+index b237c1b4..1e7e5934 100644
+--- a/install3/preinstall.c
++++ b/install3/preinstall.c
+@@ -90,8 +90,16 @@ int prepare_icap(struct poldek_ts *ts, const char *capname, tn_array *pkgs)
+ }
+ }
+
+- if (pkg == NULL)
++ if (pkg == NULL) {
++ /* EVR tiebreaker: pick highest EVR instead of
++ alphabetically first; see also do_select_best_pkg() */
+ pkg = n_array_nth(pkgs, 0);
++ for (i = 1; i < n_array_size(pkgs); i++) {
++ struct pkg *p = n_array_nth(pkgs, i);
++ if (pkg_cmp_evr(p, pkg) > 0)
++ pkg = p;
++ }
++ }
+
+ pkg_hand_mark(ts->pms, pkg);
+ return 1;
+diff --git a/pkgcmp.c b/pkgcmp.c
+index e75ddf5b..05fce886 100644
+--- a/pkgcmp.c
++++ b/pkgcmp.c
+@@ -170,7 +170,6 @@ int pkg_cmp_evr(const struct pkg *p1, const struct pkg *p2)
+ return rc;
+ }
+
+-
+ int pkg_cmp_name_evr(const struct pkg *p1, const struct pkg *p2)
+ {
+ register int rc;
--- /dev/null
+diff --git a/cli/search.c b/cli/search.c
+index 4d6f06a..6db2ebc 100644
+--- a/cli/search.c
++++ b/cli/search.c
+@@ -61,6 +61,7 @@ static int pcre_established = 0;
+ struct pattern {
+ int type;
+ char *regexp;
++ int ignore_case;
+ int fnmatch_flags;
+ unsigned pcre_flags;
+ pcre *pcre;
+@@ -71,6 +72,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state);
+ static int search(struct cmdctx *cmdctx);
+
+ #define OPT_PATTERN_PCRE (1 << 10)
++#define OPT_PATTERN_ICASE (1 << 11)
+
+ #define OPT_SEARCH_CAP (1 << 0)
+ #define OPT_SEARCH_REQ (1 << 1)
+@@ -95,7 +97,7 @@ static int search(struct cmdctx *cmdctx);
+ OPT_SEARCH_CHANGELOG)
+
+
+-#define OPT_NO_SEARCHSW OPT_PATTERN_PCRE
++#define OPT_NO_SEARCHSW (OPT_PATTERN_PCRE | OPT_PATTERN_ICASE)
+
+ static struct argp_option options[] = {
+ { "provides", 'p', 0, 0, N_("Search capabilities"), 1},
+@@ -106,6 +108,7 @@ static struct argp_option options[] = {
+ { "summary", 's', 0, 0, N_("Search summaries, urls and license"), 1},
+ { "description", 'd', 0, 0, N_("Search descriptions"), 1},
+ { "group", 'g', 0, 0, N_("Search groups"), 1 },
++ { "ignore-case", 'i', 0, 0, N_("Ignore case when matching"), 1},
+ { "files", 'f', 0, 0, N_("Search file list"), 1},
+ { NULL, 'l', 0, OPTION_ALIAS, 0, 1},
+ { "changelog", 'L', 0, 0, N_("Search changelogs"), 1},
+@@ -124,7 +127,10 @@ struct poclidek_cmd command_search = {
+ options, parse_opt,
+ NULL, search,
+ NULL, NULL,
+- N_("With --perlre pattern must be supplied as:\n"
++ N_("Patterns are matched case-sensitively by default.\n"
++ "Use -i, --ignore-case to ignore case.\n"
++ "\n"
++ "With --perlre pattern must be supplied as:\n"
+ " <delimiter>perl-regexp<delimiter>[imsx]\n"
+ " For example to find the packages containing foo.bar do:\n"
+ " search --perlre /foo\\.bar/\n"
+@@ -204,10 +210,15 @@ static struct pattern *build_pattern(struct cmdctx *cmdctx, char *arg)
+ pt->type = PATTERN_FMASK;
+
+ pt->regexp = n_strdup(regexp);
++ pt->ignore_case = ((cmdctx->_flags & OPT_PATTERN_ICASE) != 0);
+ pt->fnmatch_flags = 0;
+ pt->pcre_flags = flags;
+ pt->pcre = NULL;
+ pt->pcre_extra = NULL;
++
++ if (pt->ignore_case && pt->type == PATTERN_PCRE)
++ pt->pcre_flags |= PCRE_CASELESS;
++
+ return pt;
+ }
+
+@@ -237,6 +248,10 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
+ cmdctx->_flags |= OPT_SEARCH_GROUP;
+ break;
+
++ case 'i':
++ cmdctx->_flags |= OPT_PATTERN_ICASE;
++ break;
++
+ case 'o':
+ cmdctx->_flags |= OPT_SEARCH_OBSL;
+ break;
+@@ -322,7 +337,13 @@ int pattern_compile(struct pattern *pt, int ntimes)
+ n_assert(pt->pcre_extra == NULL);
+
+ #ifdef FNM_CASEFOLD
+- pt->fnmatch_flags |= FNM_CASEFOLD;
++ if (pt->ignore_case)
++ pt->fnmatch_flags |= FNM_CASEFOLD;
++#else
++ if (pt->ignore_case && pt->type == PATTERN_FMASK) {
++ logn(LOGERR, _("search: case-insensitive glob matching is not supported on this platform"));
++ return 0;
++ }
+ #endif
+
+ if (pt->type != PATTERN_PCRE)
+diff --git a/doc/manual.xml b/doc/manual.xml
+index 9d8923c..6c4fcdc 100644
+--- a/doc/manual.xml
++++ b/doc/manual.xml
+@@ -846,7 +846,9 @@ Syntax of command is:
+ search [OPTION...] PATTERN [PACKAGE...]
+ </screen>
+ Where <emphasis>PATTERN</emphasis> is a glob or, with <option>--perlre</option>,
+-Perl regular expression.
++Perl regular expression. Matching is case-sensitive by default; use
++<option>-i</option>, <option>--ignore-case</option> for case-insensitive
++matching.
+
+ For instance to find the packages that contains <filename>/usr/sbin/ab</filename> file:
+ <screen>
+diff --git a/doc/poldek.1 b/doc/poldek.1
+index fd7982e..273e100 100644
+--- a/doc/poldek.1
++++ b/doc/poldek.1
+@@ -51,6 +51,12 @@ is a full\-featured packages management utility, basically designed to work with
+ This manual page covers command line option reference, the full documentation for poldek is available as a Texinfo manual\&. Do
+ \fBinfo poldek\fR
+ to get it\&.
++.PP
++In shell mode, the
++\fBsearch\fR
++command matches case\-sensitively by default; use
++\fBsearch \-i\fR
++for case\-insensitive matching\&.
+ .SH "OPTIONS REFERENCE"
+ .SS "Repository index creation"
+ .PP
+diff --git a/doc/poldek.1.xml b/doc/poldek.1.xml
+index e1a923a..a7eeff1 100644
+--- a/doc/poldek.1.xml
++++ b/doc/poldek.1.xml
+@@ -44,6 +44,10 @@ tasks like installation, upgrading or removal.
+ <para>
+ This manual page covers command line option reference, the full documentation
+ for poldek is available as a Texinfo manual. Do <command>info poldek</command> to get it.
++</para>
++<para>
++In shell mode, the <command>search</command> command matches case-sensitively
++by default; use <command>search -i</command> for case-insensitive matching.
+ </para>
+ </refsect1>
+
%bcond_with python # don't build python bindings
%bcond_with tests # tests
-# required versions (forced to avoid SEGV with mixed db used by rpm and poldek)
%define ver_rpm 1:4.14
%define rel 12
Summary(hu.UTF-8): RPM csomagkezelést segítő eszköz
Summary(pl.UTF-8): Pomocnicze narzędzie do zarządzania pakietami RPM
Name: poldek
-Version: 0.44.0
+Version: 0.45.0
Release: %{rel}
License: GPL v2
Group: Applications/System
#Source0: http://poldek.pld-linux.org/download/snapshots/%{name}-%{version}-cvs%{snap}.tar.bz2
Source0: https://github.com/poldek-pm/poldek/releases/download/v%{version}/%{name}-%{version}.tar.xz
-# Source0-md5: cd0eab5e6fe2ac6995c03541506561fc
+# Source0-md5: 445033ef4d1312718d27b4b7fc197643
Source1: tld.conf
Source2: tld-multilib.conf
Source3: tld-debuginfo.conf
Patch0: %{name}-config.patch
Patch1: pm-hooks.patch
Patch2: %{name}-ext-down-enable.patch
-Patch3: fix-reinstall-sigsev.patch
-Patch4: restore-verify-all.patch
-Patch5: proxy-fix.patch
-Patch6: verify-fix.patch
-Patch7: linguas.patch
-Patch8: pkgiter-preun-req-skip.patch
-Patch9: gcc15.patch
+Patch3: %{name}-search-i-opt.patch
+Patch4: %{name}-nocolor-cmp.patch
+Patch5: %{name}-multilib-bare-name-install.patch
+Patch6: %{name}-dup-sources.patch
+Patch7: %{name}-env-columns-lines.patch
+Patch8: %{name}-scoring-evr.patch
+Patch9: %{name}-global-ignore-merges.patch
URL: http://poldek.pld-linux.org/
BuildRequires: autoconf >= 2.63
BuildRequires: automake >= 1:1.11
%prep
%setup -q
-%patch -P 0 -p1
-%patch -P 1 -p1
-%patch -P 2 -p1
-%patch -P 3 -p1
-%patch -P 4 -p1
-%patch -P 5 -p1
-%patch -P 6 -p1
-%patch -P 7 -p1
-%patch -P 8 -p1
-%patch -P 9 -p1
+%patch -P0 -p1
+%patch -P1 -p1
+%patch -P2 -p1
+%patch -P3 -p1
+%patch -P4 -p1
+%patch -P5 -p1
+%patch -P6 -p1
+%patch -P7 -p1
+%patch -P8 -p1
+%patch -P9 -p1
%{__rm} doc/poldek.info
%{__rm} m4/libtool.m4 m4/lt*.m4
%endif
# sources we don't package
-%{__rm} $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/{rh,fedora,centos}-source.conf
+%{__rm} $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/{repos.d/pld.conf,{rh,fedora,centos}-source.conf}
# include them in %doc
%{__rm} -rf configs
cp -a conf configs
%attr(755,root,root) %ghost %{_libdir}/libpoclidek.so.2
%attr(755,root,root) %ghost %{_libdir}/libpoldek.so.4
%attr(755,root,root) %ghost %{_libdir}/libtndb.so.0
-%attr(755,root,root) %ghost %{_libdir}/libtrurl.so.1
+%attr(755,root,root) %ghost %{_libdir}/libtrurl.so.10
%attr(755,root,root) %ghost %{_libdir}/libvfile.so.0
%endif
+++ /dev/null
-commit ab4f31ec7afa21d7a769ad6ceb5ec176667191b2
-Author: mis <mistoo@gmail.com>
-Date: Sat Mar 1 20:02:11 2025 +0100
-
- fix: do not check http connection with 'HEAD /' when proxied
-
-diff --git a/vfile/vfff/http.c b/vfile/vfff/http.c
-index 92effa2..fa0fc74 100644
---- a/vfile/vfff/http.c
-+++ b/vfile/vfff/http.c
-@@ -790,27 +790,6 @@ static time_t parse_date(const char *dt)
- return ts;
- }
-
--static
--int vhttp_vcn_is_alive(struct vcn *cn)
--{
-- char req_line[256];
--
-- if (cn->state != VCN_ALIVE)
-- return 0;
--
-- make_req_line(req_line, sizeof(req_line), "HEAD", "/");
--
-- if (!httpcn_req(cn, req_line, NULL))
-- return 0;
--
-- if (!httpcn_get_resp(cn)) {
-- cn->state = VCN_DEAD;
-- return 0;
-- }
--
-- return 1;
--}
--
- static int is_closing_connection_status(struct http_resp *resp)
- {
- int close_cn = 0;
-@@ -836,6 +815,34 @@ static int is_closing_connection_status(struct http_resp *resp)
- return close_cn;
- }
-
-+static int vhttp_vcn_is_alive(struct vcn *cn)
-+{
-+ char req_line[256];
-+
-+ if (cn->state != VCN_ALIVE)
-+ return 0;
-+
-+ if (cn->flags & VCN_PROXIED)
-+ return 0;
-+
-+ make_req_line(req_line, sizeof(req_line), "HEAD", "/");
-+
-+ if (!httpcn_req(cn, req_line, NULL))
-+ return 0;
-+
-+ if (!httpcn_get_resp(cn)) {
-+ cn->state = VCN_DEAD;
-+ return 0;
-+ }
-+
-+ if (is_closing_connection_status(cn->resp)) {
-+ cn->state = VCN_DEAD;
-+ return 0;
-+ }
-+
-+ return 1;
-+}
-+
- static
- int is_redirected_connection(struct http_resp *resp, struct vfff_req *rreq)
- {
-diff --git a/vfile/vfff/vfff.c b/vfile/vfff/vfff.c
-index f745ec1..cd014f9 100644
---- a/vfile/vfff/vfff.c
-+++ b/vfile/vfff/vfff.c
-@@ -436,5 +436,10 @@ int vfff_transfer_file(struct vcn *cn, struct vfff_req *vreq, long total_size)
- if (vreq->progress_fn)
- vreq->progress_fn(vreq->progress_fn_data, total_size, -1);
-
-- return is_err == 0;
-+ if (is_err == 0 && cn->state == VCN_ALIVE) {
-+ cn->ts_is_alive = time(0); /* update alive timestamp on success */
-+ return 1;
-+ }
-+
-+ return 0;
- }
-diff --git a/vfile/vfff/vfff.h b/vfile/vfff/vfff.h
-index 22665b0..4f448de 100644
---- a/vfile/vfff/vfff.h
-+++ b/vfile/vfff/vfff.h
-@@ -60,6 +60,7 @@ struct vfff_req;
- /* flags */
- #define VCN_SUPPORTS_SIZE (1 << 0)
- #define VCN_SUPPORTS_MDTM (1 << 1)
-+#define VCN_PROXIED (1 << 9)
-
- struct vcn {
- int proto;
-diff --git a/vfile/vfffmod.c b/vfile/vfffmod.c
-index 98f828d..c6a8503 100644
---- a/vfile/vfffmod.c
-+++ b/vfile/vfffmod.c
-@@ -202,8 +202,12 @@ static struct vcn *vcn_pool_do_connect(struct vf_request *req)
- if (cn == NULL) {
- cn = vcn_new(vcn_proto, host, port, login, passwd,
- req->proxy_login, req->proxy_passwd);
-- if (cn)
-+ if (cn) {
-+ if (req->proxy_host)
-+ cn->flags |= VCN_PROXIED;
-+
- n_list_push(vcn_pool, cn);
-+ }
- }
-
- return cn;
+++ /dev/null
-diff --git a/arg_packages.c b/arg_packages.c
-index 48499a9..fab7618 100644
---- a/arg_packages.c
-+++ b/arg_packages.c
-@@ -350,6 +350,11 @@ int arg_packages_add_pkg(struct arg_packages *aps, struct pkg *pkg)
- return 1;
- }
-
-+int arg_packages_add_pkgs(struct arg_packages *aps, const tn_array *pkgs)
-+{
-+ n_array_concat_ex(aps->packages, pkgs, (tn_fn_dup)pkg_link);
-+ return 1;
-+}
-
- static
- int arg_packages_load_list(struct arg_packages *aps, const char *fpath)
-diff --git a/arg_packages.h b/arg_packages.h
-index 611111a..13e8ba2 100644
---- a/arg_packages.h
-+++ b/arg_packages.h
-@@ -38,7 +38,7 @@ EXPORT int arg_packages_add_pkgmaska(struct arg_packages *aps, tn_array *masks);
- EXPORT int arg_packages_add_pkgfile(struct arg_packages *aps, const char *pathname);
- EXPORT int arg_packages_add_pkglist(struct arg_packages *aps, const char *path);
- EXPORT int arg_packages_add_pkg(struct arg_packages *aps, struct pkg *pkg);
--EXPORT int arg_packages_add_pkga(struct arg_packages *aps, tn_array *pkgs);
-+EXPORT int arg_packages_add_pkgs(struct arg_packages *aps, const tn_array *pkgs);
-
- EXPORT int arg_packages_setup(struct arg_packages *aps, struct pm_ctx *ctx);
-
-diff --git a/poldek_ts.c b/poldek_ts.c
-index 5185f55..398aafd 100644
---- a/poldek_ts.c
-+++ b/poldek_ts.c
-@@ -943,44 +943,31 @@ static int ts_run_uninstall(struct poldek_ts *ts)
- /* just verify deps, conflicts, ordering, etc */
- static int ts_run_verify(struct poldek_ts *ts)
- {
-- int nerr = 0, rc = 1;
-+ int nerr = 0;
-
- //n_assert(poldek_ts_issetf(ts, POLDEK_TS_VERIFY));
-
-- if (poldek_ts_get_arg_count(ts) == 0) {
-- logn(LOGERR, _("Nothing to do"));
-+ if (!ts_prerun(ts))
- return 0;
-- // XXX disabled feature of whole set verification, does anybody needs that?
-- //load_sources(ts->ctx);
--
-- } else {
-- if (!ts_prerun(ts))
-- return 0;
-
-- if (!load_sources(ts->ctx))
-- return 0;
--
-- unsigned flags = TS_MARK_DEPS | TS_MARK_VERBOSE | TS_MARK_CAPSINLINE;
-- rc = ts_mark_arg_packages(ts, flags);
-- (void)rc; /* XXX unused for now */
-- }
-+ if (!load_sources(ts->ctx))
-+ return 0;
-
-- /* XXX disabled feature of whole set verification
-- if (poldek_ts_get_arg_count(ts) > 0) {
-- pkgs = pkgmark_get_packages(ts->pms, PKGMARK_MARK | PKGMARK_DEP);
-+ unsigned flags = TS_MARK_DEPS | TS_MARK_CAPSINLINE;
-
-+ if (poldek_ts_get_arg_count(ts) == 0) { /* no args */
-+ arg_packages_add_pkgs(ts->aps, ts->ctx->ps->pkgs);
- } else {
-- pkgs = n_ref(ts->ctx->ps->pkgs);
-+ flags |= TS_MARK_VERBOSE;
- }
-
-- if (pkgs == NULL)
-- return 0;
-- */
-+ ts_mark_arg_packages(ts, flags);
-
- tn_array *pkgs = pkgmark_get_packages(ts->pms, PKGMARK_ANY);
- if (pkgs == NULL)
- return 0;
-
-+ /* just print errors here, deps are already verified by ts_mark_arg_packages */
- if (ts->getop(ts, POLDEK_OP_VRFY_DEPS)) {
- msgn(3, _("Verifying dependencies..."));
- if (pkgmark_log_unsatisfied_dependecies(ts->pms) > 0)
+++ /dev/null
-diff --git a/trurlib/include/trurl/nhash.h b/trurlib/include/trurl/nhash.h
-index e03fa9c..3ffe267 100644
---- a/trurlib/include/trurl/nhash.h
-+++ b/trurlib/include/trurl/nhash.h
-@@ -104,6 +104,7 @@ uint32_t n_hash_compute_index_hash(const tn_hash *ht, uint32_t raw_hash);
- struct trurl_hash_iterator {
- tn_hash *ht;
- int pos;
-+ int bpos;
- };
-
- typedef struct trurl_hash_iterator tn_hash_it;
-diff --git a/trurlib/n_hash_get.c b/trurlib/n_hash_get.c
-index 81c612b..48f0897 100644
---- a/trurlib/n_hash_get.c
-+++ b/trurlib/n_hash_get.c
-@@ -8,21 +8,39 @@ void n_hash_it_init(tn_hash_it *hi, tn_hash *ht)
- {
- hi->ht = ht;
- hi->pos = 0;
-+ hi->bpos = 0;
- }
-
- void *n_hash_it_get(tn_hash_it *hi, const char **key) {
- struct hash_bucket **tbl = hi->ht->table;
- size_t i = hi->pos;
-
-- while (tbl[i] == NULL && i < hi->ht->size)
-+ while (i < hi->ht->size && tbl[i] == NULL)
- i++;
-
- if (i >= hi->ht->size)
- return NULL;
-
-- struct hash_bucket *ptr = tbl[i];
-+ struct hash_bucket *ptr;
-+ int j = 0;
-+
-+ ptr = tbl[i];
-+ while (ptr != NULL) {
-+ if (j == hi->bpos)
-+ break;
-+ ptr = ptr->next;
-+ j++;
-+ }
-+
-+ n_assert(ptr);
-+
-+ if (ptr->next == NULL) {
-+ hi->pos = i + 1;
-+ hi->bpos = 0;
-+ } else {
-+ hi->bpos++;
-+ }
-
-- hi->pos = i + 1;
- if (key)
- *key = ptr->key;
-
-diff --git a/pkgmark.c b/pkgmark.c
-index 84ea6f4..9845bce 100644
---- a/pkgmark.c
-+++ b/pkgmark.c
-@@ -351,8 +351,9 @@ void pkgmark_massset(struct pkgmark_set *pms, int set, uint32_t flag)
- return;
-
- tn_hash_it it;
-- struct pkg_mark *m;
-+ n_hash_it_init(&it, pms->ht);
-
-+ struct pkg_mark *m;
- while ((m = n_hash_it_get(&it, NULL)) != NULL) {
- if (set)
- m->flags |= flag;
-diff --git a/cli/dent.c b/cli/dent.c
-index b24a723..f4cd4bb 100644
---- a/cli/dent.c
-+++ b/cli/dent.c
-@@ -36,13 +36,14 @@ struct pkg_dent *pkg_dent_new(struct poclidek_ctx *cctx, const char *name,
- struct pkg *pkg, int flags, const char *dirpath)
- {
- struct pkg_dent *ent;
-- int dirpath_at = 0, dirpath_len = 0, len = 0;
-+ int name_len = 0, dirpath_at = 0, dirpath_len = 0, len = 0;
-
- if (name) {
- while (*name == '/')
- name++;
-
-- len += strlen(name) + 1;
-+ name_len = strlen(name);
-+ len += name_len + 1;
- n_assert(flags & PKG_DENT_DIR);
- n_assert(dirpath);
-
-@@ -60,7 +61,7 @@ struct pkg_dent *pkg_dent_new(struct poclidek_ctx *cctx, const char *name,
- if (name) {
- char *p;
-
-- memcpy(ent->_buf, name, len);
-+ memcpy(ent->_buf, name, name_len + 1);
- ent->name = ent->_buf;
-
- if (dirpath) {
-diff --git a/cli/ls.c b/cli/ls.c
-index 0027ee0..e2bc01d 100644
---- a/cli/ls.c
-+++ b/cli/ls.c
-@@ -585,9 +585,10 @@ int do_ls(const tn_array *ents, struct cmdctx *cmdctx, const tn_array *evrs)
- cmdctx_printf(cmdctx, "%-*s %-*s\n",
- term_width_div2 + term_width_div2/10 - 1, pkg_name,
- (term_width/7), group ? group : "(unset)");
-- }
-- else if (flags & OPT_LS_SOURCERPM) {
-- const char *srcrpm = pkg_srcfilename_s(pkg);
-+ } else if (flags & OPT_LS_SOURCERPM) {
-+ char buf[512];
-+ const char *srcrpm = pkg_srcfilename(pkg, buf, sizeof(buf));
-+
- cmdctx_printf(cmdctx, "%-*s %-*s\n",
- term_width_div2 + term_width_div2/10 - 1, pkg_name,
- (term_width/7), srcrpm ? srcrpm : "(unset)");
-diff --git a/vfile/vfile.h b/vfile/vfile.h
-index 3b55b00..b1612f2 100644
---- a/vfile/vfile.h
-+++ b/vfile/vfile.h
-@@ -174,11 +174,11 @@ EXPORT int vf_url_as_path(char *buf, size_t size, const char *url);
-
- /* replace password with "x" * len(password) */
- EXPORT const char *vf_url_hidepasswd(char *buf, int size, const char *url);
--#define vf_url_hidepasswd_s(url) vf_url_hidepasswd(alloca(PATH_MAX), PATH_MAX, url)
-+#define vf_url_hidepasswd_s(url) vf_url_hidepasswd(alloca(256), 256, url)
-
- /* applies vf_url_hidepasswd() + slim down url string to maxl */
- EXPORT const char *vf_url_slim(char *buf, int size, const char *url, int maxl);
--#define vf_url_slim_s(url, maxl) vf_url_slim(alloca(PATH_MAX), PATH_MAX, url, (maxl) > 40 ? (maxl) : 40)
-+#define vf_url_slim_s(url, maxl) vf_url_slim(alloca(256), 256, url, (maxl) > 40 ? (maxl) : 40)
-
- EXPORT char *vf_url_unescape(const char *url);
-
-diff --git a/pkg.h b/pkg.h
-index 15a68c4..59f3e84 100644
---- a/pkg.h
-+++ b/pkg.h
-@@ -223,14 +223,14 @@ EXPORT int pkg_has_pkgcnfl(struct pkg *pkg, struct pkg *cpkg);
-
- /* src.rpm */
- EXPORT char *pkg_srcfilename(const struct pkg *pkg, char *buf, size_t size);
--#define pkg_srcfilename_s(pkg) pkg_srcfilename(pkg, alloca(512), 512)
-+#define pkg_srcfilename_s(pkg) pkg_srcfilename(pkg, alloca(256), 256)
-
- /* RET %path/%name-%version-%release.%arch.rpm */
- EXPORT char *pkg_filename(const struct pkg *pkg, char *buf, size_t size);
--#define pkg_filename_s(pkg) pkg_filename(pkg, alloca(512), 512)
-+#define pkg_filename_s(pkg) pkg_filename(pkg, alloca(256), 256)
-
- EXPORT char *pkg_path(const struct pkg *pkg, char *buf, size_t size);
--#define pkg_path_s(pkg) pkg_path(pkg, alloca(512), 512)
-+#define pkg_path_s(pkg) pkg_path(pkg, alloca(256), 256)
-
- EXPORT char *pkg_localpath(const struct pkg *pkg, char *path, size_t size,
- const char *cachedir);
-@@ -251,13 +251,13 @@ EXPORT int pkg_printf(const struct pkg *pkg, const char *str);
- EXPORT int pkg_snprintf(char *str, size_t size, const struct pkg *pkg);
- EXPORT char *pkg_str(char *str, size_t size, const struct pkg *pkg);
-
--#define pkg_snprintf_s(pkg) pkg_str(alloca(512), 512, pkg)
--#define pkg_snprintf_s0(pkg) pkg_str(alloca(512), 512, pkg)
--#define pkg_snprintf_s1(pkg) pkg_str(alloca(512), 512, pkg)
-+#define pkg_snprintf_s(pkg) pkg_str(alloca(256), 256, pkg)
-+#define pkg_snprintf_s0(pkg) pkg_str(alloca(256), 256, pkg)
-+#define pkg_snprintf_s1(pkg) pkg_str(alloca(256), 256, pkg)
-
- EXPORT int pkg_evr_snprintf(char *str, size_t size, const struct pkg *pkg);
- EXPORT char *pkg_evr_str(char *str, size_t size, const struct pkg *pkg);
--#define pkg_evr_snprintf_s(pkg) pkg_evr_str(alloca(512), 512, pkg)
-+#define pkg_evr_snprintf_s(pkg) pkg_evr_str(alloca(256), 256, pkg)
-
- /* must be free()d by pkguinf_free(); see pkgu.h */
- EXPORT struct pkguinf *pkg_uinf(const struct pkg *pkg);
-diff --git a/pkgdir/pkg_restore.c b/pkgdir/pkg_restore.c
-index e7e9373..8dfb4c9 100644
---- a/pkgdir/pkg_restore.c
-+++ b/pkgdir/pkg_restore.c
-@@ -202,9 +202,9 @@ struct pkg *pkg_restore_st(tn_stream *st, tn_alloc *na, struct pkg *pkg,
- int tag, last_tag, tag_binsize = PKG_STORETAG_SIZENIL;
- const char *errmg_double_tag = "%s:%lu: double '%c' tag";
- const char *errmg_ldtag = "%s:%lu: load '%c' tag error";
-+ int load_full_fl = (ldflags & PKGDIR_LD_FULLFLIST);
-
- #if 0
-- printf("FULL %d\n", (ldflags & PKGDIR_LD_FULLFLIST));
- if (depdirs) {
- int i;
- printf("depdirs %p %d\n", depdirs, n_array_size(depdirs));
-@@ -387,13 +387,13 @@ struct pkg *pkg_restore_st(tn_stream *st, tn_alloc *na, struct pkg *pkg,
-
- case PKG_STORETAG_FL:
- pkgt.nodep_files_offs = n_stream_tell(st);
-- //printf("flag_fullflist %d, %p\n", flag_fullflist, depdirs);
-- if ((ldflags & PKGDIR_LD_FULLFLIST) == 0 && depdirs == NULL) {
-+ if (!load_full_fl && depdirs == NULL) {
- pkgfl_skip_st(st);
-
- } else {
- tn_tuple *fl;
-- if (pkgfl_restore_st(na, &fl, st, depdirs, 1) < 0) {
-+
-+ if (pkgfl_restore_st(na, &fl, st, load_full_fl ? NULL : depdirs, 1) < 0) {
- logn(LOGERR, errmg_ldtag, fn, ul_offs, *line);
- nerr++;
- goto l_end;
-@@ -422,6 +422,10 @@ struct pkg *pkg_restore_st(tn_stream *st, tn_alloc *na, struct pkg *pkg,
-
- pkgt.pkgfl = ffl;
- }
-+
-+ pkgt.flags |= PKGT_HAS_FILES;
-+ if (load_full_fl)
-+ pkgt.flags |= PKGT_HAS_ALLFILES;
- }
- break;
-