Imported Upstream version 17.24.1 upstream/17.24.1
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 30 Nov 2020 07:01:35 +0000 (16:01 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 30 Nov 2020 07:01:35 +0000 (16:01 +0900)
35 files changed:
.clang-format
VERSION.cmake
doc/autoinclude/EnvironmentVariables.doc
package/libzypp.changes
po/fi.po
po/gl.po
po/hi.po
po/uk.po
tests/zypp/PurgeKernels_test.cc
tests/zypp/base/String_test.cc
tests/zypp/data/PurgeKernels/arch/solver-system.xml
tests/zypp/data/PurgeKernels/rebuild/solver-system.xml [new file with mode: 0644]
tests/zypp/data/PurgeKernels/rebuild/solver-test.xml [new file with mode: 0644]
tests/zypp/data/PurgeKernels/simple/solver-system.xml
tests/zypp/data/PurgeKernels/withdeps/solver-system.xml
tests/zyppng/media/EvDownloader_test.cc
zypp/PurgeKernels.cc
zypp/RepoInfo.cc
zypp/RepoManager.cc
zypp/Resolver.cc
zypp/Resolver.h
zypp/ZYppCallbacks.h
zypp/base/Regex.cc
zypp/base/Regex.h
zypp/media/CurlHelper.cc
zypp/media/MediaCurl.cc
zypp/media/MediaUserAuth.cc
zypp/media/MediaUserAuth.h
zypp/solver/detail/Resolver.cc
zypp/solver/detail/Resolver.h
zypp/solver/detail/SATResolver.h
zypp/target/TargetCallbackReceiver.cc
zypp/target/TargetCallbackReceiver.h
zypp/target/rpm/RpmDb.cc
zypp/zyppng/media/network/downloader.cc

index da75ab9..c217262 100644 (file)
@@ -79,7 +79,7 @@ KeepEmptyLinesAtTheStartOfBlocks: true
 MacroBlockBegin: ''
 MacroBlockEnd: ''
 MaxEmptyLinesToKeep: 1
-NamespaceIndentation: None
+NamespaceIndentation: All
 ObjCBinPackProtocolList: Auto
 ObjCBlockIndentWidth: 2
 ObjCSpaceAfterProperty: true
index e477f17..cf102dc 100644 (file)
@@ -60,9 +60,9 @@
 #
 SET(LIBZYPP_MAJOR "17")
 SET(LIBZYPP_COMPATMINOR "22")
-SET(LIBZYPP_MINOR "23")
-SET(LIBZYPP_PATCH "8")
+SET(LIBZYPP_MINOR "24")
+SET(LIBZYPP_PATCH "1")
 #
-# LAST RELEASED: 17.23.8 (22)
+# LAST RELEASED: 17.24.1 (22)
 # (The number in parenthesis is LIBZYPP_COMPATMINOR)
 #=======
index cdeeff3..ad0b1bd 100644 (file)
@@ -32,6 +32,8 @@
 \li \c ZYPP_MEDIA_CURL_DEBUG=<1|2> Log http headers, if \c 2 also log server responses.
 \li \c ZYPP_MEDIA_CURL_IPRESOLVE=<4|6> Tell curl to resolve names to IPv4/IPv6 addresses only.
 
+\li \c ZYPP_RPM_DEBUG=1 Log verbose output from all rpm commands.
+
 \subsection zypp-envars-mediabackend Selecting the mediabackend to use.
 
 \li \c ZYPP_MULTICURL=0 Turn off multicurl (metalink and zsync) and fall back to plain libcurl.
index 4ca09eb..b6af4fa 100644 (file)
@@ -1,4 +1,23 @@
 -------------------------------------------------------------------
+Thu Jul 16 18:04:18 CEST 2020 - ma@suse.de
+
+- Fix bsc#1174011 auth=basic ignored in some cases (bsc#1174011)
+  Proactively send credentials if the URL specifes '?auth=basic'
+  and a username.
+- ZYPP_MEDIA_CURL_DEBUG: Strip credentials in header log (bsc#1174011)
+- version 17.24.1 (22)
+
+-------------------------------------------------------------------
+Tue Jul 14 13:19:56 CEST 2020 - ma@suse.de
+
+- Completey rework the purge-kernels algorithm (fix bsc#1173106)
+  The new code is closer to the original perl script, grouping the
+  packages by name before applying the keep spec.
+- Set ZYPP_RPM_DEBUG=1 to capture verbose rpm command output
+  (implements #228)
+- version 17.24.0 (22)
+
+-------------------------------------------------------------------
 Fri Jun 26 14:13:10 CEST 2020 - ma@suse.de
 
 - Fix core dump with corrupted history file (bsc#1170801)
index a7f6426..bc98646 100644 (file)
--- a/po/fi.po
+++ b/po/fi.po
@@ -20,8 +20,8 @@ msgstr ""
 "Project-Id-Version: zypp.fi\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2019-12-05 14:22+0100\n"
-"PO-Revision-Date: 2020-01-13 19:59+0000\n"
-"Last-Translator: Tommi Nieminen <software@legisign.org>\n"
+"PO-Revision-Date: 2020-06-28 06:55+0000\n"
+"Last-Translator: Kimmo Kujansuu <mrkujansuu@gmail.com>\n"
 "Language-Team: Finnish <https://l10n.opensuse.org/projects/libzypp/master/fi/"
 ">\n"
 "Language: fi\n"
@@ -4133,7 +4133,7 @@ msgstr "Virheellinen viittaus tyhjään URL-objektiin"
 
 #: zypp/Url.cc:327 zypp/Url.cc:341
 msgid "Unable to parse Url components"
-msgstr "Verkko-osoitteen jäsentäminen ei onnistu."
+msgstr "Verkko-osoitteen jäsentäminen ei onnistu"
 
 #: zypp/VendorSupportOptions.cc:14
 msgid "unknown"
@@ -4365,7 +4365,7 @@ msgstr ""
 #: zypp/media/MediaException.cc:146
 #, c-format, boost-format
 msgid "Error occurred while setting download (curl) options for '%s':"
-msgstr "Virhe asetettaessa lataajan (curl) valintoja osoitteelle \"%s\"."
+msgstr "Virhe määritettäessä kohteen '%s' latausasetuksia (curl):"
 
 #: zypp/media/MediaException.cc:153
 #, c-format, boost-format
index ca4b63f..71a75a3 100644 (file)
--- a/po/gl.po
+++ b/po/gl.po
@@ -15,7 +15,7 @@ msgstr ""
 "Project-Id-Version: YaST (@memory@)\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2019-12-05 14:22+0100\n"
-"PO-Revision-Date: 2019-08-26 11:51+0000\n"
+"PO-Revision-Date: 2020-06-29 19:45+0000\n"
 "Last-Translator: Manuel Vazquez <xixirei@yahoo.es>\n"
 "Language-Team: Galician <https://l10n.opensuse.org/projects/libzypp/master/"
 "gl/>\n"
@@ -4951,13 +4951,13 @@ msgstr ""
 #: zypp/solver/detail/SATResolver.cc:1389
 #, boost-format
 msgid "allow to install the PTF %1%"
-msgstr ""
+msgstr "permitir a instalación de PTF %1%"
 
 #. translator: %1% is a package name
 #: zypp/solver/detail/SATResolver.cc:1392
 #, boost-format
 msgid "install %1% although it is blacklisted"
-msgstr ""
+msgstr "instalar %1% aínda que estea na lista negra"
 
 #: zypp/solver/detail/SATResolver.cc:1412
 #, c-format, boost-format
index 7f1bc04..633f9ca 100644 (file)
--- a/po/hi.po
+++ b/po/hi.po
@@ -5,15 +5,16 @@ msgstr ""
 "Project-Id-Version: zypp.hi\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2019-12-05 14:22+0100\n"
-"PO-Revision-Date: 2007-08-24 22:33+0530\n"
-"Last-Translator: Sangeeta Kumari <k.sangeeta09@gmail.com>\n"
-"Language-Team: Hindi <en@li.org>\n"
+"PO-Revision-Date: 2020-06-29 19:45+0000\n"
+"Last-Translator: Panwar <caspian7pena@gmail.com>\n"
+"Language-Team: Hindi <https://l10n.opensuse.org/projects/libzypp/master/hi/>"
+"\n"
 "Language: hi\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 3.6.1\n"
 
 #. dubious: Throw on malformed known types, otherwise log a warning.
 #: zypp/CheckSum.cc:136
@@ -23,7 +24,7 @@ msgstr ""
 
 #: zypp/CountryCode.cc:50
 msgid "Unknown country: "
-msgstr "अज्ञात देश :"
+msgstr "अज्ञात देश : "
 
 #. Defined CountryCode constants
 #. Defined LanguageCode constants
@@ -4672,7 +4673,7 @@ msgstr ""
 #: zypp/target/rpm/RpmDb.cc:1929
 #, c-format, boost-format
 msgid "Changed configuration files for %s:"
-msgstr "%s: के लिए परिवर्तित कंफिगरेशन फाइलें"
+msgstr "%s हेतु विन्यास फ़ाइलें जिन्हें बदला गया :"
 
 #. %s = filenames
 #: zypp/target/rpm/RpmDb.cc:2104
index 6d2ef3a..c5f52a0 100644 (file)
--- a/po/uk.po
+++ b/po/uk.po
@@ -14,23 +14,23 @@ msgstr ""
 "Project-Id-Version: zypp.uk\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2019-12-05 14:22+0100\n"
-"PO-Revision-Date: 2018-04-14 04:38+0000\n"
-"Last-Translator: Andriy Bandura <andriykopanytsia@gmail.com>\n"
+"PO-Revision-Date: 2020-06-29 19:45+0000\n"
+"Last-Translator: Lesath <4lesath@gmail.com>\n"
 "Language-Team: Ukrainian <https://l10n.opensuse.org/projects/libzypp/master/"
 "uk/>\n"
 "Language: uk\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
-"X-Generator: Weblate 2.18\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
+"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Weblate 3.6.1\n"
 
 #. dubious: Throw on malformed known types, otherwise log a warning.
 #: zypp/CheckSum.cc:136
 #, c-format, boost-format
 msgid "Dubious type '%s' for %u byte checksum '%s'"
-msgstr "Сумнівний тип \"%s\" для %u байтової контрольної суми \"%s\""
+msgstr "Сумнівний тип «%s» для %u байтової контрольної суми «%s»"
 
 #: zypp/CountryCode.cc:50
 msgid "Unknown country: "
@@ -60,7 +60,7 @@ msgstr "Афганістан"
 #. :AFG:004:
 #: zypp/CountryCode.cc:161
 msgid "Antigua and Barbuda"
-msgstr "ТÑ\80инÑ\96дад Ñ\82а Ð¢Ð¾Ð±Ð°Ð³Ð¾"
+msgstr "Ð\90нÑ\82игÑ\83а Ñ\96 Ð\91аÑ\80бÑ\83да"
 
 #. :ATG:028:
 #: zypp/CountryCode.cc:162
@@ -77,14 +77,14 @@ msgstr "Албанія"
 #. :ALB:008:
 #: zypp/CountryCode.cc:164
 msgid "Armenia"
-msgstr "Ð\90Ñ\80генÑ\82ина"
+msgstr "Ð\92Ñ\96Ñ\80менÑ\96Ñ\8f"
 
 # AN
 # fuzzy
 #. :ARM:051:
 #: zypp/CountryCode.cc:165
 msgid "Netherlands Antilles"
-msgstr "Ð\93олландÑ\96Ñ\8f"
+msgstr "Ð\9dÑ\96деÑ\80ландÑ\81Ñ\8cкÑ\96 Ð\90нÑ\82илÑ\8cÑ\81Ñ\8cкÑ\96 Ð¾Ñ\81Ñ\82Ñ\80ови"
 
 #. :ANT:530:
 #: zypp/CountryCode.cc:166
@@ -106,7 +106,7 @@ msgstr "Аргентина"
 #. :ARG:032:
 #: zypp/CountryCode.cc:169
 msgid "American Samoa"
-msgstr "Африка, північ"
+msgstr "Американське Самоа"
 
 #. :ASM:016:
 #: zypp/CountryCode.cc:170
@@ -160,7 +160,7 @@ msgstr "Бельгія"
 #. :BEL:056:
 #: zypp/CountryCode.cc:179
 msgid "Burkina Faso"
-msgstr "Буркіна Фасо"
+msgstr "Буркіна-Фасо"
 
 #. :BFA:854:
 #: zypp/CountryCode.cc:180
@@ -172,7 +172,7 @@ msgstr "Болгарія"
 #. :BGR:100:
 #: zypp/CountryCode.cc:181
 msgid "Bahrain"
-msgstr "Бразилія"
+msgstr "Бахрейн"
 
 #. :BHR:048:
 #: zypp/CountryCode.cc:182
@@ -210,7 +210,7 @@ msgstr "Бразилія"
 #. :BRA:076:
 #: zypp/CountryCode.cc:188
 msgid "Bahamas"
-msgstr "Ð\9fанама"
+msgstr "Ð\91агамÑ\81Ñ\8cкÑ\96 Ð¾Ñ\81Ñ\82Ñ\80ови"
 
 # BH
 # fuzzy
@@ -239,7 +239,7 @@ msgstr "Білорусь"
 #. :BLR:112:
 #: zypp/CountryCode.cc:193
 msgid "Belize"
-msgstr "Ð\91елÑ\8cгÑ\96Ñ\8f"
+msgstr "Ð\91елÑ\96з"
 
 #. :BLZ:084:
 #: zypp/CountryCode.cc:194
@@ -315,7 +315,7 @@ msgstr "Куба"
 #. :CUB:192:
 #: zypp/CountryCode.cc:208
 msgid "Cape Verde"
-msgstr "Ð\9aапо Верде"
+msgstr "Ð\9aабо-Верде"
 
 #. :CPV:132:
 #: zypp/CountryCode.cc:209
@@ -352,7 +352,7 @@ msgstr "Данія"
 #. :DNK:208:
 #: zypp/CountryCode.cc:215
 msgid "Dominica"
-msgstr "РÑ\83мÑ\83нÑ\96Ñ\8f"
+msgstr "Ð\94омÑ\96нÑ\96ка"
 
 #. :DMA:212:
 #: zypp/CountryCode.cc:216
@@ -399,7 +399,7 @@ msgstr "Іспанія"
 #. :ESP:724:
 #: zypp/CountryCode.cc:224
 msgid "Ethiopia"
-msgstr "Ð\95Ñ\81Ñ\82онія"
+msgstr "Ð\95Ñ\84Ñ\96опія"
 
 #. :ETH:231:
 #: zypp/CountryCode.cc:225
@@ -446,7 +446,7 @@ msgstr "Габон"
 #. :GAB:266:
 #: zypp/CountryCode.cc:233
 msgid "United Kingdom"
-msgstr "Ð\9eб'Ñ\94днане Королівство"
+msgstr "СполÑ\83Ñ\87ене Королівство"
 
 # GD
 #. :GBR:826:
@@ -473,7 +473,7 @@ msgstr "Гернсі"
 # fuzzy
 #: zypp/CountryCode.cc:238
 msgid "Ghana"
-msgstr "Ð\9aиÑ\82ай"
+msgstr "Ð\93ана"
 
 #. :GHA:288:
 #: zypp/CountryCode.cc:239
@@ -490,7 +490,7 @@ msgstr "Гренландія"
 #. :GRL:304:
 #: zypp/CountryCode.cc:241
 msgid "Gambia"
-msgstr "Ямайка"
+msgstr "Ð\93амбÑ\96Ñ\8f"
 
 # GU
 # fuzzy
@@ -529,7 +529,7 @@ msgstr "Гватемала"
 #. :GTM:320:
 #: zypp/CountryCode.cc:248
 msgid "Guam"
-msgstr "Гватемала"
+msgstr "Гуам"
 
 #. :GUM:316:
 #: zypp/CountryCode.cc:249
@@ -546,7 +546,7 @@ msgstr "Гвіана"
 #. :GUY:328:
 #: zypp/CountryCode.cc:251
 msgid "Hong Kong"
-msgstr "Ð\93онг Ð\9aонг"
+msgstr "Ð\93онконг"
 
 #. :HKG:344:
 #: zypp/CountryCode.cc:252
@@ -612,7 +612,7 @@ msgstr "Ірак"
 #. :IRQ:368:
 #: zypp/CountryCode.cc:264
 msgid "Iran"
-msgstr "Ізраїль"
+msgstr "Іран"
 
 #. :IRN:364:
 #: zypp/CountryCode.cc:265
@@ -662,7 +662,7 @@ msgstr "Киргизстан"
 #. :KGZ:417:
 #: zypp/CountryCode.cc:273
 msgid "Cambodia"
-msgstr "Ð\9aолÑ\83мбÑ\96Ñ\8f"
+msgstr "Ð\9aамбоджа"
 
 #. :KHM:116:
 #: zypp/CountryCode.cc:274
@@ -706,7 +706,7 @@ msgstr "Кайманові острови"
 #. :CYM:136:
 #: zypp/CountryCode.cc:281
 msgid "Kazakhstan"
-msgstr "ТайванÑ\8c"
+msgstr "Ð\9aазаÑ\85Ñ\81Ñ\82ан"
 
 #. :KAZ:398:
 #: zypp/CountryCode.cc:282
@@ -740,7 +740,7 @@ msgstr "Шрі-Ланка"
 #. :LKA:144:
 #: zypp/CountryCode.cc:287
 msgid "Liberia"
-msgstr "СеÑ\80бія"
+msgstr "Ð\9bÑ\96беÑ\80ія"
 
 #. :LBR:430:
 #: zypp/CountryCode.cc:288
@@ -808,21 +808,21 @@ msgstr "Маршаллові острови"
 #. :MHL:584:
 #: zypp/CountryCode.cc:300
 msgid "Macedonia"
-msgstr "Македонія"
+msgstr "Ð\9fÑ\96внÑ\96Ñ\87на Ð\9cакедонÑ\96Ñ\8f"
 
 # ML
 # fuzzy
 #. :MKD:807:
 #: zypp/CountryCode.cc:301
 msgid "Mali"
-msgstr "Ð\9cалÑ\8cÑ\82а"
+msgstr "Ð\9cалÑ\96"
 
 # MM
 # fuzzy
 #. :MLI:466:
 #: zypp/CountryCode.cc:302
 msgid "Myanmar"
-msgstr "Ð\9fанама"
+msgstr "Ð\9câ\80\99Ñ\8fнма"
 
 #. :MMR:104:
 #: zypp/CountryCode.cc:303
@@ -834,7 +834,7 @@ msgstr "Монголія"
 #. :MNG:496:
 #: zypp/CountryCode.cc:304
 msgid "Macao"
-msgstr "Ð\9cалÑ\8cÑ\82а"
+msgstr "Ð\90оминÑ\8c"
 
 #. :MAC:446:
 #: zypp/CountryCode.cc:305
@@ -853,7 +853,7 @@ msgstr "Мартиніка"
 #. :MTQ:474:
 #: zypp/CountryCode.cc:307
 msgid "Mauritania"
-msgstr "Ð\9bиÑ\82ва"
+msgstr "Ð\9cавÑ\80Ñ\96Ñ\82анÑ\96Ñ\8f"
 
 #. :MRT:478:
 #: zypp/CountryCode.cc:308
@@ -870,21 +870,21 @@ msgstr "Мальта"
 #. :MLT:470:
 #: zypp/CountryCode.cc:310
 msgid "Mauritius"
-msgstr "Ð\9bиÑ\82ва"
+msgstr "Ð\9cавÑ\80Ñ\96кÑ\96й"
 
 # MV
 # fuzzy
 #. :MUS:480:
 #: zypp/CountryCode.cc:311
 msgid "Maldives"
-msgstr "Мальта"
+msgstr "Мальдіви"
 
 # MW
 # fuzzy
 #. :MDV:462:
 #: zypp/CountryCode.cc:312
 msgid "Malawi"
-msgstr "Мальта"
+msgstr "Малаві"
 
 #. :MWI:454:
 #: zypp/CountryCode.cc:313
@@ -913,7 +913,7 @@ msgstr "Намібія"
 #. :NAM:516:
 #: zypp/CountryCode.cc:317
 msgid "New Caledonia"
-msgstr "Ð\9cакедонія"
+msgstr "Ð\9dова Ð\9aаледонія"
 
 # NG
 # fuzzy
@@ -1023,7 +1023,7 @@ msgstr "Піткерн"
 #. :PCN:612:
 #: zypp/CountryCode.cc:338
 msgid "Puerto Rico"
-msgstr "Пуерто ріко"
+msgstr "Пуертоіко"
 
 #. :PRI:630:
 #: zypp/CountryCode.cc:339
@@ -1040,7 +1040,7 @@ msgstr "Португалія"
 #. :PRT:620:
 #: zypp/CountryCode.cc:341
 msgid "Palau"
-msgstr "Парагвай"
+msgstr "Палау"
 
 #. :PLW:585:
 #: zypp/CountryCode.cc:342
@@ -1076,7 +1076,7 @@ msgstr "Російська Федерація"
 #. :RUS:643:
 #: zypp/CountryCode.cc:348
 msgid "Rwanda"
-msgstr "Ð\9aанада"
+msgstr "РÑ\83анда"
 
 #. :RWA:646:
 #: zypp/CountryCode.cc:349
@@ -1126,7 +1126,7 @@ msgstr "Свальбард і Ян Маєн"
 #. :SJM:744:
 #: zypp/CountryCode.cc:358
 msgid "Slovakia"
-msgstr "СловаÑ\86Ñ\8cка"
+msgstr "СловаÑ\87Ñ\87ина"
 
 #. :SVK:703:
 #: zypp/CountryCode.cc:359
@@ -1148,7 +1148,7 @@ msgstr "Сенегал"
 #. :SEN:686:
 #: zypp/CountryCode.cc:362
 msgid "Somalia"
-msgstr "РÑ\83мÑ\83нÑ\96Ñ\8f"
+msgstr "СомалÑ\96"
 
 #. :SOM:706:
 #: zypp/CountryCode.cc:363
@@ -1170,14 +1170,14 @@ msgstr "Ель Сальвадор"
 #. :SLV:222:
 #: zypp/CountryCode.cc:366
 msgid "Syria"
-msgstr "СеÑ\80бія"
+msgstr "СиÑ\80ія"
 
 # SZ
 # fuzzy
 #. :SYR:760:
 #: zypp/CountryCode.cc:367
 msgid "Swaziland"
-msgstr "ТаÑ\97ланд"
+msgstr "Ð\95Ñ\81ваÑ\82Ñ\96нÑ\96"
 
 #. :SWZ:748:
 #: zypp/CountryCode.cc:368
@@ -1189,7 +1189,7 @@ msgstr "Острови Теркс і Кайкос"
 #. :TCA:796:
 #: zypp/CountryCode.cc:369
 msgid "Chad"
-msgstr "Ð\9aиÑ\82ай"
+msgstr "Чад"
 
 #. :TCD:148:
 #: zypp/CountryCode.cc:370
@@ -1274,14 +1274,14 @@ msgstr "Україна"
 #. :UKR:804:
 #: zypp/CountryCode.cc:385
 msgid "Uganda"
-msgstr "Ð\9aанада"
+msgstr "Уганда"
 
 # UM
 # fuzzy
 #. :UGA:800:
 #: zypp/CountryCode.cc:386
 msgid "United States Minor Outlying Islands"
-msgstr "США"
+msgstr "Ð\92Ñ\96ддаленÑ\96 Ð¾Ñ\81Ñ\82Ñ\80ови Ð¡Ð¨Ð\90"
 
 # US
 # fuzzy
@@ -1303,7 +1303,7 @@ msgstr "Узбекистан"
 #. :UZB:860:
 #: zypp/CountryCode.cc:390
 msgid "Holy See (Vatican City State)"
-msgstr "Святий Престіл (Ватикан, місто-держава)"
+msgstr "Святий Престол (Ватикан, місто-держава)"
 
 # VC
 # fuzzy
@@ -1336,7 +1336,7 @@ msgstr "Віргінські острови, США"
 #. :VIR:850:
 #: zypp/CountryCode.cc:395
 msgid "Vietnam"
-msgstr "В'єтнамська"
+msgstr "В'єтнам"
 
 #. :VNM:704:
 #: zypp/CountryCode.cc:396
@@ -1375,7 +1375,7 @@ msgstr "Південна Африка"
 #. :ZAF:710:
 #: zypp/CountryCode.cc:402
 msgid "Zambia"
-msgstr "Ямайка"
+msgstr "Ð\97амбÑ\96Ñ\8f"
 
 #. :ZMB:894:
 #: zypp/CountryCode.cc:403
@@ -1384,7 +1384,7 @@ msgstr "Зімбабве"
 
 #: zypp/Dep.cc:96
 msgid "Provides"
-msgstr "Ð\9dадаÑ\94"
+msgstr "Ð\9eзнаки"
 
 #: zypp/Dep.cc:97
 msgid "Prerequires"
@@ -1431,24 +1431,23 @@ msgstr "Неможливо відкрити канал (%s)."
 #: zypp/ExternalProgram.cc:362
 #, c-format, boost-format
 msgid "Can't chroot to '%s' (%s)."
-msgstr "Не вдалося створити chroot для \"%s\" (%s)."
+msgstr "Не вдалося створити chroot для «%s» (%s)."
 
 #: zypp/ExternalProgram.cc:372
 #, c-format, boost-format
 msgid "Can't chdir to '%s' inside chroot '%s' (%s)."
-msgstr ""
-"Неможливо змінити каталог на '%s' всередині середовища chroot '%s' (%s)."
+msgstr "Неможливо зробити chdir «%s» всередині chroot «%s» (%s)."
 
 #: zypp/ExternalProgram.cc:373
 #, c-format, boost-format
 msgid "Can't chdir to '%s' (%s)."
-msgstr "Ð\9dеможливо Ð·Ð¼Ñ\96ниÑ\82и ÐºÐ°Ñ\82алог Ð½Ð° '%s' (%s)."
+msgstr "Ð\9dеможливо Ð·Ð¼Ñ\96ниÑ\82и Ð´Ð¸Ñ\80екÑ\82оÑ\80Ñ\96Ñ\8e Ð½Ð° Â«%s» (%s)."
 
 #. don't want to get here
 #: zypp/ExternalProgram.cc:385
 #, c-format, boost-format
 msgid "Can't exec '%s' (%s)."
-msgstr "Не вдалося виконати \"%s\" (%s)."
+msgstr "Не вдалося виконати «%s» (%s)."
 
 #: zypp/ExternalProgram.cc:393
 #, c-format, boost-format
@@ -1463,7 +1462,7 @@ msgstr "Команда завершила роботу зі станом %d."
 #: zypp/ExternalProgram.cc:542
 #, c-format, boost-format
 msgid "Command was killed by signal %d (%s)."
-msgstr "Ð\9aомандÑ\83 Ð±Ñ\83ло Ð·Ñ\83пинено за сигналом %d (%s)."
+msgstr "Ð\9aомандÑ\83 Ð±Ñ\83ло Ð²Ð±Ð¸Ñ\82о за сигналом %d (%s)."
 
 #: zypp/ExternalProgram.cc:547
 msgid "Command exited with unknown error."
@@ -1486,7 +1485,7 @@ msgstr "Не вдалось вилучити ключ."
 #: zypp/KeyRing.cc:609
 #, c-format, boost-format
 msgid "Signature file %s not found"
-msgstr "Файл підпису \"%s\" не знайдено"
+msgstr "Файл підпису «%s» не знайдено"
 
 #: zypp/LanguageCode.cc:49
 msgid "Unknown language: "
@@ -1590,7 +1589,7 @@ msgstr "Мови апачі"
 #. language code: ara ar
 #: zypp/LanguageCode.cc:201
 msgid "Arabic"
-msgstr "Ð\90Ñ\80абÑ\81Ñ\8cкий"
+msgstr "Ð\90Ñ\80абÑ\81Ñ\8cка"
 
 #. language code: arc
 #: zypp/LanguageCode.cc:203
@@ -1629,7 +1628,7 @@ msgstr "Штучні (інші)"
 #. language code: arw
 #: zypp/LanguageCode.cc:217
 msgid "Arawak"
-msgstr "Аравак"
+msgstr "Аравацька"
 
 #. language code: asm as
 #: zypp/LanguageCode.cc:219
@@ -1659,7 +1658,7 @@ msgstr "Аварська"
 #. language code: ave ae
 #: zypp/LanguageCode.cc:229
 msgid "Avestan"
-msgstr "Авестан"
+msgstr "Авестійська"
 
 # SZ
 # fuzzy
@@ -3956,32 +3955,32 @@ msgstr[2] "(закінчується через %d днів)"
 #: zypp/RepoInfo.cc:517
 #, boost-format
 msgid "Looking for gpg key ID %1% in cache %2%."
-msgstr ""
+msgstr "Пошук ID ключа для gpg %1% у кеші %2%."
 
 #. translator: %1% is a gpg key ID like 3DBDC284
 #. %2% is a repositories name
 #: zypp/RepoInfo.cc:545
 #, boost-format
 msgid "Looking for gpg key ID %1% in repository %2%."
-msgstr ""
+msgstr "Пошук ID ключа для gpg %1% у сховищі %2%."
 
 #. translator: %1% is a repositories name
 #: zypp/RepoInfo.cc:569
 #, boost-format
 msgid "Repository %1% does not define additional 'gpgkey=' URLs."
-msgstr ""
+msgstr "Сховище %1% не визначає додаткові мережеві адреси для «gpgkey=»."
 
 #: zypp/RepoManager.cc:312
 #, boost-format
 msgid "Cannot read repo directory '%1%': Permission denied"
-msgstr "Ð\9dеможливо Ð¿Ñ\80оÑ\87иÑ\82аÑ\82и ÐºÐ°Ñ\82алог Ñ\81Ñ\85овиÑ\89а '%1%': Ð\94оÑ\81Ñ\82Ñ\83п Ð·Ð°Ð±Ð¾Ñ\80онений"
+msgstr "Ð\9dеможливо Ð¿Ñ\80оÑ\87иÑ\82аÑ\82и Ð´Ð¸Ñ\80екÑ\82оÑ\80Ñ\96Ñ\8e Ñ\81Ñ\85овиÑ\89а '%1%': Ð\94оÑ\81Ñ\82Ñ\83п Ð·Ð°Ð±Ð¾Ñ\80онено"
 
 #. TranslatorExplanation '%s' is a pathname
 #: zypp/RepoManager.cc:320 zypp/RepoManager.cc:783 zypp/RepoManager.cc:1536
 #: zypp/repo/PluginServices.cc:49
 #, c-format, boost-format
 msgid "Failed to read directory '%s'"
-msgstr "Ð\9dе Ð²Ð´Ð°Ð»Ð¾Ñ\81Ñ\8c Ð¿Ñ\80оÑ\87иÑ\82аÑ\82и ÐºÐ°Ñ\82алог «%s»"
+msgstr "Ð\9dе Ð²Ð´Ð°Ð»Ð¾Ñ\81Ñ\8c Ð¿Ñ\80оÑ\87иÑ\82аÑ\82и Ð´Ð¸Ñ\80екÑ\82оÑ\80Ñ\96Ñ\8e «%s»"
 
 #: zypp/RepoManager.cc:330
 #, boost-format
@@ -4026,12 +4025,12 @@ msgstr "Неможливо створити %s"
 
 #: zypp/RepoManager.cc:1148
 msgid "Can't create metadata cache directory."
-msgstr "Ð\9dеможливо Ñ\81Ñ\82воÑ\80иÑ\82и ÐºÐ°Ñ\82алог кешу метаданих."
+msgstr "Ð\9dеможливо Ñ\81Ñ\82воÑ\80иÑ\82и Ð´Ð¸Ñ\80екÑ\82оÑ\80Ñ\96Ñ\8e кешу метаданих."
 
 #: zypp/RepoManager.cc:1294
 #, c-format, boost-format
 msgid "Building repository '%s' cache"
-msgstr "Створення кешу сховища \"%s\""
+msgstr "Створення кешу сховища «%s»"
 
 #: zypp/RepoManager.cc:1314
 #, c-format, boost-format
@@ -4067,7 +4066,7 @@ msgstr "Невідома помилка читання з «%s»"
 #: zypp/RepoManager.cc:1618
 #, c-format, boost-format
 msgid "Adding repository '%s'"
-msgstr "Додавання сховища \"%s\""
+msgstr "Додавання сховища «%s»"
 
 #. TranslatorExplanation '%s' is an URL
 #: zypp/RepoManager.cc:1706
@@ -4078,7 +4077,7 @@ msgstr "Не чинна назва файла сховища «%s»"
 #: zypp/RepoManager.cc:1747
 #, c-format, boost-format
 msgid "Removing repository '%s'"
-msgstr "Вилучення сховища \"%s\""
+msgstr "Вилучення сховища «%s»"
 
 #: zypp/RepoManager.cc:1766 zypp/RepoManager.cc:1844
 msgid "Can't figure out where the repo is stored."
@@ -4101,7 +4100,7 @@ msgstr "Не чинний рядок запиту URL LDAP"
 #: zypp/Url.cc:153
 #, c-format, boost-format
 msgid "Invalid LDAP URL query parameter '%s'"
-msgstr "Не чинний параметр запиту \"%s\" URL LDAP"
+msgstr "Не чинний параметр запиту «%s» URL LDAP"
 
 #: zypp/Url.cc:301
 msgid "Unable to clone Url object"
@@ -4141,7 +4140,7 @@ msgstr "Потрібний додатковий контракт клієнта"
 
 #: zypp/VendorSupportOptions.cc:31
 msgid "invalid"
-msgstr "нечинний"
+msgstr "недійсний"
 
 #: zypp/VendorSupportOptions.cc:39
 msgid "The level of support is unspecified"
@@ -4218,24 +4217,26 @@ msgstr "Невідомий режим збігу «%s» для шаблона «
 #: zypp/base/StrMatcher.cc:157
 #, c-format, boost-format
 msgid "Invalid regular expression '%s': regcomp returned %d"
-msgstr "Некоректний формальний вираз \"%s\": regcomp повернула %d"
+msgstr "Некоректний формальний вираз «%s»: regcomp повернула %d"
 
 #: zypp/base/StrMatcher.cc:158
 #, c-format, boost-format
 msgid "Invalid regular expression '%s'"
-msgstr "Нечинний формальний вираз «%s»"
+msgstr "Недійсний формальний вираз «%s»"
 
 #. !\todo add comma to the message for the next release
 #: zypp/media/MediaCIFS.cc:427 zypp/media/MediaCurl.cc:1738
 #, c-format, boost-format
 msgid "Authentication required for '%s'"
-msgstr "Потрібна автентифікація для \"%s\""
+msgstr "Потрібна автентифікація для «%s»"
 
 #: zypp/media/MediaCurl.cc:1111
 msgid ""
 "Visit the SUSE Customer Center to check whether your registration is valid "
 "and has not expired."
 msgstr ""
+"Відвідайте Центр клієнтів SUSE, щоб перевірити, чи ваша реєстрація дійсна та "
+"чи не закінчився термін дії."
 
 #: zypp/media/MediaCurl.cc:1113
 msgid ""
@@ -4262,17 +4263,17 @@ msgstr "Некоректна назва файла: %s"
 #: zypp/media/MediaException.cc:53
 #, c-format, boost-format
 msgid "Medium not opened when trying to perform action '%s'."
-msgstr "Носій не було відкрито під час спроби виконання дії \"%s\"."
+msgstr "Носій не було відкрито під час спроби виконання дії «%s»."
 
 #: zypp/media/MediaException.cc:58
 #, c-format, boost-format
 msgid "File '%s' not found on medium '%s'"
-msgstr "Файл \"%s\" не знайдено на носії \"%s\""
+msgstr "Файл «%s» не знайдено на носії «%s»"
 
 #: zypp/media/MediaException.cc:63
 #, c-format, boost-format
 msgid "Cannot write file '%s'."
-msgstr "Неможливо записати у файл \"%s\"."
+msgstr "Неможливо записати у файл «%s»."
 
 #: zypp/media/MediaException.cc:68
 msgid "Medium not attached"
@@ -4286,22 +4287,22 @@ msgstr "Некоректна точка з’єднання носія"
 #: zypp/media/MediaException.cc:79
 #, c-format, boost-format
 msgid "Download (curl) initialization failed for '%s'"
-msgstr "Започаткування завантаження (curl) зазнало невдачі для \"%s\""
+msgstr "Започаткування завантаження (curl) зазнало невдачі для «%s»"
 
 #: zypp/media/MediaException.cc:84
 #, c-format, boost-format
 msgid "System exception '%s' on medium '%s'."
-msgstr "Системне виключення \"%s\" на носії \"%s\"."
+msgstr "Системне виключення «%s» на носії «%s»."
 
 #: zypp/media/MediaException.cc:89
 #, c-format, boost-format
 msgid "Path '%s' on medium '%s' is not a file."
-msgstr "Шлях \"%s\" на носії \"%s\" не є файлом."
+msgstr "Шлях «%s» на носії «%s» не є файлом."
 
 #: zypp/media/MediaException.cc:94
 #, c-format, boost-format
 msgid "Path '%s' on medium '%s' is not a directory."
-msgstr "Шлях \"%s\" на носії \"%s\" не є каталогом."
+msgstr "Шлях «%s» на носії «%s» не є директорією."
 
 #: zypp/media/MediaException.cc:101
 msgid "Malformed URI"
@@ -4322,7 +4323,7 @@ msgstr "Порожнє місце призначення за URI"
 #: zypp/media/MediaException.cc:126
 #, c-format, boost-format
 msgid "Unsupported URI scheme in '%s'."
-msgstr "Непідтримувана схема URI у \"%s\"."
+msgstr "Непідтримувана схема URI у «%s»."
 
 #: zypp/media/MediaException.cc:131
 msgid "Operation not supported by medium"
@@ -4335,7 +4336,7 @@ msgid ""
 "Error code: %s\n"
 "Error message: %s\n"
 msgstr ""
-"Помилка завантаження (curl) для \"%s\":\n"
+"Помилка завантаження (curl) для «%s»:\n"
 "Код помилки: %s\n"
 "Повідомлення про помилку: %s\n"
 
@@ -4344,18 +4345,17 @@ msgstr ""
 #, c-format, boost-format
 msgid "Error occurred while setting download (curl) options for '%s':"
 msgstr ""
-"Під час встановлення параметрів завантаження (curl) для \"%s\" сталася "
-"помилка:"
+"Під час встановлення параметрів завантаження (curl) для «%s» сталася помилка:"
 
 #: zypp/media/MediaException.cc:153
 #, c-format, boost-format
 msgid "Media source '%s' does not contain the desired medium"
-msgstr "Пристрій носія \"%s\" не містить бажаного носія"
+msgstr "Пристрій носія «%s» не містить бажаного носія"
 
 #: zypp/media/MediaException.cc:158
 #, c-format, boost-format
 msgid "Medium '%s' is in use by another instance"
-msgstr "Носій \"%s\" використовується іншою копією програми"
+msgstr "Носій «%s» використовується іншою копією програми"
 
 #: zypp/media/MediaException.cc:164
 msgid "Cannot eject any media"
@@ -4364,22 +4364,22 @@ msgstr "Не вдається виштовхнути жодного носія"
 #: zypp/media/MediaException.cc:166
 #, c-format, boost-format
 msgid "Cannot eject media '%s'"
-msgstr "Не вдається виштовхнути носія \"%s\""
+msgstr "Не вдається виштовхнути носія «%s»"
 
 #: zypp/media/MediaException.cc:181
 #, c-format, boost-format
 msgid "Permission to access '%s' denied."
-msgstr "Дозвіл на доступ до \"%s\" заборонено."
+msgstr "Дозвіл на доступ до «%s» заборонено."
 
 #: zypp/media/MediaException.cc:189
 #, c-format, boost-format
 msgid "Timeout exceeded when accessing '%s'."
-msgstr "Перевищено час очікування при спробі доступу до '%s'."
+msgstr "Перевищено час очікування при спробі доступу до «%s»."
 
 #: zypp/media/MediaException.cc:197
 #, c-format, boost-format
 msgid "Downloaded data exceeded the expected filesize '%s' of '%s'."
-msgstr ""
+msgstr "Завантажені дані перевищили очікуваний розмір файлу «%s» у «%s»."
 
 #: zypp/media/MediaException.cc:205
 #, c-format, boost-format
@@ -4391,20 +4391,20 @@ msgstr "Адреса «%s» тимчасово недоступна."
 msgid " SSL certificate problem, verify that the CA cert is OK for '%s'."
 msgstr ""
 " Проблема з сертифікатом SSL, перевірте чи все гаразд з сертифікатом CA для "
-"\"%s\"."
+"«%s»."
 
 #: zypp/media/MediaHandler.cc:370
 msgid ""
 "Create attach point: Can't find a writable directory to create an attach "
 "point"
 msgstr ""
-"СÑ\82воÑ\80Ñ\8eÑ\94мо Ñ\82оÑ\87кÑ\83 Ð¼Ð¾Ð½Ñ\82Ñ\83ваннÑ\8f: Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ Ð·Ð½Ð°Ð¹Ñ\82и ÐºÐ°Ñ\82алог Ð· Ð¼Ð¾Ð¶Ð»Ð¸Ð²Ñ\96Ñ\81Ñ\82Ñ\8e Ð·Ð°Ð¿Ð¸Ñ\81Ñ\83 Ð´Ð»Ñ\8f "
-"створення точки монтування"
+"СÑ\82воÑ\80Ñ\8eÑ\94мо Ñ\82оÑ\87кÑ\83 Ð¼Ð¾Ð½Ñ\82Ñ\83ваннÑ\8f: Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ Ð·Ð½Ð°Ð¹Ñ\82и Ð´Ð¸Ñ\80екÑ\82оÑ\80Ñ\96Ñ\8e Ð· Ð¼Ð¾Ð¶Ð»Ð¸Ð²Ñ\96Ñ\81Ñ\82Ñ\8e Ð·Ð°Ð¿Ð¸Ñ\81Ñ\83 "
+"для створення точки монтування"
 
 #: zypp/media/MediaUserAuth.cc:136
 #, c-format, boost-format
 msgid "Unsupported HTTP authentication method '%s'"
-msgstr "Метод автентифікації HTTP \"%s\", який не підтримується"
+msgstr "Метод автентифікації HTTP «%s», який не підтримується"
 
 #: zypp/misc/CheckAccessDeleted.cc:444
 msgid "Please install package 'lsof' first."
@@ -4454,7 +4454,7 @@ msgstr "Модуль служби не підтримує зміну атриб
 #: zypp/repo/RepoProvideFile.cc:261
 #, c-format, boost-format
 msgid "Can't provide file '%s' from repository '%s'"
-msgstr "Неможливо надати файл \"%s\" зі сховища \"%s\""
+msgstr "Неможливо надати файл «%s» зі сховища «%s»"
 
 #: zypp/repo/RepoProvideFile.cc:267
 msgid "No url in repository."
@@ -4795,19 +4795,19 @@ msgstr "Встановити %s з невключеного сховища"
 #: zypp/solver/detail/SATResolver.cc:1386
 #, boost-format
 msgid "install %1% although it has been retracted"
-msgstr ""
+msgstr "встановити %1% хоч він і був відкладений"
 
 #. translator: %1% is a package name
 #: zypp/solver/detail/SATResolver.cc:1389
 #, boost-format
 msgid "allow to install the PTF %1%"
-msgstr ""
+msgstr "дозволити встановити PTF %1%"
 
 #. translator: %1% is a package name
 #: zypp/solver/detail/SATResolver.cc:1392
 #, boost-format
 msgid "install %1% although it is blacklisted"
-msgstr ""
+msgstr "встановити %1% хоч він і був у чорному списку"
 
 #: zypp/solver/detail/SATResolver.cc:1412
 #, c-format, boost-format
@@ -5000,7 +5000,7 @@ msgstr "Схема Url не дозволяє %s"
 #: zypp/url/UrlBase.cc:173
 #, c-format, boost-format
 msgid "Invalid %s component '%s'"
-msgstr "Не чинний %s компонент \"%s\""
+msgstr "Не чинний %s компонент «%s»"
 
 #: zypp/url/UrlBase.cc:180
 #, c-format, boost-format
@@ -5018,7 +5018,7 @@ msgstr "Схема URL - це потрібний компонент"
 #: zypp/url/UrlBase.cc:830
 #, c-format, boost-format
 msgid "Invalid Url scheme '%s'"
-msgstr "Не чинна схема URL \"%s\""
+msgstr "Не чинна схема URL «%s»"
 
 #: zypp/url/UrlBase.cc:949
 msgid "Url scheme does not allow a username"
@@ -5039,7 +5039,7 @@ msgstr "Схема URL не дозволяє компонент вузла"
 #: zypp/url/UrlBase.cc:1049
 #, c-format, boost-format
 msgid "Invalid host component '%s'"
-msgstr "Не чинний компонент вузла \"%s\""
+msgstr "Не чинний компонент вузла «%s»"
 
 #: zypp/url/UrlBase.cc:1070
 msgid "Url scheme does not allow a port"
@@ -5048,7 +5048,7 @@ msgstr "Схема URL не дозволяє порт"
 #: zypp/url/UrlBase.cc:1081
 #, c-format, boost-format
 msgid "Invalid port component '%s'"
-msgstr "Не чинний компонент порту \"%s\""
+msgstr "Не чинний компонент порту «%s»"
 
 #: zypp/url/UrlBase.cc:1098
 msgid "Url scheme requires path name"
index df6ff01..f61a5fe 100644 (file)
@@ -24,7 +24,13 @@ namespace  {
     return pck.name() + "-" + pck.edition().asString() + "." + pck.arch().asString();
   }
 
-  using TestSample = std::tuple<Pathname, std::string, zypp::Arch, std::string, std::map<std::string, bool> >;
+  using TestSample = std::tuple<
+    Pathname,    // repoPath
+    std::string, // uname_r
+    zypp::Arch,  // arch
+    std::string, // keepSpec
+    std::map<std::string, bool> // expectedRems
+    >;
 
   std::vector<TestSample>  maketestdata() {
     return {
@@ -48,9 +54,9 @@ namespace  {
           // left over devel packages that need to go away too
           { "kernel-devel-1-1.2.noarch", false },
           { "kernel-source-1-1.2.noarch", false },
-          { "kernel-default-devel-1-3.2.x86_64", false },
-          { "kernel-devel-1-3.2.noarch", false },
-          { "kernel-source-1-3.2.noarch", false },
+          { "kernel-default-devel-1-3.x86_64", false },
+          { "kernel-default-devel-debuginfo-1-3.x86_64", false },
+          { "kernel-devel-1-3.noarch", false },
         }
       },
       //test that keeps only the running kernel
@@ -87,9 +93,10 @@ namespace  {
           // left over devel packages that need to go away too
           { "kernel-devel-1-1.2.noarch", false },
           { "kernel-source-1-1.2.noarch", false },
-          { "kernel-default-devel-1-3.2.x86_64", false },
-          { "kernel-devel-1-3.2.noarch", false },
-          { "kernel-source-1-3.2.noarch", false },
+          { "kernel-default-devel-1-3.x86_64", false },
+          { "kernel-devel-1-3.noarch", false },
+          { "kernel-default-devel-1-3.x86_64", false },
+          { "kernel-default-devel-debuginfo-1-3.x86_64", false },
         }
       },
       TestSample {
@@ -112,11 +119,9 @@ namespace  {
           { "kernel-syms-1-5.x86_64", false },
           { "dummy-kmp-default-1-0.x86_64", false },
           // left over devel packages that need to go away too
-          { "kernel-devel-1-1.2.noarch", false },
-          { "kernel-source-1-1.2.noarch", false },
-          { "kernel-default-devel-1-3.2.x86_64", false },
-          { "kernel-devel-1-3.2.noarch", false },
-          { "kernel-source-1-3.2.noarch", false },
+          { "kernel-default-devel-1-3.x86_64", false },
+          { "kernel-default-devel-debuginfo-1-3.x86_64", false },
+          { "kernel-devel-1-3.noarch", false },
         }
       },
       TestSample {
@@ -132,6 +137,11 @@ namespace  {
           { "kernel-devel-1-2.noarch", false },
           { "kernel-livepatch-default-1-2.x86_64", false },
           { "kernel-syms-1-2.x86_64", false },
+          // the following packages are not held back because they do not fit keep spec and no deps are keeping them
+          { "kernel-default-devel-1-1.x86_64", false },
+          { "kernel-default-devel-debuginfo-1-1.x86_64", false },
+          { "kernel-devel-1-1.noarch", false},
+          { "kernel-syms-1-1.x86_64", false},
         }
       },
       TestSample {
@@ -147,6 +157,10 @@ namespace  {
           { "kernel-devel-1-2.noarch", false },
           { "kernel-livepatch-default-1-2.x86_64", false },
           { "kernel-syms-1-2.x86_64", false },
+          { "kernel-default-devel-1-5.x86_64", false },
+          { "kernel-default-devel-debuginfo-1-5.x86_64", false },
+          { "kernel-devel-1-5.noarch", false },
+          { "kernel-syms-1-5.x86_64", false },
         }
       },
       TestSample {
@@ -157,6 +171,10 @@ namespace  {
         Arch("x86_64"),
         "running,1-2",
         {
+          { "kernel-default-devel-1-5.x86_64", false },
+          { "kernel-default-devel-debuginfo-1-5.x86_64", false },
+          { "kernel-devel-1-5.noarch", false },
+          { "kernel-syms-1-5.x86_64", false },
         }
       },
       TestSample {
@@ -170,11 +188,9 @@ namespace  {
           { "kernel-default-1-1.aarch64", false },
           { "kernel-default-1-1.i686", false },
 
-          /*
-          { "kernel-default-devel-1-1.x86_64", false },
-          { "kernel-default-devel-debuginfo-1-1.x86_64", false },
-          { "kernel-syms-1-1.x86_64", false },
-          */
+          //{ "kernel-syms-1-1.x86_64", false },
+          //{ "kernel-default-devel-1-1.x86_64", false },
+          //{ "kernel-default-devel-debuginfo-1-1.x86_64", false },
 
           { "kernel-default-1-2.aarch64", false },
           { "kernel-default-1-2.i686", false },
@@ -198,17 +214,23 @@ namespace  {
           { "kernel-livepatch-default-1-2.i686", false },
           { "kernel-livepatch-default-1-2.x86_64", false },
 
-          /*
-           Since kernel-syms package requires do not care about architecture, every kernel-syms
-           package for flavour-version will be kept as long as a kernel-flavour-devel package of any arch is installed
           { "kernel-syms-1-1.aarch64", false },
           { "kernel-syms-1-1.i686", false },
-          */
+
           { "kernel-syms-1-2.aarch64", false },
           { "kernel-syms-1-2.i686", false },
           { "kernel-syms-1-2.x86_64", false },
         }
       },
+      TestSample {
+        TESTS_SRC_DIR"/zypp/data/PurgeKernels/rebuild",
+        "1-1-default",
+        Arch("x86_64"),
+        "running",
+        {
+          { "kernel-source-1-1.noarch", false },
+        }
+      }
     };
   }
 }
@@ -237,7 +259,7 @@ BOOST_DATA_TEST_CASE(purge_kernels, bdata::make( maketestdata() ), repoPath, una
     removeCount++;
 
     auto pck = expectedRemovals.find( makeNVRA(*it) );
-    BOOST_REQUIRE_MESSAGE(  pck != expectedRemovals.end(), std::string("Unexpected package removed: ") + makeNVRA(*it) );
+    BOOST_REQUIRE_MESSAGE(  pck != expectedRemovals.end(), std::string("Unexpected package removed: ") + makeNVRA(*it) + (it->status().isByUser() ? " (by user)" : " (autoremoved)") );
 
     pck->second = true;
   }
index 5544e7d..731cd35 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <zypp/base/LogTools.h>
 #include <zypp/base/String.h>
+#include <zypp/base/Regex.h>
 
 using boost::unit_test::test_suite;
 using boost::unit_test::test_case;
@@ -395,3 +396,58 @@ BOOST_AUTO_TEST_CASE(hexencode_hexdecode)
      }
     }
 }
+
+BOOST_AUTO_TEST_CASE(regex_subst)
+{
+  const std::string input = "StRi_g: Hello this is a StRiNg with multiple Stri_g mentions to replace StRi_g";
+  str::regex rxexpr( "(St[rR]i[nN_]g)" );
+  BOOST_REQUIRE_EQUAL( rxexpr.get()->re_nsub, 1 );
+
+  {
+    const std::string result = str::regex_substitute( input, rxexpr, "FooBar", true );
+    BOOST_REQUIRE_EQUAL( result, "FooBar: Hello this is a FooBar with multiple FooBar mentions to replace FooBar"  );
+  }
+
+  {
+    const std::string result = str::regex_substitute( input, rxexpr, "FooBar", false );
+    BOOST_REQUIRE_EQUAL( result, "FooBar: Hello this is a StRiNg with multiple Stri_g mentions to replace StRi_g"  );
+  }
+
+  {
+    const std::string nomatch = "Text with no match";
+    const std::string result = str::regex_substitute( nomatch , rxexpr, "FooBar", false );
+    BOOST_REQUIRE_EQUAL( result, nomatch  );
+  }
+
+  {
+    const std::string input = "StRi_gString: Hello this is a StRiNg with multiple Stri_g mentions to replace StRi_g";
+    str::regex rxexpr( "(^St[rR]i[nN_]g)" );
+    const std::string result = str::regex_substitute( input, rxexpr, "FooBar", true );
+    BOOST_REQUIRE_EQUAL( result, "FooBarString: Hello this is a StRiNg with multiple Stri_g mentions to replace StRi_g"  );
+  }
+
+  {
+    const std::string input = "StRi_gString\n: Hello this is a StRiNg with multiple Stri_g mentions to replace StRi_g";
+    str::regex rxexpr( "(String$)", str::regex::match_extended | str::regex::newline );
+    const std::string result = str::regex_substitute( input, rxexpr, "FooBar", true );
+    BOOST_REQUIRE_EQUAL( result, "StRi_gFooBar\n: Hello this is a StRiNg with multiple Stri_g mentions to replace StRi_g"  );
+  }
+
+  {
+    const std::string input = "Hello StRi_gString\n: Hello.";
+    str::regex rxexpr( "((^: Hello)|(String\n))", str::regex::match_extended);
+    const std::string result = str::regex_substitute( input, rxexpr, " FooBar", true );
+    BOOST_REQUIRE_EQUAL( result, "Hello StRi_g FooBar FooBar."  );
+  }
+
+  {
+    const std::string input = "4.12.14-lp151.28.48-default";
+    const std::string flavour = str::regex_substitute( input, str::regex( ".*-", str::regex::match_extended ), "", true );
+    BOOST_REQUIRE_EQUAL( flavour, "default"  );
+    std::string version = str::regex_substitute( input, str::regex( "-[^-]*$", str::regex::match_extended ), "", true );
+    BOOST_REQUIRE_EQUAL( version, "4.12.14-lp151.28.48"  );
+    const std::string release = str::regex_substitute( version, str::regex( ".*-", str::regex::match_extended ), "", true );
+    BOOST_REQUIRE_EQUAL( release, "lp151.28.48"  );
+  }
+
+}
diff --git a/tests/zypp/data/PurgeKernels/rebuild/solver-system.xml b/tests/zypp/data/PurgeKernels/rebuild/solver-system.xml
new file mode 100644 (file)
index 0000000..8a2e0f7
--- /dev/null
@@ -0,0 +1,173 @@
+<channel><subchannel>
+<package>
+       <name>glibc</name>
+       <vendor>openSUSE</vendor>
+       <history>
+           <update>
+                   <arch>x86_64</arch>
+                   <version>1</version><release>1</release>
+           </update>
+       </history>
+       <provides>
+               <dep name='glibc' op='==' version='1' release='1' />
+       </provides>
+</package>
+
+<package>
+       <name>kernel-firmware</name>
+    <provides>
+        <dep name='kernel-firmware' op='==' version='20190312' release='lp151.2.3.1' />
+    </provides>
+</package>
+
+<package>
+       <name>kernel-macros</name>
+       <vendor>openSUSE</vendor>
+       <history>
+       <update>
+               <arch>noarch</arch>
+               <version>1</version><release>0</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='kernel-subpackage-macros' />
+               <dep name='kernel-macros' op='==' version='1' release='0' />
+       </provides>
+</package>
+
+<!-- START KERNEL SET -->
+<package>
+       <name>kernel-default</name>
+       <vendor>openSUSE</vendor>
+       <history>
+       <update>
+               <arch>x86_64</arch>
+               <version>1</version><release>1.2</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='multiversion(kernel)' />
+               <dep name='kernel-default-1-1' />
+               <dep name='kernel' op='==' version='1' release='1' />
+               <dep name='kernel-default' op='==' version='1' release='1' />
+               <dep name='kernel-default' op='==' version='1' release='1.2' />
+               <dep name='kernel-uname-r' op='==' version='1-1' release='default' />
+       </provides>
+       <recommends>
+               <dep name='kernel-firmware' />
+       </recommends>
+</package>
+
+<package>
+       <name>kernel-default-devel</name>
+       <vendor>openSUSE</vendor>
+       <history>
+       <update>
+               <arch>x86_64</arch>
+               <version>1</version><release>1.2</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='multiversion(kernel)' />
+               <dep name='kernel-default-devel' op='==' version='1' release='1.2' />
+       </provides>
+       <requires>
+               <dep name='kernel-devel' op='==' version='1' release='1.2' />
+       </requires>
+       <supplements>
+               <dep name='packageand(kernel-default:kernel-devel)' />
+       </supplements>
+</package>
+
+<package>
+       <name>kernel-default-devel-debuginfo</name>
+       <vendor>openSUSE</vendor>
+       <history>
+       <update>
+               <arch>x86_64</arch>
+               <version>1</version><release>1.2</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='kernel-default-devel-debuginfo' op='==' version='1' release='1.2' />
+       </provides>
+</package>
+
+<package>
+       <name>kernel-devel</name>
+       <vendor>openSUSE</vendor>
+       <history>
+       <update>
+               <arch>noarch</arch>
+               <version>1</version><release>1</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='multiversion(kernel)' />
+               <dep name='kernel-devel' op='==' version='1' release='1' />
+               <dep name='kernel-devel' op='==' version='1' release='1.2' />
+       </provides>
+       <requires>
+               <dep name='kernel-macros' />
+       </requires>
+</package>
+
+<package>
+       <name>kernel-source</name>
+       <vendor>openSUSE</vendor>
+       <history>
+       <update>
+               <arch>noarch</arch>
+               <version>1</version><release>1</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='multiversion(kernel)' />
+               <dep name='kernel-source' op='==' version='1' release='1' />
+       </provides>
+       <requires>
+               <dep name='kernel-devel' op='==' version='1' release='1' />
+       </requires>
+</package>
+
+
+<package>
+       <name>kernel-source</name>
+       <vendor>openSUSE</vendor>
+       <history>
+       <update>
+               <arch>noarch</arch>
+               <version>1</version><release>1.2</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='multiversion(kernel)' />
+               <dep name='kernel-source' op='==' version='1' release='1.2' />
+       </provides>
+       <requires>
+               <dep name='kernel-devel' op='==' version='1' release='1.2' />
+       </requires>
+</package>
+
+<package>
+       <name>kernel-syms</name>
+       <vendor>openSUSE</vendor>
+       <buildtime>1570603549</buildtime>
+       <history>
+       <update>
+               <arch>x86_64</arch>
+               <version>1</version><release>1</release>
+       </update>
+       </history>
+       <provides>
+               <dep name='multiversion(kernel)' />
+               <dep name='kernel-syms' op='==' version='1' release='1' />
+       </provides>
+       <requires>
+               <dep name='kernel-devel' op='==' version='1' release='1' />
+               <dep name='kernel-default-devel' op='==' version='1' release='1' />
+       </requires>
+</package>
+<!-- END KERNEL SET -->
+
+</channel></subchannel>
diff --git a/tests/zypp/data/PurgeKernels/rebuild/solver-test.xml b/tests/zypp/data/PurgeKernels/rebuild/solver-test.xml
new file mode 100644 (file)
index 0000000..993d537
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<test>
+<setup arch="x86_64">
+       <system file="solver-system.xml"/>
+       <locale name="en_US" />
+       <locale name="de" />
+</setup>
+</test>
index 26d2f0a..8728c74 100644 (file)
        </update>
        </history>
        <provides>
+               <dep name='multiversion(kernel)' />
                <dep name='kernel-source' op='==' version='1' release='1' />
        </provides>
        <requires>
         </history>
         <provides>
                 <dep name='multiversion(kernel)' />
+                               <dep name='kernel-devel' op='==' version='1' release='1' />
                 <dep name='kernel-devel' op='==' version='1' release='1.2' />
         </provides>
         <requires>
         </update>
         </history>
         <provides>
+                               <dep name='multiversion(kernel)' />
+                               <dep name='kernel-source' op='==' version='1' release='1' />
                 <dep name='kernel-source' op='==' version='1' release='1.2' />
         </provides>
         <requires>
         </history>
         <provides>
                 <dep name='multiversion(kernel)' />
+                               <dep name='kernel-default-devel' op='==' version='1' release='3' />
                 <dep name='kernel-default-devel' op='==' version='1' release='3.2' />
         </provides>
         <requires>
         </history>
         <provides>
                 <dep name='multiversion(kernel)' />
+                               <dep name='kernel-devel' op='==' version='1' release='3' />
                 <dep name='kernel-devel' op='==' version='1' release='3.2' />
         </provides>
         <requires>
         </update>
         </history>
         <provides>
+                               <dep name='multiversion(kernel)' />
+                               <dep name='kernel-source' op='==' version='1' release='3' />
                 <dep name='kernel-source' op='==' version='1' release='3.2' />
         </provides>
         <requires>
        </provides>
        <requires>
                <dep name='kernel-default-devel'/>
-    <dep name='ksym(foobar)' op='==' version='abdcfee' />
+        <dep name='ksym(foobar)' op='==' version='abdcfee' />
        </requires>
 </package>
 
index 6e30db8..08b8017 100644 (file)
        </provides>
        <requires>
                <dep name='kernel-default-devel'/>
-    <dep name='ksym(foobar)' op='==' version='abdcfee' />
+       <dep name='ksym(foobar)' op='==' version='abdcfee' />
        </requires>
 </package>
 
                <dep name='package-needing-krnlmodule' op='==' version='1' release='0' />
        </provides>
        <requires>
-       <dep name='dummy-kmp-default' op='==' version='1' release='0' />
+               <dep name='dummy-kmp-default' op='==' version='1' release='0' />
        </requires>
 </package>
 
index f630869..95025eb 100644 (file)
@@ -3,6 +3,7 @@
 #include <zypp/zyppng/media/network/networkrequesterror.h>
 #include <zypp/zyppng/media/network/networkrequestdispatcher.h>
 #include <zypp/zyppng/media/network/request.h>
+#include <zypp/media/CredentialManager.h>
 #include <zypp/TmpPath.h>
 #include <zypp/PathInfo.h>
 #include <zypp/ZConfig.h>
@@ -571,3 +572,66 @@ BOOST_DATA_TEST_CASE( dltest_auth, bdata::make( withSSL ), withSSL )
     BOOST_REQUIRE( !gotAuthRequest );
   }
 }
