Browse Source

allow all dpkg selections to be set via apt-mark and libapt

As we have support for 'hold', we need support for undoing a hold which
in effect means that we implemented most other states as well, just that
they weren't exposed in the interface directly so far.
David Kalnischkies 8 years ago
parent
commit
64e3414e00

+ 61 - 27
apt-pkg/statechanges.cc

@@ -14,46 +14,62 @@ class StateChanges::Private
 {
 public:
    APT::VersionVector hold;
+   APT::VersionVector unhold;
    APT::VersionVector install;
+   APT::VersionVector deinstall;
+   APT::VersionVector purge;
    APT::VersionVector error;
 };
 
-void StateChanges::Hold(pkgCache::VerIterator const &Ver)
-{
-   d->hold.push_back(Ver);
-}
-APT::VersionVector& StateChanges::Hold()
-{
-   return d->hold;
-}
-void StateChanges::Unhold(pkgCache::VerIterator const &Ver)
-{
-   d->install.push_back(Ver);
-}
-APT::VersionVector& StateChanges::Unhold()
-{
-   return d->install;
+#define APT_GETTERSETTER(Name, Container) \
+void StateChanges::Name(pkgCache::VerIterator const &Ver) \
+{ \
+   Container.push_back(Ver); \
+}\
+APT::VersionVector& StateChanges::Name() \
+{ \
+   return Container; \
 }
+APT_GETTERSETTER(Hold, d->hold)
+APT_GETTERSETTER(Unhold, d->unhold)
+APT_GETTERSETTER(Install, d->install)
+APT_GETTERSETTER(Remove, d->deinstall)
+APT_GETTERSETTER(Purge, d->purge)
+#undef APT_GETTERSETTER
 APT::VersionVector& StateChanges::Error()
 {
    return d->error;
 }
 
-void StateChanges::Discard()
+void StateChanges::clear()
 {
    d->hold.clear();
+   d->unhold.clear();
    d->install.clear();
+   d->deinstall.clear();
+   d->purge.clear();
    d->error.clear();
 }
 
+bool StateChanges::empty() const
+{
+   return d->hold.empty() &&
+      d->unhold.empty() &&
+      d->install.empty() &&
+      d->deinstall.empty() &&
+      d->purge.empty() &&
+      d->error.empty();
+}
+
 bool StateChanges::Save(bool const DiscardOutput)
 {
    d->error.clear();
-   if (d->hold.empty() && d->install.empty())
+   if (d->hold.empty() && d->unhold.empty() && d->install.empty() && d->deinstall.empty() && d->purge.empty())
       return true;
 
    std::vector<std::string> Args = debSystem::GetDpkgBaseCommand();
    // ensure dpkg knows about the package so that it keeps the status we set
+   if (d->hold.empty() == false || d->install.empty() == false)
    {
       APT::VersionVector makeDpkgAvailable;
       auto const notInstalled = [](pkgCache::VerIterator const &V) { return V.ParentPkg()->CurrentVer == 0; };
@@ -94,6 +110,24 @@ bool StateChanges::Save(bool const DiscardOutput)
       else
 	 fprintf(dpkg, "%s:%s %s\n", P.Name(), V.Arch(), state.c_str());
    };
+   for (auto const &V: d->unhold)
+   {
+      if (V.ParentPkg()->CurrentVer != 0)
+	 state = "install";
+      else
+	 state = "deinstall";
+      dpkgName(V);
+   }
+   if (d->purge.empty() == false)
+   {
+      state = "purge";
+      std::for_each(d->purge.begin(), d->purge.end(), dpkgName);
+   }
+   if (d->deinstall.empty() == false)
+   {
+      state = "deinstall";
+      std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName);
+   }
    if (d->hold.empty() == false)
    {
       state = "hold";
@@ -108,16 +142,16 @@ bool StateChanges::Save(bool const DiscardOutput)
 
    if (ExecWait(dpkgSelections, "dpkg --set-selections") == false)
    {
-      if (d->hold.empty())
-	 std::swap(d->install, d->error);
-      else if (d->install.empty())
-	 std::swap(d->hold, d->error);
-      else
-      {
-	 std::swap(d->hold, d->error);
-	 std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error));
-	 d->install.clear();
-      }
+      std::move(d->purge.begin(), d->purge.end(), std::back_inserter(d->error));
+      d->purge.clear();
+      std::move(d->deinstall.begin(), d->deinstall.end(), std::back_inserter(d->error));
+      d->deinstall.clear();
+      std::move(d->hold.begin(), d->hold.end(), std::back_inserter(d->error));
+      d->hold.clear();
+      std::move(d->unhold.begin(), d->unhold.end(), std::back_inserter(d->error));
+      d->unhold.clear();
+      std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error));
+      d->install.clear();
    }
    return d->error.empty();
 }

