summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Andres Klode <jak@debian.org>2026-04-07 09:39:55 +0000
committerJulian Andres Klode <jak@debian.org>2026-04-07 09:39:55 +0000
commit9a97c0e647609da41a1df6574d98320e5e253988 (patch)
tree2eba28e277f4ad7ab56f8d015decd09d0712fb2f
parentb927ef91b5f6d682d7fb5e8fd8a2d5e4d7292329 (diff)
parent58821c2dfc9fefb09d6ff6655e435bacdfabe173 (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.cc116
-rwxr-xr-xtest/integration/test-history73
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