+
+/**
+ * Test for bsc#1174011 auth=basic ignored in some cases
+ *
+ * If the URL specifes ?auth=basic libzypp should proactively send credentials we have available in the cred store
+ */
+BOOST_DATA_TEST_CASE( dltest_auth_basic, bdata::make( withSSL ), withSSL )
+{
+  //don't write or read creds from real settings dir
+  zypp::filesystem::TmpDir repoManagerRoot;
+  zypp::ZConfig::instance().setRepoManagerRoot( repoManagerRoot.path() );
+
+  auto ev = zyppng::EventDispatcher::createMain();
+
+  zyppng::Downloader downloader;
+
+  WebServer web((zypp::Pathname(TESTS_SRC_DIR)/"/zyppng/data/downloader").c_str(), 10001, withSSL );
+  BOOST_REQUIRE( web.start() );
+
+  zypp::filesystem::TmpFile targetFile;
+  zyppng::Url weburl (web.url());
+  weburl.setPathName("/handler/test.txt");
+  weburl.setQueryParam("auth", "basic");
+  weburl.setUsername("test");
+
+  // make sure the creds are already available
+  zypp::media::CredentialManager cm( repoManagerRoot.path() );
+  zypp::media::AuthData data ("test", "test");
+  data.setUrl( weburl );
+  cm.addCred( data );
+  cm.save();
+
+  zyppng::TransferSettings set = web.transferSettings();
+
+  web.addRequestHandler( "test.txt", createAuthHandler() );
+  web.addRequestHandler( "quit", [ &ev ]( WebServer::Request & ){ ev->quit();} );
+
+  {
+    // simply check by request count if the test was successfull:
+    // if the proactive code adding the credentials to the first request is not executed we will
+    // have more than 1 request.
+    int reqCount = 0;
+    auto dispatcher = downloader.requestDispatcher();
+    dispatcher->sigDownloadStarted().connect([&]( zyppng::NetworkRequestDispatcher &, zyppng::NetworkRequest & ){
+      reqCount++;
+    });
+
+
+    auto dl = downloader.downloadFile( weburl, targetFile.path() );
+    dl->setMultiPartHandlingEnabled( false );
+
+    dl->settings() = set;
+
+    dl->sigFinished( ).connect([ &ev ]( zyppng::Download & ){
+      ev->quit();
+    });
+
+    dl->start();
+    ev->run();
+    BOOST_TEST_REQ_SUCCESS( dl );
+    BOOST_REQUIRE_EQUAL( reqCount, 1 );
+  }
+}
index 0736cfc..dd8e919 100644 (file)
@@ -13,6 +13,7 @@
 #include <zypp/base/String.h>
 #include <zypp/base/Logger.h>
 #include <zypp/base/Regex.h>