+ 12 - 6
apt-pkg/statechanges.h

@@ -14,14 +14,20 @@ class APT_PUBLIC StateChanges
 {
 public:
    // getter/setter for the different states
-   APT::VersionVector& Hold();
-   void Hold(pkgCache::VerIterator const &Ver);
-   APT::VersionVector& Unhold();
-   void Unhold(pkgCache::VerIterator const &Ver);
+#define APT_GETTERSETTER(Name) \
+   APT::VersionVector& Name(); \
+   void Name(pkgCache::VerIterator const &Ver)
+   APT_GETTERSETTER(Hold);
+   APT_GETTERSETTER(Unhold);
+   APT_GETTERSETTER(Install);
+   APT_GETTERSETTER(Remove);
+   APT_GETTERSETTER(Purge);
    APT::VersionVector& Error();
+#undef APT_GETTERSETTER
 
-   // forgets all unsaved changes
-   void Discard();
+   // operate on all containers at once
+   void clear();
+   bool empty() const;
 
    /** commit the staged changes to the database(s).
     *

+ 20 - 8
apt-private/private-cmndline.cc

@@ -227,19 +227,31 @@ static bool addArgumentsAPTGet(std::vector<CommandLine::Args> &Args, char const
 static bool addArgumentsAPTMark(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/
 {
    if (CmdMatches("auto", "manual", "hold", "unhold", "showauto",
-	    "showmanual", "showhold", "showholds", "install",
+	    "showmanual", "showhold", "showholds",
 	    "markauto", "unmarkauto"))
+   {
+      addArg('f',"file","Dir::State::extended_states",CommandLine::HasArg);
+   }
+   else if (CmdMatches("install", "remove", "deinstall", "purge",
+	    "showinstall", "showinstalls", "showremove", "showremoves",
+	    "showdeinstall", "showdeinstalls", "showpurge", "showpurges"))
       ;
    else
       return false;
 
-   addArg('v',"verbose","APT::MarkAuto::Verbose",0);
-   addArg('s',"simulate","APT::Mark::Simulate",0);
-   addArg('s',"just-print","APT::Mark::Simulate",0);
-   addArg('s',"recon","APT::Mark::Simulate",0);
-   addArg('s',"dry-run","APT::Mark::Simulate",0);
-   addArg('s',"no-act","APT::Mark::Simulate",0);
-   addArg('f',"file","Dir::State::extended_states",CommandLine::HasArg);
+   if (CmdMatches("markauto", "unmarkauto"))
+   {
+      addArg('v',"verbose","APT::MarkAuto::Verbose",0);
+   }
+
+   if (strncmp(Cmd, "show", strlen("show")) != 0)
+   {
+      addArg('s',"simulate","APT::Mark::Simulate",0);
+      addArg('s',"just-print","APT::Mark::Simulate",0);
+      addArg('s',"recon","APT::Mark::Simulate",0);
+      addArg('s',"dry-run","APT::Mark::Simulate",0);
+      addArg('s',"no-act","APT::Mark::Simulate",0);
+   }
 
    return true;
 }

+ 65 - 37
cmdline/apt-mark.cc

@@ -167,8 +167,8 @@ static bool ShowAuto(CommandLine &CmdL)
    return true;
 }
 									/*}}}*/
-/* DoHold - mark packages as hold by dpkg				{{{*/
-static bool DoHold(CommandLine &CmdL)
+// DoSelection - wrapping around dpkg selections			/*{{{*/
+static bool DoSelection(CommandLine &CmdL)
 {
    pkgCacheFile CacheFile;
    pkgCache *Cache = CacheFile.GetPkgCache();
@@ -179,29 +179,39 @@ static bool DoHold(CommandLine &CmdL)
    if (pkgset.empty() == true)
       return _error->Error(_("No packages found"));
 
-   bool const MarkHold = strcasecmp(CmdL.FileList[0],"hold") == 0;
-
-   auto const part = std::stable_partition(pkgset.begin(), pkgset.end(),
-        [](pkgCache::VerIterator const &V) { return V.ParentPkg()->SelectedState == pkgCache::State::Hold; });
-
-   auto const doneBegin = MarkHold ? pkgset.begin() : part;
-   auto const doneEnd = MarkHold ? part : pkgset.end();
-
-   std::for_each(doneBegin, doneEnd, [&MarkHold](pkgCache::VerIterator const &V) {
-      if (MarkHold == true)
-        ioprintf(c1out, _("%s was already set on hold.\n"), V.ParentPkg().FullName(true).c_str());
-      else
-        ioprintf(c1out, _("%s was already not hold.\n"), V.ParentPkg().FullName(true).c_str());
-   });
-
-   if (doneBegin == pkgset.begin() && doneEnd == pkgset.end())
-      return true;
-
-   auto const changeBegin = MarkHold ? part : pkgset.begin();
-   auto const changeEnd = MarkHold ? pkgset.end() : part;
-
    APT::StateChanges marks;
-   std::move(changeBegin, changeEnd, std::back_inserter(MarkHold ? marks.Hold() : marks.Unhold()));
+   if (strcasecmp(CmdL.FileList[0], "hold") == 0 || strcasecmp(CmdL.FileList[0], "unhold") == 0)
+   {
+      auto const part = std::stable_partition(pkgset.begin(), pkgset.end(),
+	    [](pkgCache::VerIterator const &V) { return V.ParentPkg()->SelectedState == pkgCache::State::Hold; });
+
+      bool const MarkHold = strcasecmp(CmdL.FileList[0],"hold") == 0;
+      auto const doneBegin = MarkHold ? pkgset.begin() : part;
+      auto const doneEnd = MarkHold ? part : pkgset.end();
+      std::for_each(doneBegin, doneEnd, [&MarkHold](pkgCache::VerIterator const &V) {
+	    if (MarkHold == true)
+	    ioprintf(c1out, _("%s was already set on hold.\n"), V.ParentPkg().FullName(true).c_str());
+	    else
+	    ioprintf(c1out, _("%s was already not hold.\n"), V.ParentPkg().FullName(true).c_str());
+	    });
+
+      if (doneBegin == pkgset.begin() && doneEnd == pkgset.end())
+	 return true;
+
+      auto const changeBegin = MarkHold ? part : pkgset.begin();
+      auto const changeEnd = MarkHold ? pkgset.end() : part;
+      std::move(changeBegin, changeEnd, std::back_inserter(MarkHold ? marks.Hold() : marks.Unhold()));
+   }
+   else
+   {
+      // FIXME: Maybe show a message for unchanged states here as well?
+      if (strcasecmp(CmdL.FileList[0], "purge") == 0)
+	 std::swap(marks.Purge(), pkgset);
+      else if (strcasecmp(CmdL.FileList[0], "deinstall") == 0 || strcasecmp(CmdL.FileList[0], "remove") == 0)
+	 std::swap(marks.Remove(), pkgset);
+      else //if (strcasecmp(CmdL.FileList[0], "install") == 0)
+	 std::swap(marks.Install(), pkgset);
+   }
    pkgset.clear();
 
    bool success = true;
@@ -211,30 +221,44 @@ static bool DoHold(CommandLine &CmdL)
       if (success == false)
 	 _error->Error(_("Executing dpkg failed. Are you root?"));
    }
-
    for (auto Ver : marks.Hold())
       ioprintf(c1out,_("%s set on hold.\n"), Ver.ParentPkg().FullName(true).c_str());
    for (auto Ver : marks.Unhold())
       ioprintf(c1out,_("Canceled hold on %s.\n"), Ver.ParentPkg().FullName(true).c_str());
-
+   for (auto Ver : marks.Purge())
+      ioprintf(c1out,_("Selected %s for purge.\n"), Ver.ParentPkg().FullName(true).c_str());
+   for (auto Ver : marks.Remove())
+      ioprintf(c1out,_("Selected %s for removal.\n"), Ver.ParentPkg().FullName(true).c_str());
+   for (auto Ver : marks.Install())
+      ioprintf(c1out,_("Selected %s for installation.\n"), Ver.ParentPkg().FullName(true).c_str());
    return success;
 }
 									/*}}}*/
