[patch] auto dependency removal (updated patch)

Michael Vogt mvo at ubuntu.com
Thu Jun 29 09:14:38 PDT 2006


On Wed, Jun 28, 2006 at 06:30:23PM -0300, Gustavo Niemeyer wrote:
> > unused dependencies. The patch is based on code from Gustavo (thanks!)
> > and uses the techniques pioneered by Daniel Burrows in aptitude, all
> > the bugs are added by me.
> 
> Hey, don't take all the credit. I'm sure some bugs there were
> introduced by me as well. :-)
> 
> Thanks for the great work Michael, I'll have a look at it and try
> it out as soon as possible.

Thanks Gustavo! Please make sure that you use the attached (updated)
version. The new version supports the upgrade case better (downgrade
is still missing). It will carry a "auto" flag to the new version on
upgrade (if the old version had it). It will also make sure that only
newly installed packages can get the "auto" flag (the old version
assigned the flag on upgraded packages too). There are still two FIXME
in the code, one for the downgrade case and one of safety check to see
if a "we-could-auto-remove-this" changeset is ok.

Cheers,
 Michael

P.S. The patch is against current svn and may not apply cleanly to
0.42. 
-- 
Linux is not The Answer. Yes is the answer. Linux is The Question. - Neo
-------------- next part --------------
Index: smart/commands/remove.py
===================================================================
--- smart/commands/remove.py	(revision 734)
+++ smart/commands/remove.py	(working copy)
@@ -56,6 +56,9 @@
                              "when possible"))
     parser.add_option("-y", "--yes", action="store_true",
                       help=_("do not ask for confirmation"))
+    parser.add_option("--auto", action="store_true",
+                      help=_("use the auto-install information to "
+                             "remove stuff."))
     opts, args = parser.parse_args(argv)
     opts.args = args
     return opts
@@ -69,6 +72,12 @@
     cache = ctrl.getCache()
     trans = Transaction(cache, PolicyRemove)
     policy = trans.getPolicy()
+
+    if opts.auto:
+        rmcs = ctrl.markAndSweep()
+        confirm = not opts.yes
+        ctrl.commitChangeSet(rmcs, confirm=confirm)
+    
     for arg in opts.args:
 
         ratio, results, suggestions = ctrl.search(arg)
Index: smart/control.py
===================================================================
--- smart/control.py	(revision 735)
+++ smart/control.py	(working copy)
@@ -396,6 +396,43 @@
         self._pathlocks.lock(channelsdir)
         return result
 
+    def markAndSweep(self, changeset=None):
+        if changeset==None:
+            changeset={}
+        # Mark ...
+        all = self._cache.getPackages()
+        auto = pkgconf.filterByFlag("auto", all)
+        marked = {}
+        for pkg in all:
+            if (pkg.installed and pkg not in auto and
+                changeset.get(pkg) != REMOVE):
+                marked[pkg] = True
+        queue = marked.keys()
+        while queue:
+            pkg = queue.pop(0)
+            for req in pkg.requires:
+                for prv in req.providedby:
+                    for prvpkg in prv.packages:
+                        if (prvpkg.installed and
+                            prvpkg not in marked and
+                            prvpkg not in changeset):
+                            marked[prvpkg] = True
+                            queue.append(prvpkg)
+        # see what is not marked
+        suggestions = ChangeSet(self._cache)
+        for pkg in all:
+            if pkg.installed and pkg not in marked:
+                suggestions[pkg] = REMOVE
+        # FIXME: we should probably check here is those suggestions
+        #        would break the relations in the cache and bail out
+        #        if that happens 
+
+        #if suggestions:
+        #    accepted = iface.suggestChanges(suggestions)
+        #    if accepted:
+        #        changeset.update(accepted)
+        return suggestions
+
     def dumpTransactionURLs(self, trans, output=None):
         changeset = trans.getChangeSet()
         self.dumpURLs([x for x in changeset if changeset[x] is INSTALL])
@@ -530,7 +567,9 @@
                     for pkg in pmpkgs[pmclass]:
                         if pkg in cs:
                             pmcs[pkg] = cs[pkg]
+                            pmcs.setRequested(pkg, cs.getRequested(pkg))
                     if sysconf.get("commit", True):
+                        pmcs.markPackagesAutoInstalled()
                         self.writeCommitLog(pmcs)
                         pmclass().commit(pmcs, pkgpaths)
 
Index: smart/transaction.py
===================================================================
--- smart/transaction.py	(revision 734)
+++ smart/transaction.py	(working copy)
@@ -25,16 +25,31 @@
 
 class ChangeSet(dict):
 
-    def __init__(self, cache, state=None):
+    def __init__(self, cache, state=None, requested=None):
         self._cache = cache