+#include <zypp/base/Iterator.h>
 #include <zypp/ui/Selectable.h>
 #include <zypp/PurgeKernels.h>
 #include <zypp/PoolQuery.h>
 #include <map>
 #include <unordered_map>
 #include <sys/utsname.h>
+#include <functional>
+#include <array>
 
 #undef ZYPP_BASE_LOGGER_LOGGROUP
 #define ZYPP_BASE_LOGGER_LOGGROUP "PurgeKernels"
 
 namespace zypp {
 
+  using Flavour                = std::string;
+  using SolvableList           = std::list<sat::Solvable::IdType>;
+  using EditionToSolvableMap   = std::map<Edition, SolvableList >;
+  using ArchToEditionMap       = std::map<Arch, EditionToSolvableMap >;
+
+  struct GroupInfo {
+
+    enum GroupType {
+      None,             //<< Just here to support default construction
+      Kernels,          //<< Map contains kernel packages, so need to receive special handling and flavour matching
+      RelatedBinaries,  //<< Map contains related binary packages, so need to receive special handling and flavour matching
+      Sources           //<< Map contains source packages, so when matching those against running we ignore the flavour
+    } groupType;
+
+    GroupInfo( const GroupType type = None, std::string flav = "") : groupType(type), groupFlavour( std::move(flav) ) { }
+
+    ArchToEditionMap archToEdMap;   //<< Map of actual packages
+    std::string groupFlavour;       //<< This would contain a specific flavour if there is one calculated
+  };
+  using GroupMap = std::unordered_map<std::string, GroupInfo>;
+
   struct PurgeKernels::Impl  {
 
     Impl() {
       struct utsname unameData;
       if ( uname( &unameData) == 0 ) {
-        _kernelArch = Arch( unameData.machine );
-        _uname_r = std::string( unameData.release );
+
+        const auto archStr = str::regex_substitute( unameData.machine, str::regex( "^i.86$", str::regex::match_extended ), "i586" );
+
+        _kernelArch = Arch( archStr );
+        setUnameR( std::string( unameData.release ) );
+
+        _detectedRunning = true;
+
+        MIL << "Detected running kernel: " << _runningKernelEdition << " " << _runningKernelFlavour << " " << _kernelArch << std::endl;
+
+      } else {
+        MIL << "Failed to detect running kernel: " << errno << std::endl;
       }
     }
 
-    bool removePackageAndCheck( PoolItem &item, const str::regex &validRemovals ) const;
-    void parseKeepSpec();
-    void fillKeepList( const std::unordered_map< std::string, std::map< Arch, std::map<Edition, sat::Solvable> > > &installedKernels, std::set<sat::Solvable::IdType> &list ) const;
-    void cleanDevelAndSrcPackages ( const str::regex &validRemovals, std::set<Edition> &validEditions, const std::string &flavour = std::string() );
+    void setUnameR ( const std::string &uname ) {
 
-    static std::string detectRunningKernel() {
+      _uname_r = uname;
 
-      std::string kernelVersion;
-      std::ifstream procKernel( "/proc/sys/kernel/osrelease" );
-      if ( procKernel ) {
-        std::getline( procKernel, kernelVersion );
-      }
-      return kernelVersion;
+      MIL << "Set uname " << uname << std::endl;
+
+      const std::string flavour = str::regex_substitute( _uname_r, str::regex( ".*-", str::regex::match_extended ), "", true );
+      std::string version = str::regex_substitute( _uname_r, str::regex( "-[^-]*$", str::regex::match_extended | str::regex::newline ), "", true );
 
+      const std::string release = str::regex_substitute( version, str::regex( ".*-", str::regex::match_extended ), "", true );
+
+      version = str::regex_substitute( version, str::regex( "-[^-]*$", str::regex::match_extended | str::regex::newline ), "", true );
+
+      // from purge-kernels script, was copied from kernel-source/rpm/mkspec
+      version = str::regex_substitute( version, str::regex( "\\.0-rc", str::regex::match_extended ), ".rc", true );
+      version = str::regex_substitute( version, str::regex( "-rc\\d+", str::regex::match_extended ), "", true );
+      version = str::regex_substitute( version, str::regex( "-", str::regex::match_extended ), ".", true );
+
+      _runningKernelEdition = Edition( version, release );
+      _runningKernelFlavour = flavour;
+
+      MIL << "Parsed info from uname: " << std::endl;
+      MIL << "Kernel Flavour: " << _runningKernelFlavour << std::endl;
+      MIL << "Kernel Edition: " << _runningKernelEdition << std::endl;
     }
 
+    bool removePackageAndCheck( const sat::Solvable::IdType id, const std::set<sat::Solvable::IdType> &keepList , const std::set<sat::Solvable::IdType> &removeList ) const;
+    static bool versionMatch ( const Edition &a, const Edition &b );
+    void parseKeepSpec();
+    void fillKeepList(const GroupMap &installedKernels, std::set<sat::Solvable::IdType> &keepList , std::set<sat::Solvable::IdType> &removeList ) const;
 
     std::set<size_t>  _keepLatestOffsets = { 0 };
     std::set<size_t>  _keepOldestOffsets;
     std::set<Edition> _keepSpecificEditions;
     std::string       _uname_r;
+    Edition           _runningKernelEdition;
+    Flavour           _runningKernelFlavour;
     Arch              _kernelArch;
     std::string       _keepSpec = ZConfig::instance().multiversionKernels();
-    bool              _keepRunning    = true;
+    bool              _keepRunning     = true;
+    bool              _detectedRunning = false;
   };
 
   /*!
    * tries to remove a the \ref PoolItem \a pi from the pool, solves and checks if no unexpected packages are removed due to the \a validRemovals regex.
    * If the constraint fails the changes are reverted and \a false is returned.
    */
-  bool PurgeKernels::Impl::removePackageAndCheck( PoolItem &pi, const str::regex &validRemovals ) const
+  bool PurgeKernels::Impl::removePackageAndCheck( const sat::Solvable::IdType id, const std::set<sat::Solvable::IdType> &keepList , const std::set<sat::Solvable::IdType> &removeList ) const
   {
     const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );
+
+    PoolItem pi ( (sat::Solvable(id)) );
+
     auto pool = ResPool::instance();
 
+    // make sure the pool is clean
+    if ( !pool.resolver().resolvePool() ) {
+      MIL << "Pool failed to resolve, not doing anything" << std::endl;
+      return false;
+    }
+
+    MIL << "Request to remove package: " << pi << std::endl;
+
+    //list of packages that are allowed to be removed automatically.
+    const str::regex validRemovals("(kernel-syms(-.*)?|kgraft-patch(-.*)?|kernel-livepatch(-.*)?|.*-kmp(-.*)?)");
+
+    if ( ui::asSelectable()( pi )->hasLocks() ) {
+      MIL << "Package " << pi << " is locked by the user, not removing." << std::endl;
+      return false;
+    }
+
     //remember which packages are already marked for removal, we do not need to check them again
     std::set< sat::Solvable::IdType> currentSetOfRemovals;
-    for ( auto it = pool.byStatusBegin( toBeUninstalledFilter ); it != pool.byStatusEnd( toBeUninstalledFilter );  it++  )
+    for ( auto it = pool.byStatusBegin( toBeUninstalledFilter ); it != pool.byStatusEnd( toBeUninstalledFilter );  it++  ) {
       currentSetOfRemovals.insert( it->id() );
+    }
 
     pi.status().setToBeUninstalled( ResStatus::USER );
 
@@ -92,12 +163,32 @@ namespace zypp {
       return false;
     }
 
+    std::set<sat::Solvable::IdType> removedInThisRun;
+    removedInThisRun.insert( pi.id() );
+
     for ( auto it = pool.byStatusBegin( toBeUninstalledFilter ); it != pool.byStatusEnd( toBeUninstalledFilter );  it++  ) {
 
-      //this was set by us or marked by a previous removal, ignore them
-      if ( it->status().isByUser() || (currentSetOfRemovals.find( it->id() ) != currentSetOfRemovals.end()) )
+      //check if that package is removeable
+      if ( it->status().isByUser()      //this was set by us, ignore it
+           || (currentSetOfRemovals.find( it->id() ) != currentSetOfRemovals.end()) //this was marked by a previous removal, ignore them
+        )
+        continue;
+
+      // remember for later we need remove the debugsource and debuginfo packages as well
+      removedInThisRun.insert( it->id() );
+
+      MIL << "Package " << PoolItem(*it) << " was marked by the solver for removal." << std::endl;
+
+      // if we do not plan to remove that package anyway, we need to check if its allowed to be removed ( package in removelist can never be in keep list )
+      if ( removeList.find( it->id() ) != removeList.end() )
         continue;
 
+      if ( keepList.find( it->id() ) != keepList.end() ) {
+        MIL << "Package " << PoolItem(*it) << " is in keep spec, skipping" << pi << std::endl;
+        pi.statusReset();
+        return false;
+      }
+
       str::smatch what;
       if ( !str::regex_match( it->name(), what, validRemovals) ) {
         MIL << "Package " << PoolItem(*it) << " should not be removed, skipping " << pi << std::endl;
@@ -106,11 +197,54 @@ namespace zypp {
       }
     }
 
-    MIL << "Removing package: " << pi << std::endl;
+    MIL << "Successfully marked package: " << pi << " for removal."<<std::endl;
+
+    //now check and mark the -debugsource and -debuginfo packages for this package and all the packages that were removed. Maybe collect it before and just remove here
+    MIL << "Trying to remove debuginfo for: " << pi <<"."<<std::endl;
+    for ( const auto id : removedInThisRun ) {
+
+      const auto solvable = sat::Solvable(id);
+      if ( solvable.arch() == Arch_noarch ||
+           solvable.arch() == Arch_empty )
+        continue;
+
+      for ( const auto suffix : { "-debugsource", "-debuginfo" } ) {
+        PoolQuery q;
+        q.addKind( zypp::ResKind::package );
+        q.addDependency( sat::SolvAttr::provides, Capability( solvable.name()+suffix, Rel::EQ, solvable.edition() ) );
+        q.setInstalledOnly();
+        q.setMatchExact();
+
+        for ( const auto debugPackage : q ) {
+
+          if ( debugPackage.arch() != solvable.arch() )
+            continue;
+
+          MIL << "Found debug package for " << solvable << " : " << debugPackage << std::endl;
+          //if removing the package fails it will not stop us from going on , so no need to check
+          removePackageAndCheck( debugPackage.id(), keepList, removeList );
+        }
+      }
+    }
+    MIL << "Finished removing debuginfo for: " << pi <<"."<<std::endl;
+
     return true;
   }
 
   /*!
+   * Return true if \a a == \a b or \a a == (\a b minus rebuild counter)
+   */
+  bool PurgeKernels::Impl::versionMatch( const Edition &a, const Edition &b )
+  {
+    // the build counter should not be considered here, so if there is one we cut it off
+    const auto dotOffset = b.release().find_last_of(".");
+    if ( dotOffset != std::string::npos ) {
+      return a == zypp::Edition( b.version(), b.release().substr( 0, dotOffset ), b.epoch() );
+    }
+    return a == b;
+  }
+
+  /*!
    * Parse the config line keep spec that tells us which kernels should be kept
    */
   void PurgeKernels::Impl::parseKeepSpec( )
@@ -174,81 +308,99 @@ namespace zypp {
   }
 
   /*!
-   * Go over the list of installed kernels and mark those as "do not remove" that match
-   * a entry in the keep spec
+   * Go over the list of available Editions for each flavour/arch combinations, apply the keep spec and mark the
+   * packages that belong to a matching category as to keep
+   *
+   * All packages with Arch_noarch will only be matched against the version but NOT the flavour, reasoning for that is
+   * simply that source flavours not necessarily match the binary flavours. Without a translation table that would not be
+   * doable. This is also what the perl script did.
+   *
    */
-  void PurgeKernels::Impl::fillKeepList( const std::unordered_map<std::string, std::map<Arch, std::map<Edition, sat::Solvable> > > &installedKernels, std::set<sat::Solvable::IdType> &list ) const
+  void PurgeKernels::Impl::fillKeepList( const GroupMap &installedKernels, std::set<sat::Solvable::IdType> &keepList, std::set<sat::Solvable::IdType> &removeList ) const
   {
-    for ( const auto &flavourMap : installedKernels ) {
-      for ( const auto &archMap : flavourMap.second ) {
+
+    const auto markAsKeep = [ &keepList, &removeList ]( const auto &pck ) {
+      MIL << "Marking package " << sat::Solvable(pck) << " as to keep." << std::endl;
+      keepList.insert( pck ) ;
+      removeList.erase( pck );
+    };
+
+    const auto versionPredicate = []( const auto &edition ){
+      return [ &edition ]( const auto &elem ) {
+        return versionMatch( edition, elem.first );
+      };
+    };
+
+    for ( const auto &groupInfo : installedKernels ) {
+
+      MIL << "Starting with group " << groupInfo.first << std::endl;
+
+      for ( const auto &archMap : groupInfo.second.archToEdMap ) {
+
+        MIL << "Starting with arch " << archMap.first << std::endl;
+
         size_t currOff = 0; //the current "oldest" offset ( runs from map start to end )
         size_t currROff = archMap.second.size() - 1; // the current "latest" offset ( runs from map end to start )
-        for ( const auto &kernelMap : archMap.second ) {
 
-          //if we find one of the running offsets in the keepspec, we add the kernel id the the list of packages to keep
-          if (  _keepOldestOffsets.find( currOff ) != _keepOldestOffsets.end()
-               || _keepLatestOffsets.find( currROff ) != _keepLatestOffsets.end()
-               // a kernel might be explicitely locked by version
-               || _keepSpecificEditions.find( kernelMap.second.edition() ) != _keepSpecificEditions.end() ) {
-            MIL << "Marking kernel " << kernelMap.second << " as to keep." << std::endl;
-            list.insert( kernelMap.second.id() ) ;
-          }
 
-          currOff++;
-          currROff--;
-        }
-      }
-    }
-  }
+        const EditionToSolvableMap &map = archMap.second;
 
-  /*!
-   * This cleans up the source and devel packages tree for a specific flavour.
-   */
-  void PurgeKernels::Impl::cleanDevelAndSrcPackages(const str::regex &validRemovals, std::set<Edition> &validEditions, const std::string &flavour )
-  {
-    bool isWithFlavour = flavour.size();
+        if ( _keepRunning
+             && ( ( archMap.first == _kernelArch && groupInfo.second.groupFlavour == _runningKernelFlavour )
+                  || groupInfo.second.groupType == GroupInfo::Sources ) ) {
 
-    if ( isWithFlavour )
-      MIL << "Trying to remove source/devel packages for flavour " << flavour << std::endl;
-    else
-      MIL << "Trying to remove global/default source/devel packages "<< std::endl;
+          MIL << "Matching packages against running kernel "<< _runningKernelEdition << "-" << _runningKernelFlavour << "-" <<_kernelArch << std::endl;
 
-    auto withFlavour = [&isWithFlavour, &flavour]( const std::string &name ) {
-      return isWithFlavour ? name+"-"+flavour : name;
-    };
+          auto it = std::find_if( map.begin(), map.end(), versionPredicate( _runningKernelEdition ) );
+          if ( it == map.end() ) {
 
-    //try to remove the kernel-devel-flavour and kernel-source-flavour packages
-    PoolQuery q;
-    q.addKind( zypp::ResKind::package );
+            // If we look at Sources we cannot match the flavour but we still want to keep on checking the rest of the keep spec
+            if ( groupInfo.second.groupType != GroupInfo::Sources  ) {
+              MIL << "Running kernel "<< _runningKernelEdition << "-" << _runningKernelFlavour << "-" <<_kernelArch << " not installed."<<std::endl;
+              MIL << "NOT removing any packages for flavor "<<_runningKernelFlavour<<"-"<<_kernelArch<<" ."<<std::endl;
 
-    q.addAttribute( sat::SolvAttr::name, withFlavour("kernel-devel") );
-    q.addAttribute( sat::SolvAttr::name, withFlavour("kernel-source") );
-    q.setInstalledOnly();
-    q.setMatchExact();
+              for ( const auto &kernelMap : map ) {
+                for( const auto &pck : kernelMap.second )
+                  markAsKeep(pck);
+              }
+              continue;
+            }
 
-    for ( auto installedSrcPck : q ) {
-      // For now print a message that we are removing a source package that has no corresponding kernel installed.
-      // This was changed due to bug #1171224 because orphaned kernel-source/devel packages were kept due to package
-      // rebuilds that did not obsolete the previously installed release, e.g. kernel-source-1-1.1 vs kernel-source-1-1.2
-      if ( validEditions.find( installedSrcPck.edition() ) == validEditions.end() ) {
-        MIL << "Trying to remove source package " << installedSrcPck <<  " no corresponding kernel with the same version was installed." << std::endl;
-      }
+          } else {
+            // there could be multiple matches here because of rebuild counter, lets try to find the last one
+            MIL << "Found possible running candidate edition: " << it->first << std::endl;
+            auto nit = it;
+            for ( nit++ ; nit != map.end() && versionMatch( _runningKernelEdition, nit->first ) ; nit++ ) {
+              MIL << "Found possible more recent running candidate edition: " << nit->first << std::endl;
+              it = nit;
+            }
+          }
 
-      //if no package providing kernel-flavour = VERSION is installed , we are free to remove the package
-      PoolQuery instKrnl;
-      instKrnl.addKind( zypp::ResKind::package );
-      instKrnl.setInstalledOnly();
-      instKrnl.setMatchExact();
-      instKrnl.addDependency( sat::SolvAttr::provides, withFlavour("kernel"), Rel::EQ, installedSrcPck.edition() );
+          // mark all packages of the running version as keep
+          if ( it != map.end() ) {
+            for( const auto &pck : it->second ) {
+              markAsKeep(pck);
+            }
+          }
+        }
 
-      bool found = std::any_of ( instKrnl.begin(), instKrnl.end(), []( auto it ) {  return !PoolItem(it).status().isToBeUninstalled(); } );
-      if ( found ) {
-        MIL << "Skipping source package " << installedSrcPck << " binary packages with the same edition are still installed" << std::endl;
-        continue;
-      }
+        for ( const auto &kernelMap : map ) {
+          //if we find one of the running offsets in the keepspec, we add the kernel id the the list of packages to keep
+          if (  _keepOldestOffsets.find( currOff ) != _keepOldestOffsets.end()
+               || _keepLatestOffsets.find( currROff ) != _keepLatestOffsets.end()
+               // a kernel might be explicitely locked by version
+               // this will currently keep all editions that match, so if keep spec has 1-1 , this will keep 1-1 but also all 1-1.n
+               ||  std::find_if( _keepSpecificEditions.begin(), _keepSpecificEditions.end(),
+                    [ edition = &kernelMap.first ]( const auto &elem ) { return versionMatch( *edition, elem ); } ) != _keepSpecificEditions.end() ) {
 
-      PoolItem pi( installedSrcPck );
-      removePackageAndCheck( pi, validRemovals );
+            for( const auto &pck : kernelMap.second ) {
+              markAsKeep(pck);
+            }
+          }
+          currOff++;
+          currROff--;
+        }
+      }
     }
   }
 
@@ -260,139 +412,162 @@ namespace zypp {
 
   void PurgeKernels::markObsoleteKernels()
   {
-    if ( _pimpl->_keepSpec.empty() )
+    MIL << std::endl << "--------------------- Starting to mark obsolete kernels ---------------------"<<std::endl;
+
+    if ( _pimpl->_keepSpec.empty() ) {
+      WAR << "Keep spec is empty, removing nothing." << std::endl;
       return;
+    }
 
     _pimpl->parseKeepSpec();
 
+    if ( _pimpl->_keepRunning && !_pimpl->_detectedRunning ) {
+      WAR << "Unable to detect running kernel, but keeping the running kernel was requested. Not removing any packages." << std::endl;
+      return;
+    }
+
     auto pool = ResPool::instance();
     pool.resolver().setForceResolve( true ); // set allow uninstall flag
 
     const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );
 
-    //list of packages that are allowed to be removed automatically.
-    const str::regex validRemovals("(kernel-syms(-.*)?|kgraft-patch(-.*)?|kernel-livepatch(-.*)?|.*-kmp(-.*)?)");
-
-    //list of packages that are allowed to be removed automatically when uninstalling kernel-devel packages
-    const str::regex validDevelRemovals("(kernel-source(-.*)?|(kernel-syms(-.*)?)|(kernel-devel(-.*)?)|(kernel(-.*)?-devel))");
-
     // kernel flavour regex
     const str::regex kernelFlavourRegex("^kernel-(.*)$");
 
-    // the map of all installed kernels, grouped by Flavour -> Arch -> Version
-    std::unordered_map< std::string, std::map< Arch, std::map<Edition, sat::Solvable> > > installedKernels;
+    // the map of all installed kernel packages, grouped by Flavour -> Arch -> Version -> (List of all packages in that category)
+    // devel and source packages are grouped together
+    GroupMap installedKrnlPackages;
 
-    // the set of kernel package IDs that have to be kept always
-    std::set<sat::Solvable::IdType> packagesToKeep;
-
-    //collect the list of installed kernel packages
-    PoolQuery q;
-    q.addKind( zypp::ResKind::package );
-    q.addAttribute( sat::SolvAttr::provides, "kernel" );
-    q.setInstalledOnly();
-    q.setMatchExact();
 
-    MIL << "Searching for obsolete kernels." << std::endl;
+    // packages that we plan to remove
+    std::set<sat::Solvable::IdType> packagesToRemove;
 
-    for ( auto installedKernel : q ) {
+    const auto addPackageToMap = [&installedKrnlPackages, &packagesToRemove] ( const GroupInfo::GroupType type, const std::string &ident, const std::string &flavour, const auto &installedKrnlPck ) {
 
-      MIL << "Found installed kernel " << installedKernel << std::endl;
+      if ( !installedKrnlPackages.count( ident ) )
+        installedKrnlPackages.insert( std::make_pair( ident, GroupInfo(type, flavour) ) );
 
-      //we can not simply skip the running kernel to make sure the keep-spec works correctly
-      if ( _pimpl->_keepRunning
-           && installedKernel.provides().matches( Capability( "kernel-uname-r", Rel::EQ, Edition( _pimpl->_uname_r ) ) )
-           && installedKernel.arch() == _pimpl->_kernelArch ) {
-        MIL << "Marking kernel " << installedKernel << " as to keep." << std::endl;
-        packagesToKeep.insert( installedKernel.id() );
+      auto &groupInfo = installedKrnlPackages[ ident ];
+      if ( groupInfo.groupType != type || groupInfo.groupFlavour != flavour ) {
+        ERR << "Got inconsistent type and flavour for ident this is a BUG: " <<  ident << std::endl
+            << "Original Flavour-Type:  "<<groupInfo.groupFlavour<<"-"<<groupInfo.groupType << std::endl
+            << "Competing Flavour-Type: "<< flavour << "-" << type << std::endl;
       }
 
-      str::smatch what;
-      str::regex_match( installedKernel.name(), what, kernelFlavourRegex );
-      if ( what[1].empty() ) {
-        WAR << "Could not detect kernel flavour for: " << installedKernel << " ...skipping" << std::endl;
-        continue;
-      }
+      const auto currArch = installedKrnlPck.arch();
+      if ( !groupInfo.archToEdMap.count( currArch ) )
+        groupInfo.archToEdMap.insert( std::make_pair( currArch , EditionToSolvableMap {} ) );
 
-      const std::string flavour = what[1];
-      if ( !installedKernels.count( flavour ) )
-        installedKernels.insert( std::make_pair( flavour, std::map< Arch, std::map<Edition, sat::Solvable> > {} ) );
+      auto &editionToSolvableMap = groupInfo.archToEdMap[ currArch ];
 
-      auto &flavourMap = installedKernels[ flavour ];
-      if ( !flavourMap.count( installedKernel.arch() ) )
-        flavourMap.insert( std::make_pair( installedKernel.arch(), std::map<Edition, sat::Solvable>{} ) );
+      const auto currEd  = installedKrnlPck.edition();
+      if ( !editionToSolvableMap.count( currEd ) )
+        editionToSolvableMap.insert( std::make_pair( currEd, SolvableList{} ) );
 
-      flavourMap[ installedKernel.arch() ].insert( std::make_pair( installedKernel.edition(), installedKernel ) );
-    }
+      editionToSolvableMap[currEd].push_back( installedKrnlPck.id() );
 
-    _pimpl->fillKeepList( installedKernels, packagesToKeep );
+      //in the first step we collect all packages in this list, then later we will remove the packages we want to explicitely keep
+      packagesToRemove.insert( installedKrnlPck.id() );
+    };
 
+    // the set of package IDs that have to be kept always
+    std::set<sat::Solvable::IdType> packagesToKeep;
 
-    MIL << "Starting to remove obsolete kernels." << std::endl;
+    //collect the list of installed kernel packages
+    PoolQuery q;
+    q.addKind( zypp::ResKind::package );
+    q.addAttribute( sat::SolvAttr::provides, "multiversion(kernel)" );
+    q.setInstalledOnly();
+    q.setMatchExact();
 
+    MIL << "Searching for obsolete multiversion kernel packages." << std::endl;
 
-    std::set<Edition> removedVersions;
+    for ( auto installedKrnlPck : q ) {
 
-    /*
-     * If there is a KMP or livepatch depending on the package remove it as well. If
-     * there is another package depending on the kernel keep the kernel. If there is
-     * a package that depends on a KMP keep the KMP and a kernel required to use the
-     * KMP.
-     */
-    for ( const auto &flavourMap : installedKernels ) {
+      MIL << "Found installed multiversion kernel package " << installedKrnlPck << std::endl;
 
-      // collect all removed versions of this edition
-      std::set<Edition> removedFlavourVersions;
+      if ( installedKrnlPck.provides().matches(Capability("kernel-uname-r")) ) {
+        MIL << "Identified as a kernel package " << std::endl;
 
-      for ( const auto &archMap : flavourMap.second ) {
-        for ( const auto &kernelMap : archMap.second ) {
-          auto &installedKernel = kernelMap.second;
+        // we group kernel packages by flavour
+        str::smatch what;
+        str::regex_match( installedKrnlPck.name(), what, kernelFlavourRegex );
+        if ( what[1].empty() ) {
+          WAR << "Could not detect flavour for: " << installedKrnlPck << " ...skipping" << std::endl;
+          continue;
+        }
 
-          // if the kernel is locked by the user, its not removed automatically
-          if ( ui::asSelectable()( installedKernel )->hasLocks() )
-            continue;
+        std::string flavour = what[1];
 
-          // this package is in the keep spec, do not touch
-          if ( packagesToKeep.count( installedKernel.id() ) )
-            continue;
+        // XXX: No dashes in flavor names
+        const auto dash = flavour.find_first_of('-');
+        if ( dash != std::string::npos ) {
+          flavour = flavour.substr( 0, dash );
+        }
 
-          // try to remove the kernel package, check afterwards if only expected packages have been removed
-          PoolItem pi( installedKernel );
-          if ( !_pimpl->removePackageAndCheck( pi, validRemovals ) ) {
-            continue;
-          }
+        // the ident for kernels is the flavour, to also handle cases like kernel-base and kernel which should be in the same group handled together
+        addPackageToMap( GroupInfo::Kernels, flavour, flavour, installedKrnlPck );
 
-          removedFlavourVersions.insert( installedKernel.edition() );
+      } else {
 
-          //lets remove the kernel-flavour-devel package too
-          PoolQuery develPckQ;
-          develPckQ.addKind( zypp::ResKind::package );
-          develPckQ.addDependency( sat::SolvAttr::name, installedKernel.name()+"-devel", Rel::EQ, installedKernel.edition() );
-          develPckQ.addDependency( sat::SolvAttr::name, installedKernel.name()+"-devel-debuginfo", Rel::EQ, installedKernel.edition() );
-          develPckQ.setInstalledOnly();
-          develPckQ.setMatchExact();
+        const str::regex explicitelyHandled("kernel-syms(-.*)?|kernel(-.*)?-devel");
 
-          for ( auto krnlDevPck : develPckQ ) {
+        MIL << "Not a kernel package, inspecting more closely " << std::endl;
 
-            if ( krnlDevPck.arch() != installedKernel.arch() )
-              continue;
+        // we directly handle all noarch packages that export multiversion(kernel)
+        if ( installedKrnlPck.arch() == Arch_noarch ) {
 
-            PoolItem devPi(krnlDevPck);
-            _pimpl->removePackageAndCheck( devPi, validDevelRemovals );
+          MIL << "Handling package explicitely due to architecture (noarch)."<< std::endl;
+          addPackageToMap( GroupInfo::Sources, installedKrnlPck.name(), "", installedKrnlPck );
+
+        } else if ( str::smatch match; str::regex_match( installedKrnlPck.name(), match, explicitelyHandled ) ) {
+
+          // try to get the flavour from the name
+          // if we have a kernel-syms getting no flavour means we have the "default" one, otherwise we use the flavour
+          // getting no flavour for a kernel(-*)?-devel means we have the kernel-devel package otherwise the flavour specific one
+          // ...yes this is horrible
+          std::string flav;
+          if ( match.size() > 1 )  {
+            flav = match[2].substr(1);
+          } else if ( installedKrnlPck.name() == "kernel-syms" ) {
+            flav = "default";
           }
+
+          MIL << "Handling package explicitely due to name match."<< std::endl;
+          addPackageToMap ( GroupInfo::RelatedBinaries, installedKrnlPck.name(), flav, installedKrnlPck );
+
+        } else {
+          MIL << "Package not explicitely handled" << std::endl;
         }
       }
-      //try to remove the kernel-devel-flavour and kernel-source-flavour packages
-      _pimpl->cleanDevelAndSrcPackages( validDevelRemovals, removedFlavourVersions, flavourMap.first );
-      removedVersions.insert( removedFlavourVersions.begin(), removedFlavourVersions.end() );
+
     }
 
-    // clean the global -devel and -source packages
-    _pimpl->cleanDevelAndSrcPackages( validDevelRemovals, removedVersions );
+    MIL << "Grouped packages: " << std::endl;
+    std::for_each( installedKrnlPackages.begin(), installedKrnlPackages.end(),[]( const auto &ident ){
+      MIL << "\tGroup ident: "<<ident.first<<std::endl;
+      MIL << "\t Group type: "<<ident.second.groupType<<std::endl;
+      MIL << "\t Group flav: "<<ident.second.groupFlavour<<std::endl;
+      std::for_each( ident.second.archToEdMap.begin(), ident.second.archToEdMap.end(), []( const auto &arch) {
+        MIL << "\t\tArch: "<<arch.first<<std::endl;
+        std::for_each( arch.second.begin(), arch.second.end(), []( const auto &edition) {
+          MIL << "\t\t\tEdition: "<<edition.first<<std::endl;
+          std::for_each( edition.second.begin(), edition.second.end(), []( const auto &packageId) {
+            MIL << "\t\t\t\t "<<sat::Solvable(packageId)<<std::endl;
+          });
+        });
+      });
+    });
+
+    _pimpl->fillKeepList( installedKrnlPackages, packagesToKeep, packagesToRemove );
+
+    for ( const auto id : packagesToRemove )
+      _pimpl->removePackageAndCheck( id, packagesToKeep, packagesToRemove );
   }
 
   void PurgeKernels::setUnameR( const std::string &val )
   {
-    _pimpl->_uname_r = val;
+    _pimpl->setUnameR( val );
   }
 
   std::string PurgeKernels::unameR() const
index 832425d..15bf6a7 100644 (file)
@@ -920,7 +920,8 @@ namespace zypp
     if ( ! (rawMirrorListUrl().asString().empty()) )
       str << (_pimpl->_mirrorListForceMetalink ? "metalink=" : "mirrorlist=") << rawMirrorListUrl() << endl;
 
-    str << "type=" << type().asString() << endl;
+    if ( type() != repo::RepoType::NONE )
+      str << "type=" << type().asString() << endl;
 
     if ( priority() != defaultPriority() )
       str << "priority=" << priority() << endl;
index dceddc3..e932ec8 100644 (file)
@@ -1118,20 +1118,24 @@ namespace zypp
          if ( repokind != probed )
          {
            repokind = probed;
-           // Adjust the probed type in RepoInfo
-           info.setProbedType( repokind ); // lazy init!
-           //save probed type only for repos in system
+           // update probed type only for repos in system
            for_( it, repoBegin(), repoEnd() )
            {
              if ( info.alias() == (*it).alias() )
              {
                RepoInfo modifiedrepo = *it;
                modifiedrepo.setType( repokind );
-               modifyRepository( info.alias(), modifiedrepo );
+               // don't modify .repo in refresh.
+               // modifyRepository( info.alias(), modifiedrepo );
                break;
              }
            }
+           // Adjust the probed type in RepoInfo
+           info.setProbedType( repokind ); // lazy init!
          }
+         // no need to continue with an unknown type
+         if ( repokind.toEnum() == RepoType::NONE_e )
+           ZYPP_THROW(RepoUnknownTypeException( info ));
        }
 
         Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
index 0f7ed61..3df37dc 100644 (file)
@@ -50,6 +50,9 @@ namespace zypp
   //   Resolver interface forwarded to implementation
   //
   ///////////////////////////////////////////////////////////////////
+  sat::detail::CSolver * Resolver::get() const
+  { return _pimpl->get(); }
+
   bool Resolver::verifySystem ()
   { return _pimpl->verifySystem(); }
 
index 0222f07..1a89977 100644 (file)
@@ -437,6 +437,9 @@ namespace zypp
      */
     solver::detail::ItemCapKindList installedSatisfied( const PoolItem & item );
 
+  public:
+    /** Expert backdoor. */
+    sat::detail::CSolver * get() const;
 
   private:
     friend std::ostream & operator<<( std::ostream & str, const Resolver & obj );
index ccd54e8..edc1e52 100644 (file)
@@ -556,6 +556,14 @@ namespace zypp
          , const std::string &/*reason*/
          , RpmLevel /*level*/
         ) {}
+
+        /** "rpmout/installpkg": Additional rpm output (sent immediately).
+        * Data:
+        * solvable : satSolvable processed
+        * line     : std::reference_wrapper<const std::string>
+        * lineno   : unsigned
+        */
+       static const UserData::ContentType contentRpmout;
       };
 
       // progress for removing a resolvable
@@ -592,6 +600,11 @@ namespace zypp
           , Error /*error*/
          , const std::string &/*reason*/
         ) {}
+
+        /** "rpmout/removepkg": Additional rpm output (sent immediately).
+        * For data \see \ref InstallResolvableReport::contentRpmout
+        */
+        static const UserData::ContentType contentRpmout;
       };
 
       // progress for rebuilding the database
index 93ed426..829af23 100644 (file)
@@ -57,40 +57,52 @@ regex::~regex() throw()
     regfree(&m_preg);
 }
 
-bool zypp::str::regex_match(const char * s, smatch& matches, const regex& regex)
+bool regex::matches( const char *s, smatch &matches, int flags ) const
 {
-  bool r = s && regex.m_valid && !regexec(&regex.m_preg, s, 12, &matches.pmatch[0], 0);
+  const auto possibleMatchCount = m_preg.re_nsub + 1;
+  matches.pmatch.resize( possibleMatchCount );
+  memset( matches.pmatch.data(), -1, sizeof( regmatch_t ) * ( possibleMatchCount ) );
+
+  bool r = s && m_valid && !regexec( &m_preg, s, matches.pmatch.size(), matches.pmatch.data(), flags );
   if (r)
     matches.match_str = s;
   return r;
 }
 
-bool zypp::str::regex_match(const char * s,  const regex& regex)
+bool regex::matches( const char *s ) const
 {
-  return s && !regexec(&regex.m_preg, s, 0, NULL, 0);
+  return s && !regexec(&m_preg, s, 0, NULL, 0);
 }
 
-smatch::smatch()
+bool zypp::str::regex_match(const char * s, smatch& matches, const regex& regex)
+{
+  return regex.matches( s, matches );
+}
+
+bool zypp::str::regex_match(const char * s,  const regex& regex)
 {
-  memset(&pmatch, -1, sizeof(pmatch));
+  return regex.matches( s );
 }
 
+smatch::smatch()
+{ }
+
 std::string smatch::operator[](unsigned i) const
 {
-  if ( i < sizeof(pmatch)/sizeof(*pmatch) && pmatch[i].rm_so != -1 )
+  if ( i < pmatch.size() && pmatch[i].rm_so != -1 )
     return match_str.substr( pmatch[i].rm_so, pmatch[i].rm_eo-pmatch[i].rm_so );
 
   return std::string();
 }
 
 std::string::size_type smatch::begin( unsigned i ) const
-{ return( i < sizeof(pmatch)/sizeof(*pmatch) && pmatch[i].rm_so != -1 ? pmatch[i].rm_so : std::string::npos ); }
+{ return( i < pmatch.size() && pmatch[i].rm_so != -1 ? pmatch[i].rm_so : std::string::npos ); }
 
 std::string::size_type smatch::end( unsigned i ) const
-{ return( i < sizeof(pmatch)/sizeof(*pmatch) && pmatch[i].rm_so != -1 ? pmatch[i].rm_eo : std::string::npos ); }
+{ return( i < pmatch.size() && pmatch[i].rm_so != -1 ? pmatch[i].rm_eo : std::string::npos ); }
 
 std::string::size_type smatch::size( unsigned i ) const
-{ return( i < sizeof(pmatch)/sizeof(*pmatch) && pmatch[i].rm_so != -1 ? pmatch[i].rm_eo-pmatch[i].rm_so : std::string::npos ); }
+{ return( i < pmatch.size() && pmatch[i].rm_so != -1 ? pmatch[i].rm_eo-pmatch[i].rm_so : std::string::npos ); }
 
 unsigned smatch::size() const
 {
@@ -98,10 +110,44 @@ unsigned smatch::size() const
   // Get highest (pmatch[i].rm_so != -1). Just looking for the 1st
   // (pmatch[i].rm_so == -1) is wrong as optional mayches "()?"
   // may be embeded.
-  for ( unsigned i = 0; i < sizeof(pmatch)/sizeof(*pmatch); ++i )
+  for ( unsigned i = 0; i < pmatch.size(); ++i )
   {
     if ( pmatch[i].rm_so != -1 )
       matches = i;
   }
   return ++matches;
 }
+
+std::string zypp::str::regex_substitute( const std::string &s, const regex &regex, const std::string &replacement, bool global )
+{
+  std::string result;
+  std::string::size_type off = 0;
+  int flags = regex::none;
+
+  while ( true ) {
+
+    smatch match;
+    if ( !regex.matches( s.data()+off, match, flags ) ) {
+      break;
+    }
+
+    if ( match.size() ) {
+      result += s.substr( off, match.begin(0) );
+      result += replacement;
+      off = match.end(0) + off;
+    }
+
+    if ( !global )
+      break;
+
+    // once we passed the beginning of the string we should not match ^ anymore, except the last character was
+    // actually a newline
+    if ( off > 0 && off < s.size() && s[ off - 1 ] == '\n' )
+      flags = regex::none;
+    else
+      flags = regex::not_bol;
+  }
+
+  result += s.substr( off );
+  return result;
+}
index 9aebfe9..9a5abc5 100644 (file)
@@ -77,6 +77,14 @@ namespace zypp
     inline bool regex_match( const std::string & s, const regex & regex )
     { return regex_match( s.c_str(), regex ); }
 
+    /**
+     * Replaces the matched regex with the string passed in \a replacement.
+     * If \a global is set the search continues after the first match
+     *
+     * \note Using backreferences in the replacement string is NOT supported.
+     */
+    std::string regex_substitute ( const std::string & s, const regex & regex, const std::string &replacement, bool global = true );
+
     //////////////////////////////////////////////////////////////////
     /// \class regex
     /// \brief Regular expression
@@ -91,6 +99,12 @@ namespace zypp
         icase          = REG_ICASE,    ///< Do not differentiate case
         nosubs         = REG_NOSUB,    ///< Support for substring addressing of matches is not required
         match_extended = REG_EXTENDED, ///< Use POSIX Extended Regular Expression syntax when interpreting regex.
+        newline         = REG_NEWLINE   ///< Match newline
+      };
+
+      enum MatchFlags {
+        none    = 0,
+        not_bol = REG_NOTBOL ///< Do not match begin of line
       };
 
       regex();
@@ -109,6 +123,9 @@ namespace zypp
       std::string asString() const
       { return m_str; }
 
+      bool matches (const char * s, str::smatch& matches, int flags = none ) const;
+      bool matches ( const char * s ) const;
+
     public:
       /** Expert backdoor. Returns pointer to the compiled regex for direct use in regexec() */
       regex_t * get()
@@ -119,8 +136,6 @@ namespace zypp
 
     private:
       friend class smatch;
-      friend bool regex_match(const char * s, str::smatch& matches, const regex& regex);
-      friend bool regex_match(const char * s,  const regex& regex);
       std::string m_str;
       int m_flags;
       regex_t m_preg;
@@ -161,7 +176,7 @@ namespace zypp
       std::string::size_type size( unsigned i ) const;
 
       std::string match_str;
-      regmatch_t pmatch[12];
+      std::vector<regmatch_t> pmatch;
     };
 
   } // namespace str