-/* ShowHold - show packages set on hold in dpkg status			{{{*/
-static bool ShowHold(CommandLine &CmdL)
+static bool ShowSelection(CommandLine &CmdL)				/*{{{*/
 {
    pkgCacheFile CacheFile;
    pkgCache *Cache = CacheFile.GetPkgCache();
    if (unlikely(Cache == NULL))
       return false;
 
+   pkgCache::State::PkgSelectedState selector;
+   if (strncasecmp(CmdL.FileList[0], "showpurge", strlen("showpurge")) == 0)
+      selector = pkgCache::State::Purge;
+   else if (strncasecmp(CmdL.FileList[0], "showdeinstall", strlen("showdeinstall")) == 0 ||
+	 strncasecmp(CmdL.FileList[0], "showremove", strlen("showremove")) == 0)
+      selector = pkgCache::State::DeInstall;
+   else if (strncasecmp(CmdL.FileList[0], "showhold", strlen("showhold")) == 0)
+      selector = pkgCache::State::Hold;
+   else //if (strcasecmp(CmdL.FileList[0], "showinstall", strlen("showinstall")) == 0)
+      selector = pkgCache::State::Install;
+
    std::vector<string> packages;
 
    if (CmdL.FileList[1] == 0)
    {
       packages.reserve(50); // how many holds are realistic? I hope just a few…
       for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P)
-	 if (P->SelectedState == pkgCache::State::Hold)
+	 if (P->SelectedState == selector)
 	    packages.push_back(P.FullName(true));
    }
    else