+        self._requested = {}
         if state:
             self.update(state)
+        if requested:
+            self._requested.update(requested)
 
+    def clear(self):
+        dict.clear(self)
+        self._requested.clear()
+
+    def update(self, other):
+        dict.update(self, other)
+        if type(other) is ChangeSet:
+            self._requested.update(other._requested)
+
+    def copy(self):
+        return ChangeSet(self._cache, self, self._requested)
+
     def getCache(self):
         return self._cache
 
     def getState(self):
-        return self.copy()
+        return (self.copy(), self._requested.copy())
 
     def setState(self, state):
         if state is not self:
@@ -44,19 +59,67 @@
     def getPersistentState(self):
         state = {}
         for pkg in self:
-            state[(pkg.__class__, pkg.name, pkg.version)] = self[pkg]
+            req = pkg in self._requested
+            state[(pkg.__class__, pkg.name, pkg.version)] = self[pkg], req
         return state
 
     def setPersistentState(self, state):
         self.clear()
         for pkg in self._cache.getPackages():
-            op = state.get((pkg.__class__, pkg.name, pkg.version))
-            if op is not None:
+            tup = state.get((pkg.__class__, pkg.name, pkg.version))
+            if tup is not None:
+                op, req = tup
                 self[pkg] = op
+                if req:
+                    self._requested[pkg] = True
 
-    def copy(self):
-        return ChangeSet(self._cache, self)
+    def markPackagesAutoInstalled(self):
+        """ Mark all packages that have been auto-installed permanently
+            and reset the flag if the package was removed again. Also
+            make sure that the auto-flag is carried to the new pkg on
+            upgrades/downgrades.
+        """
+        # we need to take care of upgrades here, a changeset represents
+        # upgrades as a series of REMOVE old-version INSTALL new-version
 
+        upgrade = {}
+        # find out about the upgrades/downgrades
+        # FIXME: cover the downgrade case too
+        for pkg in self:
+            if self[pkg] is REMOVE:
+                # check if this remove is because the pkg is upgrades
+                # and if that is the case, move the "auto" flag to the
+                # (upgraded) package and rember that this was a upgraded
+                # pkg
+                for prv in pkg.provides:
+                    for upg in prv.upgradedby:
+                        for upgpkg in upg.packages:
+                            if self.get(upgpkg) is INSTALL:
+                                upgrade[upgpkg] = True
+                                if pkgconf.testFlag("auto", pkg):
+                                    pkgconf.setFlag("auto",
+                                                    upgpkg.name, "=",
+                                                    upgpkg.version)
+                pkgconf.clearFlag("auto", pkg.name)
+        # do the auto-marking on new installs that where not explicitly
+        # requested
+        for pkg in self:
+            if (self[pkg] is INSTALL and
+                not pkg in upgrade and
+                not self.getRequested(pkg)):
+                pkgconf.setFlag("auto",
+                                pkg.name, "=", pkg.version)
+
+    def getRequested(self, pkg):
+        return pkg in self._requested
+
+    def setRequested(self, pkg, flag):
+        assert pkg in self
+        if flag:
+            self._requested[pkg] = True
+        elif pkg in self._requested:
+            del self._requested[pkg]
+
     def set(self, pkg, op, force=False):
         if self.get(pkg) is op:
             return
@@ -66,12 +129,16 @@
             else:
                 if pkg in self:
                     del self[pkg]
+                    if pkg in self._requested:
+                        del self._requested[pkg]
         else:
             if force or pkg.installed:
                 self[pkg] = REMOVE
             else:
                 if pkg in self:
                     del self[pkg]
+                    if pkg in self._requested:
+                        del self._requested[pkg]
 
     def installed(self, pkg):
         op = self.get(pkg)
@@ -83,6 +150,8 @@
             sop = self[pkg]
             if sop is not other.get(pkg):
                 diff[pkg] = sop
+                if pkg in self._requested:
+                    diff._requested[pkg] = True
         return diff
 
     def intersect(self, other):
@@ -91,6 +160,8 @@
             sop = self[pkg]
             if sop is other.get(pkg):
                 isct[pkg] = sop
+                if pkg in self._requested:
+                    isct._requested[pkg] = True
         return isct
 
     def __str__(self):
@@ -1105,6 +1176,8 @@
                         op = REMOVE
                 if op is INSTALL or op is REINSTALL:
                     self._install(pkg, changeset, locked, pending)
+                    if pkg in changeset:
+                        changeset.setRequested(pkg, True)
                 elif op is REMOVE:
                     self._remove(pkg, changeset, locked, pending)
                 elif op is UPGRADE:


More information about the Smart mailing list