index ddc1b1e..cfa8746 100644 (file)
@@ -28,25 +28,33 @@ void globalInitCurlOnce()
 int log_curl(CURL *curl, curl_infotype info,
   char *ptr, size_t len, void *max_lvl)
 {
-  std::string pfx(" ");
-  long        lvl = 0;
-  switch( info)
+  if ( max_lvl == nullptr )
+    return 0;
+
+  long maxlvl = *((long *)max_lvl);
+
+  char pfx = ' ';
+  switch( info )
   {
-    case CURLINFO_TEXT:       lvl = 1; pfx = "*"; break;
-    case CURLINFO_HEADER_IN:  lvl = 2; pfx = "<"; break;
-    case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
-    default:                                      break;
+    case CURLINFO_TEXT:       if ( maxlvl < 1 ) return 0; pfx = '*'; break;
+    case CURLINFO_HEADER_IN:  if ( maxlvl < 2 ) return 0; pfx = '<'; break;
+    case CURLINFO_HEADER_OUT: if ( maxlvl < 2 ) return 0; pfx = '>'; break;
+    default:
+      return 0;
   }
-  if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
+
+  std::vector<std::string> lines;
+  str::split( std::string(ptr,len), std::back_inserter(lines), "\r\n" );
+  for( const auto & line : lines )
   {
-    std::string                            msg(ptr, len);
-    std::list<std::string>                 lines;
-    std::list<std::string>::const_iterator line;
-    zypp::str::split(msg, std::back_inserter(lines), "\r\n");
-    for(line = lines.begin(); line != lines.end(); ++line)
-    {
-      DBG << pfx << " " << *line << std::endl;
+    if ( str::startsWith( line, "Authorization:" ) ) {
+      std::string::size_type pos { line.find( " ", 15 ) }; // Authorization: <type> <credentials>
+      if ( pos == std::string::npos )
+       pos = 15;
+      DBG << pfx << " " << line.substr( 0, pos ) << " <credentials removed>" << std::endl;
     }
+    else
+      DBG << pfx << " " << line << std::endl;
   }
   return 0;
 }
index 4b235da..2947c31 100644 (file)
@@ -380,6 +380,26 @@ void MediaCurl::setupEasy()
 
   SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
 
+  /* Fixes bsc#1174011 "auth=basic ignored in some cases"
+       * We should proactively add the password to the request if basic auth is configured
+       * and a password is available in the credentials but not in the URL.
+       *
+       * We will be a bit paranoid here and require that the URL has a user embedded, otherwise we go the default route
+       * and ask the server first about the auth method
+       */
+  if ( _settings.authType() == "basic"
+       && _settings.username().size()
+       && !_settings.password().size() ) {
+
+    CredentialManager cm(CredManagerOptions(ZConfig::instance().repoManagerRoot()));
+    const auto cred = cm.getCred( _url );
+    if ( cred && cred->valid() ) {
+      if ( !_settings.username().size() )
+        _settings.setUsername(cred->username());
+      _settings.setPassword(cred->password());
+    }
+  }
+
   /*---------------------------------------------------------------*
    CURLOPT_USERPWD: [user name]:[password]
 
index 6ffbe0b..0c62d96 100644 (file)
@@ -90,7 +90,12 @@ std::ostream & CurlAuthData::dumpOn( std::ostream & str ) const
   return str;
 }
 
-long CurlAuthData::auth_type_str2long(std::string & auth_type_str)
+long CurlAuthData::auth_type_str2long( std::string & auth_type_str )
+{
+  return auth_type_str2long( const_cast< const std::string &>(auth_type_str) );
+}
+
+long CurlAuthData::auth_type_str2long( const std::string & auth_type_str )
 {
   curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
 
index 88fbfdd..f1a338d 100644 (file)
@@ -135,7 +135,8 @@ public:
    * \throws MediaException if an invalid authentication type name is
    *         encountered.
    */
-  static long auth_type_str2long(std::string & auth_type_str);
+  static long auth_type_str2long( std::string & auth_type_str );
+  static long auth_type_str2long( const std::string &auth_type_str );
 
   /**
    * Converts a long of ORed CURLAUTH_* identifiers into a string of comma
index 232f7ca..cdc9a00 100644 (file)
@@ -97,6 +97,9 @@ Resolver::~Resolver()
   delete _satResolver;
 }
 
+sat::detail::CSolver * Resolver::get() const
+{ return _satResolver->get(); }
+
 //---------------------------------------------------------------------------
 // forward flags too SATResolver
 
index 0e3df0c..6367a9f 100644 (file)
@@ -220,6 +220,9 @@ class Resolver : private base::NonCopyable
     ItemCapKindList satifiedByInstalled (const PoolItem & item );
     ItemCapKindList installedSatisfied( const PoolItem & item );
 
+public:
+    /** Expert backdoor. */
+    sat::detail::CSolver * get() const;
 };
 
 ///////////////////////////////////////////////////////////////////