@@ -243,7 +267,7 @@ static bool ShowHold(CommandLine &CmdL)
       APT::PackageSet pkgset = APT::PackageSet::FromCommandLine(CacheFile, CmdL.FileList + 1, helper);
       packages.reserve(pkgset.size());
       for (APT::PackageSet::const_iterator P = pkgset.begin(); P != pkgset.end(); ++P)
-	 if (P->SelectedState == pkgCache::State::Hold)
+	 if (P->SelectedState == selector)
 	    packages.push_back(P.FullName(true));
    }
 
@@ -295,15 +319,19 @@ int main(int argc,const char *argv[])					/*{{{*/
    CommandLine::Dispatch Cmds[] = {{"help",&ShowHelp},
 				   {"auto",&DoAuto},
 				   {"manual",&DoAuto},
-				   {"hold",&DoHold},
-				   {"unhold",&DoHold},
+				   {"hold",&DoSelection},
+				   {"unhold",&DoSelection},
+				   {"install",&DoSelection},
+				   {"remove",&DoSelection}, // dpkg uses deinstall, but we use remove everywhere else
+				   {"deinstall",&DoSelection},
+				   {"purge",&DoSelection},
 				   {"showauto",&ShowAuto},
 				   {"showmanual",&ShowAuto},
-				   {"showhold",&ShowHold},
-				   // be nice and forgive the typo
-				   {"showholds",&ShowHold},
-				   // be nice and forgive it as it is technical right
-				   {"install",&DoHold},
+				   {"showhold",&ShowSelection}, {"showholds",&ShowSelection},
+				   {"showinstall",&ShowSelection}, {"showinstalls",&ShowSelection},
+				   {"showdeinstall",&ShowSelection}, {"showdeinstalls",&ShowSelection},
+				   {"showremove",&ShowSelection}, {"showremoves",&ShowSelection},
+				   {"showpurge",&ShowSelection}, {"showpurges",&ShowSelection},
 				   // obsolete commands for compatibility
 				   {"markauto", &DoMarkAuto},
 				   {"unmarkauto", &DoMarkAuto},

+ 64 - 46
doc/apt-mark.8.xml

@@ -14,7 +14,7 @@
    &apt-email;
    &apt-product;
    <!-- The last update date -->
-   <date>2012-06-09T00:00:00Z</date>
+   <date>2015-09-25T00:00:00Z</date>
  </refentryinfo>
  
  <refmeta>
@@ -26,31 +26,37 @@
  <!-- Man page title -->
  <refnamediv>
     <refname>apt-mark</refname>
-    <refpurpose>mark/unmark a package as being automatically-installed</refpurpose>
+    <refpurpose>show, set and unset various settings for a package</refpurpose>
  </refnamediv>
 
  &synopsis-command-apt-mark;
 
  <refsect1><title>Description</title>
-   <para><command>apt-mark</command> will change whether a package has
-   been marked as being automatically installed.
+    <para><command>apt-mark</command> can be used as a unified frontend to set
+       various settings for a package like marking a package as being
+       automatically/manually installed or changing <command>dpkg</command>
+       selections such as hold, install, deinstall and purge which are respected
+       e.g. by <command>apt-get dselect-upgrade</command> or <command>aptitude</command>.
    </para>
+ </refsect1><refsect1><title>Automatically and manually installed packages</title>
    <para>
      When you request that a package is installed, and as a result
      other packages are installed to satisfy its dependencies, the
-     dependencies are marked as being automatically installed.  Once
-     these automatically installed packages are no longer depended on
-     by any manually installed packages, they will be removed by e.g.
-     <command>apt-get</command> or <command>aptitude</command>.
+     dependencies are marked as being automatically installed, while
+     package you installed explicitely is marked as manually installed.
+     Once a automatically installed package is no longer depended on
+     by any manually installed package it is considered no longer needed
+     and e.g. <command>apt-get</command> or <command>aptitude</command>
+     will at least suggest removing them.
    </para>
-     <variablelist>
+   <variablelist>
      <varlistentry><term><option>auto</option></term>
 	 <listitem><para><literal>auto</literal> is used to mark a
      package as being automatically installed, which will cause the
      package to be removed when no more manually installed packages
      depend on this package.
        </para></listitem>
-       </varlistentry>
+     </varlistentry>
 
      <varlistentry><term><option>manual</option></term>
 	 <listitem><para><literal>manual</literal> is used to mark a
@@ -58,23 +64,7 @@
      package from being automatically removed if no other packages
      depend on it.
        </para></listitem>
-       </varlistentry>
-
-       <varlistentry><term><option>hold</option></term>
-	 <listitem><para><literal>hold</literal> is used to mark a
-       package as held back, which will prevent the package from being
-       automatically installed, upgraded or removed.
-       The command is only a wrapper around <command>dpkg --set-selections</command>
-       and the state is therefore maintained by &dpkg; and not affected
-       by the <option>--file</option> option.
-       </para></listitem>
-       </varlistentry>
-
-       <varlistentry><term><option>unhold</option></term>
-	 <listitem><para><literal>unhold</literal> is used to cancel a
-       previously set hold on a package to allow all actions again.
-       </para></listitem>
-       </varlistentry>
+     </varlistentry>
 
        <varlistentry><term><option>showauto</option></term>
 	 <listitem><para><literal>showauto</literal> is used to print a
@@ -82,38 +72,66 @@
      All automatically installed packages will be listed if no package is given.
      If packages are given only those which are automatically installed will be shown.
        </para></listitem>
-       </varlistentry>
+     </varlistentry>
 
        <varlistentry><term><option>showmanual</option></term>
 	 <listitem><para><literal>showmanual</literal> can be used in
      the same way as <literal>showauto</literal> except that it will print
      a list of manually installed packages instead.
        </para></listitem>
+     </varlistentry>
+   </variablelist>
+
+   <refsect2><title>Options</title>
+     <variablelist>
+       <varlistentry>
+	 <term><option>-f=&synopsis-param-filename;</option></term>
+	 <term><option>--file=&synopsis-param-filename;</option></term>
+	 <listitem><para>
+	   Read/Write package stats from the filename given with the parameter
+	   &synopsis-param-filename; instead of from the default location, which
+	   is <filename>extended_status</filename> in the directory defined
+	   by the Configuration Item: <literal>Dir::State</literal>.
+	 </para></listitem>
        </varlistentry>
+     </variablelist>
+   </refsect2>
 
-       <varlistentry><term><option>showhold</option></term>
-	 <listitem><para><literal>showhold</literal> is used to print a list
-     of packages on hold in the same way as for the other show commands.
+ </refsect1><refsect1><title>Prevent changes for a package</title>
+   <variablelist>
+     <varlistentry><term><option>hold</option></term>
+	 <listitem><para><literal>hold</literal> is used to mark a
+     package as held back, which will prevent the package from being
+     automatically installed, upgraded or removed.
        </para></listitem>
-       </varlistentry>
+     </varlistentry>
 
+     <varlistentry><term><option>unhold</option></term>
+	 <listitem><para><literal>unhold</literal> is used to cancel a
+     previously set hold on a package to allow all actions again.
+       </para></listitem>
+     </varlistentry>
 
-     </variablelist>
- </refsect1>
- 
- <refsect1><title>options</title>
-   
-   <variablelist>
-	<varlistentry>
-	   <term><option>-f=&synopsis-param-filename;</option></term>
-	   <term><option>--file=&synopsis-param-filename;</option></term>
-     <listitem><para>
-     Read/Write package stats from the filename given with the parameter
-     &synopsis-param-filename; instead of from the default location, which
-     is <filename>extended_status</filename> in the directory defined
-     by the Configuration Item: <literal>Dir::State</literal>.</para></listitem>
+     <varlistentry><term><option>showhold</option></term>
+	 <listitem><para><literal>showhold</literal> is used to print a list
+     of packages on hold in the same way as for the other show commands.
+       </para></listitem>
      </varlistentry>
+   </variablelist>
+ </refsect1><refsect1><title>Shedule packages for install, remove and purge</title>
+ <para>
+    Some frontends like <command>apt-get dselect-upgrade</command> can be used to
+    apply previously sheduled changes to the install state of packages. Such changes
+    can be sheduled with the <option>install</option>, <option>remove</option>
+    (also known as <option>deinstall</option>) and <option>purge</option> commands.
+    Packages with a specific selection can be displayed with <option>showinstall</option>,
+    <option>showremove</option> and <option>showpurge</option> respectively.
+    More information about these so called dpkg selections can be found in &dpkg;.
+ </para>
+ </refsect1>
 
+ <refsect1><title>Options</title>
+   <variablelist>
      &apt-commonoptions;
    </variablelist>
  </refsect1>

+ 35 - 6
doc/apt-verbatim.ent

@@ -394,18 +394,47 @@
 </cmdsynopsis></refsynopsisdiv>">
 <!ENTITY synopsis-command-apt-mark "<refsynopsisdiv><cmdsynopsis>
 	<command>apt-mark</command>
-	<arg><option>-f=&synopsis-param-filename;</option></arg>
+	<group choice='plain'>
+		<group choice='req'>
+			<arg choice='plain'><option>-f=&synopsis-param-filename;</option></arg>
+			<arg choice='plain'>
+				<group choice='req'>
+					<arg choice='plain'>auto</arg>
+					<arg choice='plain'>manual</arg>
+				</group>
+				&synopsis-arg-pkg;
+			</arg>
+			<arg choice='plain'>
+				<group choice='req'>
+					<arg choice='plain'>showauto</arg>
+					<arg choice='plain'>showmanual</arg>
+				</group>
+				<arg choice='opt' rep='repeat'><replaceable>&synopsis-pkg;</replaceable></arg>
+			</arg>
+		</group>
+		&synopsis-help;
+	</group>
+</cmdsynopsis><cmdsynopsis><command>apt-mark</command>
 	<group choice='plain'>
 		<arg choice='plain'>
 			<group choice='req'>
-				<arg choice='plain'>auto</arg>
-				<arg choice='plain'>manual</arg>
-				<arg choice='plain'>showauto</arg>
-				<arg choice='plain'>showmanual</arg>
+				<arg choice='plain'>hold</arg>
+				<arg choice='plain'>unhold</arg>
+				<arg choice='plain'>install</arg>
+				<arg choice='plain'>remove</arg>
+				<arg choice='plain'>purge</arg>
 			</group>
 			&synopsis-arg-pkg;
 		</arg>
-		&synopsis-help;
+		<arg choice='plain'>
+			<group choice='req'>
+				<arg choice='plain'>showhold</arg>
+				<arg choice='plain'>showinstall</arg>
+				<arg choice='plain'>showremove</arg>
+				<arg choice='plain'>showpurge</arg>
+			</group>
+			<arg choice='opt' rep='repeat'><replaceable>&synopsis-pkg;</replaceable></arg>
+		</arg>
 	</group>
 </cmdsynopsis></refsynopsisdiv>">
 <!ENTITY synopsis-command-apt-sortpkgs "<refsynopsisdiv><cmdsynopsis>

+ 40 - 0
test/integration/test-apt-mark

@@ -112,3 +112,43 @@ Inst uninstalled (1 unstable [all])
 Inst uninstalled-native (1 unstable [amd64])
 Conf uninstalled (1 unstable [all])
 Conf uninstalled-native (1 unstable [amd64])' aptget install uninstalled uninstalled-native -s
+testsuccess aptmark unhold uninstalled uninstalled-native
+
+testselections() {
+	testsuccess aptmark hold "$1"
+	testsuccessequal "$1" aptmark showholds "$1"
+	testsuccess aptmark unhold "$1"
+	testsuccessequal "$1" aptmark showinstalls "$1"
+	testsuccess aptmark hold "$1"
+	testsuccessequal "$1" aptmark showholds "$1"
+	testsuccess aptmark install "$1"
+	testsuccessequal "$1" aptmark showinstalls "$1"
+	testsuccess aptmark remove "$1"
+	testsuccessequal "$1" aptmark showremoves "$1"
+	testsuccess aptmark purge "$1"
+	testsuccessequal "$1" aptmark showpurges "$1"
+}
+testselections 'foo'
+testselections 'bar'
+
+testsuccessequal 'Reading package lists...
+Building dependency tree...
+Reading state information...
+The following packages will be REMOVED:
+  bar* foo*
+0 upgraded, 0 newly installed, 2 to remove and 0 not upgraded.
+Purg bar [1]
+Purg foo [1]' aptget dselect-upgrade -s
+
+testuninstalledselections() {
+	testsuccess aptmark hold "$1"
+	testsuccessequal "$1" aptmark showholds "$1"
+	testsuccess aptmark unhold "$1"
+	testsuccessequal "$1" aptmark showremoves "$1"
+	testsuccess aptmark hold "$1"
+	testsuccessequal "$1" aptmark showholds "$1"
+	testsuccess aptmark install "$1"
+	testsuccessequal "$1" aptmark showinstalls "$1"
+}
+testuninstalledselections 'uninstalled'
+testuninstalledselections 'uninstalled-native'