Browse Source

Add recursive removal of packages

Reeto Chatterjee 7 years ago
parent
commit
1b704a869d
3 changed files with 76 additions and 17 deletions
  1. 2 8
      clean.go
  2. 3 1
      cmd.go
  3. 71 8
      query.go

+ 2 - 8
clean.go

@@ -20,16 +20,13 @@ func removeVCSPackage(pkgs []string) {
 }
 
 // CleanDependencies removes all dangling dependencies in system
-func cleanDependencies() error {
-	hanging, err := hangingPackages()
+func cleanDependencies(removeOptional bool) error {
+	hanging, err := hangingPackages(removeOptional)
 	if err != nil {
 		return err
 	}
 
 	if len(hanging) != 0 {
-		if !continueTask("Confirm Removal?", "nN") {
-			return nil
-		}
 		err = cleanRemove(hanging)
 	}
 
@@ -42,12 +39,9 @@ func cleanRemove(pkgNames []string) (err error) {
 		return nil
 	}
 
-	oldvalue := config.NoConfirm
-	config.NoConfirm = true
 	arguments := makeArguments()
 	arguments.addArg("R")
 	arguments.addTarget(pkgNames...)
 	err = passToPacman(arguments)
-	config.NoConfirm = oldvalue
 	return err
 }

+ 3 - 1
cmd.go

@@ -291,8 +291,10 @@ func handleYay() (err error) {
 		if err != nil {
 			return
 		}
+	} else if cmdArgs.existsDouble("c") {
+		err = cleanDependencies(true)
 	} else if cmdArgs.existsArg("c", "clean") {
-		err = cleanDependencies()
+		err = cleanDependencies(false)
 	} else if len(cmdArgs.targets) > 0 {
 		err = handleYogurt()
 	}

+ 71 - 8
query.go

@@ -274,26 +274,89 @@ func packageSlices(toCheck []string) (aur []string, repo []string, err error) {
 
 // HangingPackages returns a list of packages installed as deps
 // and unneeded by the system
-func hangingPackages() (hanging []string, err error) {
+// removeOptional decides whether optional dependencies are counted or not
+func hangingPackages(removeOptional bool) (hanging []string, err error) {
 	localDb, err := alpmHandle.LocalDb()
 	if err != nil {
 		return
 	}
 
-	f := func(pkg alpm.Package) error {
-		if pkg.Reason() != alpm.PkgReasonDepend {
+	// safePackages represents every package in the system in one of 3 states
+	// State = 0 - Remove package from the system
+	// State = 1 - Keep package in the system; need to iterate over dependencies
+	// State = 2 - Keep package and have iterated over dependencies
+	safePackages := make(map[string]uint8)
+	// provides stores a mapping from the provides name back to the original package name
+	// Assumption - multiple installed packages don't provide the same dependency
+	provides := make(map[string]string)
+	packages := localDb.PkgCache()
+
+	// Mark explicit dependencies and enumerate the provides list
+	setupResources := func(pkg alpm.Package) error {
+		if pkg.Reason() == alpm.PkgReasonExplicit {
+			safePackages[pkg.Name()] = 1
+		} else {
+			safePackages[pkg.Name()] = 0
+		}
+
+		pkg.Provides().ForEach(func(dep alpm.Depend) error {
+			provides[dep.Name] = pkg.Name()
+			return nil
+		})
+		return nil
+	}
+	packages.ForEach(setupResources)
+
+	iterateAgain := true
+	processDependencies := func(pkg alpm.Package) error {
+		if state, _ := safePackages[pkg.Name()]; state == 0 || state == 2 {
 			return nil
 		}
-		requiredby := pkg.ComputeRequiredBy()
-		if len(requiredby) == 0 {
-			hanging = append(hanging, pkg.Name())
-			fmt.Println(pkg.Name() + ": " + magenta(human(pkg.ISize())))
 
+		safePackages[pkg.Name()] = 2
+
+		// Update state for dependencies
+		markDependencies := func(dep alpm.Depend) error {
+			// Don't assume a dependency is installed
+			state, ok := safePackages[dep.Name]
+			if !ok {
+				// Check if dep is a provides rather than actual package name
+				if p, ok2 := provides[dep.Name]; ok2 {
+					iterateAgain = true
+					safePackages[p] = 1
+				}
+
+				return nil
+			}
+
+			if state == 0 {
+				iterateAgain = true
+				safePackages[dep.Name] = 1
+			}
+			return nil
+		}
+
+		pkg.Depends().ForEach(markDependencies)
+		if !removeOptional {
+			pkg.OptionalDepends().ForEach(markDependencies)
 		}
 		return nil
 	}
 
-	err = localDb.PkgCache().ForEach(f)
+	for iterateAgain {
+		iterateAgain = false
+		packages.ForEach(processDependencies)
+	}
+
+	// Build list of packages to be removed
+	packages.ForEach(func(pkg alpm.Package) error {
+		if safePackages[pkg.Name()] == 0 {
+			hanging = append(hanging, pkg.Name())
+		}
+		return nil
+	})
+
+
 	return
 }