index 07ef706..cc2a4eb 100644 (file)
@@ -214,6 +214,10 @@ class SATResolver : public base::ReferenceCounted, private base::NonCopyable, pr
 
     sat::StringQueue autoInstalled() const;
     sat::StringQueue userInstalled() const;
+
+public:
+  /** Expert backdoor. */
+  sat::detail::CSolver * get() const { return _satSolver; }
 };
 
 ///////////////////////////////////////////////////////////////////
index 50150e5..c110331 100644 (file)
@@ -42,6 +42,13 @@ namespace zypp
        {
        }
 
+       void RpmInstallPackageReceiver::report( const UserData & userData_r )
+       {
+         if ( ! userData_r.haskey( "solvable" ) )
+           userData_r.set( "solvable", _resolvable->satSolvable() );
+         _report->report( userData_r );
+       }
+
         /** Start the operation */
         void RpmInstallPackageReceiver::start( const Pathname & name )
        {
@@ -129,6 +136,13 @@ namespace zypp
        }
 
         /** Start the operation */
+       void RpmRemovePackageReceiver::report( const UserData & userData_r )
+       {
+         if ( ! userData_r.haskey( "solvable" ) )
+           userData_r.set( "solvable", _resolvable->satSolvable() );
+         _report->report( userData_r );
+       }
+
         void RpmRemovePackageReceiver::start( const std::string & name )
        {
            _report->start( _resolvable );
index cb3e279..18dcc89 100644 (file)
@@ -40,6 +40,9 @@ namespace zypp
 
        virtual void reportend();
 
+       /** Forwards generic reports. */
+       void report( const UserData & userData_r ) override;
+
         /** Start the operation */
         virtual void start( const Pathname & name );
 
@@ -84,6 +87,9 @@ namespace zypp
        virtual void reportend();
 
         /** Start the operation */
+       /** Forwards generic reports. */
+       void report( const UserData & userData_r ) override;
+
         virtual void start( const std::string & name );
 
         /**
index a55f708..1a231fd 100644 (file)
@@ -70,10 +70,24 @@ namespace zypp
   {
     bool IGotIt(); // in readonly-mode
   }
+  namespace env
+  {
+    inline bool ZYPP_RPM_DEBUG()
+    {
+      static bool val = [](){
+       const char * env = getenv("ZYPP_RPM_DEBUG");
+       return( env && str::strToBool( env, true ) );
+      }();
+      return val;
+    }
+  } // namespace env
 namespace target
 {
 namespace rpm
 {
+  const callback::UserData::ContentType InstallResolvableReport::contentRpmout( "rpmout","installpkg" );
+  const callback::UserData::ContentType RemoveResolvableReport::contentRpmout( "rpmout","removepkg" );
+
 namespace
 {
 #if 1 // No more need to escape whitespace since rpm-4.4.2.3
@@ -1354,7 +1368,8 @@ RpmDb::run_rpm (const RpmArgVec& opts,
   args.push_back(_root.asString().c_str());
   args.push_back("--dbpath");
   args.push_back(_dbPath.asString().c_str());
-
+  if ( env::ZYPP_RPM_DEBUG() )
+    args.push_back("-vv");
   const char* argv[args.size() + opts.size() + 1];
 
   const char** p = argv;
@@ -1427,7 +1442,11 @@ bool RpmDb::systemReadLine( std::string & line )
          }
 
          if ( ! ::ferror( inputfile ) || ::feof( inputfile ) )
+         {
+           if ( env::ZYPP_RPM_DEBUG() )
+             L_DBG("RPM_DEBUG") << line << endl;
            return true; // complete line
+         }
        }
        clearerr( inputfile );
       }
@@ -1667,11 +1686,18 @@ void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, cal
   opts.push_back ( quotedFilename.c_str() );
   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
+  // forward additional rpm output via report;
   std::string line;
+  unsigned    lineno = 0;
+  callback::UserData cmdout( InstallResolvableReport::contentRpmout );
+  // Key "solvable" injected by RpmInstallPackageReceiver
+  cmdout.set( "line",   std::cref(line) );
+  cmdout.set( "lineno", lineno );
+
+  // LEGACY: collect and forward additional rpm output in finish
   std::string rpmmsg;                          // TODO: immediately forward lines via Callback::report rather than collecting
   std::vector<std::string> configwarnings;     // TODO: immediately process lines rather than collecting
 
-  unsigned linecnt = 0;
   while ( systemReadLine( line ) )
   {
     if ( str::startsWith( line, "%%" ) )
@@ -1681,18 +1707,21 @@ void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, cal
       report->progress( percent );
       continue;
     }
+    ++lineno;
+    cmdout.set( "lineno", lineno );
+    report->report( cmdout );
 
-    if ( linecnt < MAXRPMMESSAGELINES )
-      ++linecnt;
-    else if ( line.find( " scriptlet failed, " ) == std::string::npos )        // always log %script errors
-      continue;
+    if ( lineno >= MAXRPMMESSAGELINES ) {
+      if ( line.find( " scriptlet failed, " ) == std::string::npos )   // always log %script errors
+       continue;
+    }
 
     rpmmsg += line+'\n';
 
     if ( str::startsWith( line, "warning:" ) )
       configwarnings.push_back(line);
   }
-  if ( linecnt >= MAXRPMMESSAGELINES )
+  if ( lineno >= MAXRPMMESSAGELINES )
     rpmmsg += "[truncated]\n";
 
   int rpm_status = systemStatus();
@@ -1836,7 +1865,16 @@ void RpmDb::doRemovePackage( const std::string & name_r, RpmInstFlags flags, cal
   opts.push_back(name_r.c_str());
   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
 
+  // forward additional rpm output via report;
   std::string line;
+  unsigned    lineno = 0;
+  callback::UserData cmdout( RemoveResolvableReport::contentRpmout );
+  // Key "solvable" injected by RpmInstallPackageReceiver
+  cmdout.set( "line",   std::cref(line) );
+  cmdout.set( "lineno", lineno );
+
+
+  // LEGACY: collect and forward additional rpm output in finish
   std::string rpmmsg;          // TODO: immediately forward lines via Callback::report rather than collecting
 
   // got no progress from command, so we fake it:
@@ -1844,16 +1882,19 @@ void RpmDb::doRemovePackage( const std::string & name_r, RpmInstFlags flags, cal
   // 50 - command completed
   // 100 if no error
   report->progress( 5 );
-  unsigned linecnt = 0;
   while (systemReadLine(line))
   {
-    if ( linecnt < MAXRPMMESSAGELINES )
-      ++linecnt;
-    else if ( line.find( " scriptlet failed, " ) == std::string::npos )        // always log %script errors
-      continue;
+    ++lineno;
+    cmdout.set( "lineno", lineno );
+    report->report( cmdout );
+
+    if ( lineno >= MAXRPMMESSAGELINES ) {
+      if ( line.find( " scriptlet failed, " ) == std::string::npos )   // always log %script errors
+       continue;
+    }
     rpmmsg += line+'\n';
   }
-  if ( linecnt >= MAXRPMMESSAGELINES )
+  if ( lineno >= MAXRPMMESSAGELINES )
     rpmmsg += "[truncated]\n";
   report->progress( 50 );
   int rpm_status = systemStatus();
index 33b823b..d85a551 100644 (file)
@@ -665,6 +665,26 @@ namespace zyppng {
       internal::fillSettingsFromUrl( url, set );
       if ( _transferSettings.proxy().empty() )
         internal::fillSettingsSystemProxy( url, set );
+
+      /* Fixes bsc#1174011 "auth=basic ignored in some cases"
+       * We should proactively add the password to the request if basic auth is configured
+       * and a password is available in the credentials but not in the URL.
+       *
+       * We will be a bit paranoid here and require that the URL has a user embedded, otherwise we go the default route
+       * and ask the server first about the auth method
+       */
+      if ( set.authType() == "basic"
+           && set.username().size()
+           && !set.password().size() ) {
+        zypp::media::CredentialManager cm( zypp::media::CredManagerOptions( zypp::ZConfig::instance().repoManagerRoot()) );
+        const auto cred = cm.getCred( url );
+        if ( cred && cred->valid() ) {
+          if ( !set.username().size() )
+            set.setUsername(cred->username());
+          set.setPassword(cred->password());
+        }
+      }
+
     } catch ( const zypp::media::MediaBadUrlException & e ) {
       res = NetworkRequestErrorPrivate::customError( NetworkRequestError::MalformedURL, e.asString(), buildExtraInfo() );
     } catch ( const zypp::media::MediaUnauthorizedException & e ) {