diff --git a/dnf/cli/commands/history.py b/dnf/cli/commands/history.py index 21d04a1a65..97fba2f304 100644 --- a/dnf/cli/commands/history.py +++ b/dnf/cli/commands/history.py @@ -61,9 +61,20 @@ def set_argparser(parser): HistoryCommand._CMDS[0], ", ".join(HistoryCommand._CMDS[1:]))) parser.add_argument('--reverse', action='store_true', - help="display history list output reversed") + help="display history list output in reverse " + "(in ascending order with the first transaction first)") + parser.add_argument("-o", "--output", default=None, - help=_("For the store command, file path to store the transaction to")) + help=_("For the store command, file path to store history transactions in " + "(default: transaction[s].[json|txt])")) + parser.add_argument("-a", "--all", dest='store_all', action='store_true', default=False, + help=_("For the store command, whether to store all transactions")) + parser.add_argument("--commands", dest='store_shell_commands', action='store_true', default=False, + help=_("For the store command, whether to store just shell command(s) in a transaction(s).txt")) + parser.add_argument("--comments", dest='store_shell_command_comments', action='store_true', default=False, + help=_("For the store command, whether to include comments before " + "history transaction shell command(s)")) + parser.add_argument("--ignore-installed", action="store_true", help=_("For the replay command, don't check for installed packages matching " "those in transaction")) @@ -117,9 +128,10 @@ def configure(self): dnf.cli.commands._checkGPGKey(self.base, self.cli) elif self.opts.transactions_action == 'store': - self._require_one_transaction_id = True - if not self.opts.transactions: - raise dnf.cli.CliError(_('No transaction ID or package name given.')) + self._require_one_transaction_id = True # TODO + if not self.opts.store_all: + if not self.opts.transactions: + raise dnf.cli.CliError(_('No transaction ID or package name or -a/--all given.')) elif self.opts.transactions_action in ['redo', 'undo', 'rollback']: demands.available_repos = True demands.resolving = True @@ -179,6 +191,10 @@ def _history_get_transactions(self, extcmds): raise dnf.cli.CliError(_('Transaction ID "{0}" not found.').format(extcmds[0])) return old + def _history_get_all_transactions(self, extcmds=None): + old = self.base.history.old(extcmds) + return old + def _history_get_transaction(self, extcmds): old = self._history_get_transactions(extcmds) if len(old) > 1: @@ -358,27 +374,72 @@ def run(self): elif vcmd == 'userinstalled': self._hcmd_userinstalled() elif vcmd == 'store': - tid = self._history_get_transaction(tids) - data = serialize_transaction(tid) - try: - filename = self.opts.output if self.opts.output is not None else "transaction.json" - - # it is absolutely possible for both assumeyes and assumeno to be True, go figure - if (self.base.conf.assumeno or not self.base.conf.assumeyes) and os.path.isfile(filename): - msg = _("{} exists, overwrite?").format(filename) - if self.base.conf.assumeno or not self.base.output.userconfirm( - msg='\n{} [y/N]: '.format(msg), defaultyes_msg='\n{} [Y/n]: '.format(msg)): - print(_("Not overwriting {}, exiting.").format(filename)) - return - - with open(filename, "w") as f: - json.dump(data, f, indent=4, sort_keys=True) - f.write("\n") - - print(_("Transaction saved to {}.").format(filename)) - - except OSError as e: - raise dnf.cli.CliError(_('Error storing transaction: {}').format(str(e))) + if self.opts.store_all: + self._hcmd_store_all(tids) + else: + self._hcmd_store(tids) + + def _hcmd_store(self, extcmd): + tid = self._history_get_transaction(extcmd) + data = serialize_transaction(tid) + try: + filename = self.opts.output if self.opts.output is not None else "transaction.json" + self._write_json(filename, data) + except OSError as e: + raise dnf.cli.CliError(_('Error storing transaction: {}').format(str(e))) + + def _hcmd_store_all(self, extcmd): + #breakpoint() + transactions = self._history_get_all_transactions(extcmd) + data = {} + for txn in transactions: + data[txn.tid] = serialize_transaction(txn) + if not self.opts.store_shell_commands and not self.opts.store_shell_command_comments: + try: + filename = self.opts.output if self.opts.output is not None else "transactions.json" + self._write_json(filename, data) + except OSError as e: + raise dnf.cli.CliError(_('Error storing transactions: {}').format(str(e))) + else: + filename = self.opts.output if self.opts.output is not None else "transactions.txt" + try: + with open(filename, 'w') as f: + for transaction in reversed(transactions): + tid = transaction.tid + #breakpoint() + if self.opts.store_shell_command_comments: + transaction_dict = {'tid': transaction.tid} #serialize_transaction(transaction) + #transaction_dict['cmdline'] = str(transaction.cmdline) + transaction_dict['uid'] = dnf.cli.output.Output._pwd_ui_username( + dnf.cli.output.Output, transaction.loginuid, limit=24) # TODO: why 24? + #f.write(f'## dnf history info {transaction.tid} \n') # TODO: which fields only + f.write(f"## {transaction_dict} \n") + if transaction.cmdline is None: + cmdlines = [] + elif isinstance(transaction.cmdline, (tuple, list)): + cmdlines = transaction.cmdline + else: + cmdlines = [transaction.cmdline] + for cmdline in cmdlines: + f.write("dnf {}\n".format(cmdline)) + except OSError as e: + raise dnf.cli.CliError(_('Error storing transactions: {}').format(str(e))) + print(_("Wrote transactions to: ")+filename) + + + def _write_json(self, filename, data): + # it is absolutely possible for both assumeyes and assumeno to be True, go figure + if (self.base.conf.assumeno or not self.base.conf.assumeyes) and os.path.isfile(filename): + msg = _("{} exists, overwrite?").format(filename) + if self.base.conf.assumeno or not self.base.output.userconfirm( + msg='\n{} [y/N]: '.format(msg), defaultyes_msg='\n{} [Y/n]: '.format(msg)): + print(_("Not overwriting {}, exiting.").format(filename)) + return + + with open(filename, "w") as f: + json.dump(data, f, indent=4, sort_keys=True) + f.write("\n") + print(_("Transaction saved to {}.").format(filename)) def run_resolved(self): if self.opts.transactions_action not in ("replay", "redo", "rollback", "undo"): diff --git a/scripts/install_env_in_a_virtualenv.sh b/scripts/install_env_in_a_virtualenv.sh new file mode 100755 index 0000000000..efbe85b327 --- /dev/null +++ b/scripts/install_env_in_a_virtualenv.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +# contribscript.sh -- run dnf from a local clone in a virtualenv + +setup_const_py_in() { + #prefix=$_WRD cd "$prefix" + cp dnf/const.py.in dnf/const.py && \ + sed -i "s/^VERSION[[:blank:]]*=.*/VERSION='0.0.1-todo'/; s/^PLUGINPATH[[:blank:]]*=.*/PLUGINPATH='$pluginpath'/" dnf/const.py +} + +setup_virtualenv_dnf() { + dnfsrcdir="$VIRTUAL_ENV/src/dnf/dnf" + + pythonver="python3.12" + systemsitepackages='/usr/lib64/python3.12/site-packages' + VIRTUAL_ENV_sitepackages="${VIRTUAL_ENV}/lib/python3.12/site-packages" + (set -x; cd "${VIRTUAL_ENV_sitepackages}"; + ln -s "${systemsitepackages}/rpm"; + ln -s "${systemsitepackages}/hawkey"; + ln -s "${systemsitepackages}/libcomps"; + ln -s "${dnfsrcdir}"; + #ln -s /usr/lib64/libdnf.so.2; + ) + +} + +test_virtualenv_dnf() { + set -ex + _WRD=$VIRTUAL_ENV/src/dnf + #PYTHONPATH=${_WRD} LD_LIBRARY_PATH=/usr/lib64 python $_WRD/dnf/cli/main.py + #PYTHONPATH=${_WRD} LD_LIBRARY_PATH=/usr/lib64 python $_WRD/dnf/cli/main.py + #PYTHONPATH=${_WRD} LD_LIBRARY_PATH=/usr/lib64 python $_WRD/dnf/cli/main.py history -h + #PYTHONPATH=${_WRD} python $_WRD/dnf/cli/main.py history -h + python "${_WRD}/dnf/cli/main.py" history -h + + python "${_WRD}/dnf/cli/main.py" history store --all + cat ./transactions.json + + python "${_WRD}/dnf/cli/main.py" history store --all --comments + cat ./transactions.txt + + python "${_WRD}/dnf/cli/main.py" history store --all --commands + cat ./transactions.txt + +} + +main() { + if [ ! `id -g` -eq 0 ]; then + (set -x; setup_virtualenv_dnf) + fi + test_virtualenv_dnf +} + +main