mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 05:54:15 +02:00
[arch] fix -> not able to replace an installed package for a new one that replaces it during conflict resolutions
This commit is contained in:
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
### Fixes
|
||||
- Arch:
|
||||
- search: not able to find installed packages that were renamed on the repositories (e.g: xapps -> xapp)
|
||||
- not able to replace an installed package for a new one that replaces it during conflict resolutions (e.g: xapp replaces xapps)
|
||||
- AUR: not able to find some repository dependencies when their names are not an exact match (e.g: sc-controller [0.4.7-1] relies on "pylibacl". This dependency now is called "python-pylibacl")
|
||||
|
||||
## [0.9.8] 2020-10-02
|
||||
|
||||
@@ -1095,15 +1095,20 @@ class ArchManager(SoftwareManager):
|
||||
watcher.change_substatus('')
|
||||
return True
|
||||
|
||||
def _uninstall_pkgs(self, pkgs: Iterable[str], root_password: str, handler: ProcessHandler) -> bool:
|
||||
def _uninstall_pkgs(self, pkgs: Iterable[str], root_password: str, handler: ProcessHandler, ignore_dependencies: bool = False) -> bool:
|
||||
status_handler = TransactionStatusHandler(watcher=handler.watcher,
|
||||
i18n=self.i18n,
|
||||
names={*pkgs},
|
||||
logger=self.logger,
|
||||
pkgs_to_remove=len(pkgs))
|
||||
|
||||
cmd = ['pacman', '-R', *pkgs, '--noconfirm']
|
||||
|
||||
if ignore_dependencies:
|
||||
cmd.append('-dd')
|
||||
|
||||
status_handler.start()
|
||||
all_uninstalled, _ = handler.handle_simple(SimpleProcess(cmd=['pacman', '-R', *pkgs, '--noconfirm'],
|
||||
all_uninstalled, _ = handler.handle_simple(SimpleProcess(cmd=cmd,
|
||||
root_password=root_password,
|
||||
error_phrases={'error: failed to prepare transaction',
|
||||
'error: failed to commit transaction'},
|
||||
@@ -1167,24 +1172,25 @@ class ArchManager(SoftwareManager):
|
||||
|
||||
return True
|
||||
|
||||
def _uninstall(self, context: TransactionContext, names: Set[str], remove_unneeded: bool = False, disk_loader: DiskCacheLoader = None):
|
||||
def _uninstall(self, context: TransactionContext, names: Set[str], remove_unneeded: bool = False, disk_loader: Optional[DiskCacheLoader] = None, skip_requirements: bool = False):
|
||||
self._update_progress(context, 10)
|
||||
|
||||
net_available = internet.is_available() if disk_loader else True
|
||||
|
||||
hard_requirements = set()
|
||||
|
||||
for n in names:
|
||||
try:
|
||||
pkg_reqs = pacman.list_hard_requirements(n, self.logger)
|
||||
if not skip_requirements:
|
||||
for n in names:
|
||||
try:
|
||||
pkg_reqs = pacman.list_hard_requirements(n, self.logger)
|
||||
|
||||
if pkg_reqs:
|
||||
hard_requirements.update(pkg_reqs)
|
||||
except PackageInHoldException:
|
||||
context.watcher.show_message(title=self.i18n['error'].capitalize(),
|
||||
body=self.i18n['arch.uninstall.error.hard_dep_in_hold'].format(bold(n)),
|
||||
type_=MessageType.ERROR)
|
||||
return False
|
||||
if pkg_reqs:
|
||||
hard_requirements.update(pkg_reqs)
|
||||
except PackageInHoldException:
|
||||
context.watcher.show_message(title=self.i18n['error'].capitalize(),
|
||||
body=self.i18n['arch.uninstall.error.hard_dep_in_hold'].format(bold(n)),
|
||||
type_=MessageType.ERROR)
|
||||
return False
|
||||
|
||||
self._update_progress(context, 25)
|
||||
|
||||
@@ -1199,7 +1205,7 @@ class ArchManager(SoftwareManager):
|
||||
watcher=context.watcher):
|
||||
return False
|
||||
|
||||
if remove_unneeded:
|
||||
if not skip_requirements and remove_unneeded:
|
||||
unnecessary_packages = pacman.list_post_uninstall_unneeded_packages(to_uninstall)
|
||||
self.logger.info("Checking unnecessary optdeps")
|
||||
|
||||
@@ -1222,7 +1228,7 @@ class ArchManager(SoftwareManager):
|
||||
else:
|
||||
instances = None
|
||||
|
||||
uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler)
|
||||
uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler, ignore_dependencies=skip_requirements)
|
||||
|
||||
if uninstalled:
|
||||
if disk_loader: # loading package instances in case the uninstall succeeds
|
||||
@@ -1521,7 +1527,7 @@ class ArchManager(SoftwareManager):
|
||||
else:
|
||||
return self._get_history_repo_pkg(pkg)
|
||||
|
||||
def _request_conflict_resolution(self, pkg: str, conflicting_pkg: str, context: TransactionContext) -> bool:
|
||||
def _request_conflict_resolution(self, pkg: str, conflicting_pkg: str, context: TransactionContext, skip_requirements: bool = False) -> bool:
|
||||
conflict_msg = '{} {} {}'.format(bold(pkg), self.i18n['and'], bold(conflicting_pkg))
|
||||
if not context.watcher.request_confirmation(title=self.i18n['arch.install.conflict.popup.title'],
|
||||
body=self.i18n['arch.install.conflict.popup.body'].format(conflict_msg)):
|
||||
@@ -1534,7 +1540,8 @@ class ArchManager(SoftwareManager):
|
||||
if context.removed is None:
|
||||
context.removed = {}
|
||||
|
||||
res = self._uninstall(context=context, names={conflicting_pkg}, disk_loader=context.disk_loader, remove_unneeded=False)
|
||||
res = self._uninstall(context=context, names={conflicting_pkg}, disk_loader=context.disk_loader,
|
||||
remove_unneeded=False, skip_requirements=skip_requirements)
|
||||
context.restabilish_progress()
|
||||
return res
|
||||
|
||||
@@ -1572,16 +1579,17 @@ class ArchManager(SoftwareManager):
|
||||
|
||||
all_provided = context.get_provided_map()
|
||||
|
||||
for dep, conflicts in pacman.map_conflicts_with(repo_dep_names, remote=True).items():
|
||||
if conflicts:
|
||||
for c in conflicts:
|
||||
for dep, data in pacman.map_conflicts_with(repo_dep_names, remote=True).items():
|
||||
if data and data['c']:
|
||||
for c in data['c']:
|
||||
source_conflict = all_provided.get(c)
|
||||
|
||||
if source_conflict:
|
||||
conflict_pkg = [*source_conflict][0]
|
||||
|
||||
if dep != conflict_pkg:
|
||||
if not self._request_conflict_resolution(dep, conflict_pkg , context):
|
||||
if not self._request_conflict_resolution(dep, conflict_pkg, context,
|
||||
skip_requirements=data['r'] and conflict_pkg in data['r']):
|
||||
return {dep}
|
||||
|
||||
downloaded = 0
|
||||
@@ -1592,7 +1600,7 @@ class ArchManager(SoftwareManager):
|
||||
except ArchDownloadException:
|
||||
return False
|
||||
|
||||
status_handler = TransactionStatusHandler(watcher=context.watcher, i18n=self.i18n, names=repo_dep_names,
|
||||
status_handler = TransactionStatusHandler(watcher=context.watcher, i18n=self.i18n, names={*repo_dep_names},
|
||||
logger=self.logger, percentage=len(repo_deps) > 1, downloading=downloaded)
|
||||
status_handler.start()
|
||||
installed, _ = context.handler.handle_simple(pacman.install_as_process(pkgpaths=repo_dep_names,
|
||||
@@ -2106,8 +2114,25 @@ class ArchManager(SoftwareManager):
|
||||
if context.removed is None:
|
||||
context.removed = {}
|
||||
|
||||
to_install_replacements = pacman.map_replaces(names_to_install)
|
||||
|
||||
skip_requirement_checking = False
|
||||
if to_install_replacements: # checking if the packages to be installed replace the installed packages
|
||||
all_replacements = set()
|
||||
|
||||
for replacements in to_install_replacements:
|
||||
all_replacements.update(replacements)
|
||||
|
||||
if all_replacements:
|
||||
for pkg in to_uninstall:
|
||||
if pkg not in all_replacements:
|
||||
break
|
||||
|
||||
skip_requirement_checking = True
|
||||
|
||||
context.disable_progress_if_changing()
|
||||
if not self._uninstall(names=to_uninstall, context=context, remove_unneeded=False, disk_loader=context.disk_loader):
|
||||
if not self._uninstall(names=to_uninstall, context=context, remove_unneeded=False,
|
||||
disk_loader=context.disk_loader, skip_requirements=skip_requirement_checking):
|
||||
context.watcher.show_message(title=self.i18n['error'],
|
||||
body=self.i18n['arch.uninstalling.conflict.fail'].format(', '.join((bold(p) for p in to_uninstall))),
|
||||
type_=MessageType.ERROR)
|
||||
|
||||
@@ -960,12 +960,57 @@ def map_required_by(names: Iterable[str] = None, remote: bool = False) -> Dict[s
|
||||
return {}
|
||||
|
||||
|
||||
def map_conflicts_with(names: Iterable[str], remote: bool) -> Dict[str, Set[str]]:
|
||||
def map_conflicts_with(names: Iterable[str], remote: bool) -> Dict[str, Dict[str, Set[str]]]:
|
||||
output = run_cmd('pacman -{}i {}'.format('S' if remote else 'Q', ' '.join(names)))
|
||||
|
||||
if output:
|
||||
res = {}
|
||||
latest_name, conflicts = None, None
|
||||
latest_name, conflicts, replaces, field = None, None, None, None
|
||||
|
||||
for l in output.split('\n'):
|
||||
if l:
|
||||
if l[0] != ' ':
|
||||
line = l.strip()
|
||||
field_sep_idx = line.index(':')
|
||||
field = line[0:field_sep_idx].strip()
|
||||
|
||||
if field == 'Name':
|
||||
field = 'n'
|
||||
val = line[field_sep_idx + 1:].strip()
|
||||
latest_name = val
|
||||
elif field == 'Conflicts With':
|
||||
field = 'c'
|
||||
val = line[field_sep_idx + 1:].strip()
|
||||
conflicts = set()
|
||||
if val != 'None':
|
||||
conflicts.update((d for d in val.split(' ') if d))
|
||||
elif field == 'Replaces':
|
||||
field = 'r'
|
||||
val = line[field_sep_idx + 1:].strip()
|
||||
replaces = set()
|
||||
if val != 'None':
|
||||
replaces.update((d for d in val.split(' ') if d))
|
||||
|
||||
elif latest_name and conflicts is not None and replaces is not None:
|
||||
field = None
|
||||
res[latest_name] = {'c': conflicts, 'r': replaces}
|
||||
latest_name, conflicts, replaces = None, None, None
|
||||
|
||||
elif latest_name and field:
|
||||
if field == 'c':
|
||||
conflicts.update((d for d in l.strip().split(' ') if d))
|
||||
elif field == 'r':
|
||||
replaces.update((d for d in l.strip().split(' ') if d))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def map_replaces(names: Iterable[str], remote: bool = False) -> Dict[str, Set[str]]:
|
||||
output = run_cmd('pacman -{}i {}'.format('S' if remote else 'Q', ' '.join(names)))
|
||||
|
||||
if output:
|
||||
res = {}
|
||||
latest_name, replaces = None, None
|
||||
|
||||
for l in output.split('\n'):
|
||||
if l:
|
||||
@@ -977,18 +1022,18 @@ def map_conflicts_with(names: Iterable[str], remote: bool) -> Dict[str, Set[str]
|
||||
if field == 'Name':
|
||||
val = line[field_sep_idx + 1:].strip()
|
||||
latest_name = val
|
||||
elif field == 'Conflicts With':
|
||||
elif field == 'Replaces':
|
||||
val = line[field_sep_idx + 1:].strip()
|
||||
conflicts = set()
|
||||
replaces = set()
|
||||
if val != 'None':
|
||||
conflicts.update((d for d in val.split(' ') if d))
|
||||
replaces.update((d for d in val.split(' ') if d))
|
||||
|
||||
elif latest_name and conflicts is not None:
|
||||
res[latest_name] = conflicts
|
||||
latest_name, conflicts = None, None
|
||||
elif latest_name and replaces is not None:
|
||||
res[latest_name] = replaces
|
||||
latest_name, replaces = None, None, None
|
||||
|
||||
elif latest_name and conflicts is not None:
|
||||
conflicts.update(conflicts.update((d for d in l.strip().split(' ') if d)))
|
||||
elif latest_name and replaces is not None:
|
||||
replaces.update((d for d in l.strip().split(' ') if d))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
Reference in New Issue
Block a user