diff options
| author | Julian Andres Klode <jak@debian.org> | 2026-04-07 09:39:55 +0000 |
|---|---|---|
| committer | Julian Andres Klode <jak@debian.org> | 2026-04-07 09:39:55 +0000 |
| commit | 9a97c0e647609da41a1df6574d98320e5e253988 (patch) | |
| tree | 2eba28e277f4ad7ab56f8d015decd09d0712fb2f | |
| parent | b927ef91b5f6d682d7fb5e8fd8a2d5e4d7292329 (diff) | |
| parent | 58821c2dfc9fefb09d6ff6655e435bacdfabe173 (diff) | |
Merge branch 'history-command-list-width-scaling' into 'main'
Scale `apt history-list` to the screen width
See merge request apt-team/apt!555
| -rw-r--r-- | apt-private/private-history.cc | 116 | ||||
| -rwxr-xr-x | test/integration/test-history | 73 |
2 files changed, 136 insertions, 53 deletions
diff --git a/apt-private/private-history.cc b/apt-private/private-history.cc index 31b3b1333..0d3988662 100644 --- a/apt-private/private-history.cc +++ b/apt-private/private-history.cc @@ -16,6 +16,7 @@ #include <apt-private/private-cachefile.h> #include <apt-private/private-history.h> #include <apt-private/private-install.h> +#include <apt-private/private-output.h> #include <cassert> #include <iomanip> @@ -100,14 +101,22 @@ Change APT::Internal::InvertChange(const Change &change) // ShortenCommand - Take a command and shorten it such that it adheres // to the given maximum length. -static std::string ShortenCommand(const std::string &cmd, const std::size_t maxLen) +static std::string ShortenCommand(std::string cmd, const std::size_t maxLen) { - std::string shortenedCmd = cmd; - if (shortenedCmd.starts_with("apt ")) - shortenedCmd = shortenedCmd.substr(4); - if (shortenedCmd.length() > maxLen - 3) - return shortenedCmd.substr(0, maxLen - 4) + "..."; - return shortenedCmd; + if (cmd.starts_with("/usr/bin/")) + cmd.erase(0,9); + else if (cmd.starts_with("apt ")) + cmd.erase(0, 4); + + if (cmd.length() <= maxLen) + return cmd; + + if (maxLen <= 3) + return cmd.substr(0, maxLen); + + cmd.resize(maxLen - 3); + cmd.append("..."); + return cmd; } static std::string LocalizeKindToString(const Kind &kind) @@ -148,7 +157,7 @@ static std::string GetKindString(const Entry &entry) { // We want full output if there is only one action if (entry.changeMap.size() == 1) - return LocalizeKindToString(entry.changeMap.begin()->first).data(); + return LocalizeKindToString(entry.changeMap.begin()->first); std::string kindGroup = ""; // add localization later @@ -191,48 +200,79 @@ static std::string GetKindString(const Entry &entry) // -static void PrintHistoryVector(const HistoryBuffer buf, int columnWidth) +static void PrintHistoryVector(const HistoryBuffer &buf) { - // Calculate the width of the ID column, esentially the number of digits - int id = 0; - size_t sizeFrac = buf.size(); - int idWidth = 2; - while (sizeFrac) + // TRANSLATORS: a number used as identifier. + const std::string idHeader = _("ID"); + // TRANSLATORS: input on the terminal console. + const std::string cmdHeader = _("Command line"); + // TRANSLATORS: indicates when the entry occurred. + const std::string dateHeader = _("Date and Time"); + // TRANSLATORS: the type of operation that was performed. + const std::string actionHeader = _("Action"); + // TRANSLATORS: changes as a quantity. + const std::string changesHeader = _("Changes"); + + constexpr size_t pad = 2; + size_t idWidth = std::to_string(buf.size()).size(); + idWidth = std::max(idHeader.size(), idWidth) + pad; + size_t cmdLineWidth = cmdHeader.size() + pad; + size_t dateWidth = dateHeader.size() + pad; + size_t actionWidth = actionHeader.size() + pad; + size_t changesWidth = changesHeader.size(); + + // Scale to screen width + // ID and Changes are numerical and cannot be shortened + size_t remainingWidth = ::ScreenWidth - idWidth - changesWidth; + size_t minRequiredCmdWidth = cmdLineWidth; + std::vector<std::string> kindStrings; + std::vector<size_t> changeCounts; + kindStrings.reserve(buf.size()); + changeCounts.reserve(buf.size()); + if (!buf.empty()) { - sizeFrac /= 10; - idWidth++; + dateWidth = std::max(buf.front().startDate.size() + pad, dateWidth); + for (const auto &entry : buf) + { + std::string kindString = GetKindString(entry); + if (kindString.size() > actionWidth) + actionWidth = kindString.size(); + kindStrings.push_back(kindString); + + size_t numChanges = 0; + for (const auto &[_, changes] : entry.changeMap) + numChanges += changes.size(); + changeCounts.push_back(numChanges); + + if (entry.cmdLine.length() > minRequiredCmdWidth) + minRequiredCmdWidth = entry.cmdLine.length() + pad; + } + remainingWidth -= (dateWidth + actionWidth); + cmdLineWidth = std::min(remainingWidth, minRequiredCmdWidth); } - // Print headers - auto writeEntry = [](std::string entry, size_t width) + constexpr auto writeEntry = [](const auto &entry, size_t width) { std::cout << std::left << std::setw(width) << entry; }; - writeEntry(_("ID"), idWidth); - writeEntry(_("Command line"), columnWidth); - // NOTE: if date format is different, - // this width needs to change - writeEntry(_("Date and Time"), 23); // width for datestring - writeEntry(_("Action"), 10); // 9 chars longest action type - writeEntry(_("Changes"), columnWidth); + writeEntry(idHeader, idWidth); + writeEntry(cmdHeader, cmdLineWidth); + writeEntry(dateHeader, dateWidth); + writeEntry(actionHeader, actionWidth); + writeEntry(changesHeader, changesWidth); std::cout << "\n\n"; // Each entry corresponds to a row - for (auto entry : buf) + for (size_t id = 0; id < buf.size(); ++id) { - writeEntry(std::to_string(id), idWidth); - writeEntry(ShortenCommand(entry.cmdLine, columnWidth), columnWidth); - writeEntry(entry.startDate, 23); + const auto &entry = buf[id]; + writeEntry(id, idWidth); + writeEntry(ShortenCommand(entry.cmdLine, cmdLineWidth), cmdLineWidth); + writeEntry(entry.startDate, dateWidth); // If there are multiple actions, we want to group them - writeEntry(GetKindString(entry), 10); - - // Count the number of packages changed - size_t numChanges = 0; - for (const auto &[_, changes] : entry.changeMap) - numChanges += changes.size(); - writeEntry(std::to_string(numChanges), columnWidth); + writeEntry(kindStrings[id], actionWidth); + writeEntry(changeCounts[id], changesWidth); std::cout << "\n"; - id++; } } @@ -463,7 +503,7 @@ bool DoHistoryList(CommandLine &Cmd) if (not ParseLogDir(buf)) return _error->Error(_("Could not read: %s"), _config->FindFile("Dir::Log::History").data()); - PrintHistoryVector(buf, 25); + PrintHistoryVector(buf); return true; } diff --git a/test/integration/test-history b/test/integration/test-history index 34108e357..48966d011 100755 --- a/test/integration/test-history +++ b/test/integration/test-history @@ -54,20 +54,7 @@ Command line: apt-get install pkg1 Packages changed: Install pkg1:amd64 (1) " normalizedinfo - -# replace aribtrary command line and date with "dummy". -normalizedlist() { - apt history-list | sed -E 's/^([0-9]+)[[:space:]]+'\ -'.*'\ -'[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+'\ -'[0-9]{2}:[0-9]{2}:[0-9]{2}/'\ -'\1 dummy dummy /' -} - -testsuccessequal "ID Command line Date and Time Action Changes -0 dummy dummy Install 1 -1 dummy dummy Install 1 " normalizedlist ################################################################################ # Read previous history for manipulation @@ -118,9 +105,9 @@ Commandline: install nothing End-Date: dummy EOF -testsuccessequal "ID Command line Date and Time Action Changes +testsuccessequal "ID Command line Date and Time Action Changes -0 install nothing dummy 0 " normalizedlist +0 install nothing dummy 0 " apt history-list testsuccessequal "Transaction ID: 0 Start time: dummy @@ -129,6 +116,62 @@ Requested by: Command line: install nothing Packages changed:" normalizedinfo +cat > rootdir/var/log/apt/history.log << EOF +Start-Date: 1970-01-01 00:00:00 +Commandline: install nothing +Install: nothing +End-Date: 1970-01-01 00:00:00 + +Start-Date: 1971-01-01 00:00:00 +Commandline: remove something +Remove: something +End-Date: 1971-01-01 00:00:00 +EOF + +testsuccessequal "ID Command line Date and Time Action Changes + +0 install nothing 1970-01-01 00:00:00 Install 1 +1 remove something 1971-01-01 00:00:00 Remove 1 " apt history-list + +################################################################################ +# Test different column widths +################################################################################ +settestcolumnwidth() { + local columns=$1 + local A_chars + A_chars=$(printf 'a%.0s' $(seq 1 "$columns")) + + cat > rootdir/var/log/apt/history.log << EOF +Start-Date: dummy +Commandline: $A_chars +Install: nothing +End-Date: dummy +EOF + + export COLUMNS="$columns" +} + +settestcolumnwidth 80 +testsuccessequal "ID Command line Date and Time Action Changes + +0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...dummy Install 1 " apt history-list + +settestcolumnwidth 50 +testsuccessequal "ID Command line Date and Time Action Changes + +0 aaaaaaaaaaaaa...dummy Install 1 " apt history-list + +settestcolumnwidth 100 +testsuccessequal "ID Command line Date and Time Action Changes + +0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...dummy Install 1 " apt history-list + +settestcolumnwidth 60 +testsuccessequal "ID Command line Date and Time Action Changes + +0 aaaaaaaaaaaaaaaaaaaaaaa...dummy Install 1 " apt history-list + + testfailureequal "E: Incorrect usage, no ID was given." apt history-redo testfailureequal "E: Incorrect usage, no ID was given." apt history-undo testfailureequal "E: Incorrect usage, no ID was given." apt history-rollback |
