Repository: helm/helm
Pull request #8299 (2020-06-12 01:33:30)

Add unit test case
Signed-off-by: ZouYu <zouy.fnst@cn.fujitsu.com>

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/master/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:
Add unit test case for TestEqual() in internal/urlutil/urlutil_test.go
**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains documentation
- [x] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility


bacongobbler: Hi there. What’s the reason for these unit tests? Is there a case where this behaviour is not covered by other tests, or some code path this test case covers?
Hellcatlk: @bacongobbler , this test cases cover these [code path](https://github.com/helm/helm/blob/master/internal/urlutil/urlutil.go#L46-L51).
bacongobbler: How is that not tested by the test case on line 58?
Hellcatlk: I think so.
```
Without `line 59-60`
Running tool: /usr/local/go/bin/go test -timeout 30s -coverprofile=/tmp/vscode-gonkzK6N/go-code-cover helm.sh/helm/v3/internal/urlutil

ok  	helm.sh/helm/v3/internal/urlutil	0.002s	coverage: 75.0% of statements
```
```
Running tool: /usr/local/go/bin/go test -timeout 30s -coverprofile=/tmp/vscode-gonkzK6N/go-code-cover helm.sh/helm/v3/internal/urlutil

ok  	helm.sh/helm/v3/internal/urlutil	0.004s	coverage: 87.5% of statements
```

Patch:
diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go
index d4cd88fef..6b9c3050b 100644
--- a/cmd/helm/flags.go
+++ b/cmd/helm/flags.go
@@ -18,12 +18,12 @@ package main
 
 import (
 	"fmt"
-	"log"
 	"strings"
 
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/cli/output"
 	"helm.sh/helm/v3/pkg/cli/values"
@@ -55,22 +55,19 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
 // bindOutputFlag will add the output flag to the given command and bind the
 // value to the given format pointer
 func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
-	cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o",
+	f := cmd.Flags()
+	flag := f.VarPF(newOutputValue(output.Table, varRef), outputFlag, "o",
 		fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", ")))
 
-	err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		var formatNames []string
 		for _, format := range output.Formats() {
 			if strings.HasPrefix(format, toComplete) {
 				formatNames = append(formatNames, format)
 			}
 		}
-		return formatNames, cobra.ShellCompDirectiveDefault
+		return formatNames, completion.BashCompDirectiveDefault
 	})
-
-	if err != nil {
-		log.Fatal(err)
-	}
 }
 
 type outputValue output.Format
diff --git a/cmd/helm/get_all.go b/cmd/helm/get_all.go
index 1ebc7e387..7d893d7e0 100644
--- a/cmd/helm/get_all.go
+++ b/cmd/helm/get_all.go
@@ -18,11 +18,11 @@ package main
 
 import (
 	"io"
-	"log"
 
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/cli/output"
 )
@@ -41,12 +41,6 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "download all information for a named release",
 		Long:  getAllHelp,
 		Args:  require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			res, err := client.Run(args[0])
 			if err != nil {
@@ -63,19 +57,24 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 	f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
-	err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	flag := f.Lookup("revision")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if len(args) == 1 {
 			return compListRevisions(cfg, args[0])
 		}
-		return nil, cobra.ShellCompDirectiveNoFileComp
+		return nil, completion.BashCompDirectiveNoFileComp
 	})
 
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	f.StringVar(&template, "template", "", "go template for formatting the output, eg: {{.Release.Name}}")
 
 	return cmd
diff --git a/cmd/helm/get_hooks.go b/cmd/helm/get_hooks.go
index 6453c30eb..c2087b1ba 100644
--- a/cmd/helm/get_hooks.go
+++ b/cmd/helm/get_hooks.go
@@ -19,11 +19,11 @@ package main
 import (
 	"fmt"
 	"io"
-	"log"
 
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 )
 
@@ -41,12 +41,6 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "download all hooks for a named release",
 		Long:  getHooksHelp,
 		Args:  require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			res, err := client.Run(args[0])
 			if err != nil {
@@ -59,17 +53,23 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
-	cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
-	err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
+	f := cmd.Flags()
+	f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
+	flag := f.Lookup("revision")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if len(args) == 1 {
 			return compListRevisions(cfg, args[0])
 		}
-		return nil, cobra.ShellCompDirectiveNoFileComp
+		return nil, completion.BashCompDirectiveNoFileComp
 	})
 
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	return cmd
 }
diff --git a/cmd/helm/get_manifest.go b/cmd/helm/get_manifest.go
index 048bf36b0..f332befd9 100644
--- a/cmd/helm/get_manifest.go
+++ b/cmd/helm/get_manifest.go
@@ -19,11 +19,11 @@ package main
 import (
 	"fmt"
 	"io"
-	"log"
 
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 )
 
@@ -43,12 +43,6 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
 		Short: "download the manifest for a named release",
 		Long:  getManifestHelp,
 		Args:  require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			res, err := client.Run(args[0])
 			if err != nil {
@@ -59,17 +53,23 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
 		},
 	}
 
-	cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
-	err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
+	f := cmd.Flags()
+	f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
+	flag := f.Lookup("revision")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if len(args) == 1 {
 			return compListRevisions(cfg, args[0])
 		}
-		return nil, cobra.ShellCompDirectiveNoFileComp
+		return nil, completion.BashCompDirectiveNoFileComp
 	})
 
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	return cmd
 }
diff --git a/cmd/helm/get_notes.go b/cmd/helm/get_notes.go
index 88d494fc4..4491bd9ba 100644
--- a/cmd/helm/get_notes.go
+++ b/cmd/helm/get_notes.go
@@ -19,11 +19,11 @@ package main
 import (
 	"fmt"
 	"io"
-	"log"
 
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 )
 
@@ -39,12 +39,6 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "download the notes for a named release",
 		Long:  getNotesHelp,
 		Args:  require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			res, err := client.Run(args[0])
 			if err != nil {
@@ -57,18 +51,23 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 	f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
-	err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	flag := f.Lookup("revision")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if len(args) == 1 {
 			return compListRevisions(cfg, args[0])
 		}
-		return nil, cobra.ShellCompDirectiveNoFileComp
+		return nil, completion.BashCompDirectiveNoFileComp
 	})
 
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	return cmd
 }
diff --git a/cmd/helm/get_values.go b/cmd/helm/get_values.go
index fa7b10fed..a8c5acc5e 100644
--- a/cmd/helm/get_values.go
+++ b/cmd/helm/get_values.go
@@ -19,11 +19,11 @@ package main
 import (
 	"fmt"
 	"io"
-	"log"
 
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/cli/output"
 )
@@ -46,12 +46,6 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "download the values file for a named release",
 		Long:  getValuesHelp,
 		Args:  require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			vals, err := client.Run(args[0])
 			if err != nil {
@@ -61,19 +55,23 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 	f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
-	err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	flag := f.Lookup("revision")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if len(args) == 1 {
 			return compListRevisions(cfg, args[0])
 		}
-		return nil, cobra.ShellCompDirectiveNoFileComp
+		return nil, completion.BashCompDirectiveNoFileComp
 	})
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	f.BoolVarP(&client.AllValues, "all", "a", false, "dump all (computed) values")
 	bindOutputFlag(cmd, &outfmt)
 
diff --git a/cmd/helm/history.go b/cmd/helm/history.go
index b49c52990..3ef542e58 100644
--- a/cmd/helm/history.go
+++ b/cmd/helm/history.go
@@ -26,6 +26,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/cli/output"
@@ -60,12 +61,6 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short:   "fetch release history",
 		Aliases: []string{"hist"},
 		Args:    require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			history, err := getHistory(client, args[0])
 			if err != nil {
@@ -76,6 +71,14 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 	f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history")
 	bindOutputFlag(cmd, &outfmt)
@@ -184,7 +187,7 @@ func min(x, y int) int {
 	return y
 }
 
-func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
+func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, completion.BashCompDirective) {
 	client := action.NewHistory(cfg)
 
 	var revisions []string
@@ -192,7 +195,7 @@ func compListRevisions(cfg *action.Configuration, releaseName string) ([]string,
 		for _, release := range hist {
 			revisions = append(revisions, strconv.Itoa(release.Version))
 		}
-		return revisions, cobra.ShellCompDirectiveDefault
+		return revisions, completion.BashCompDirectiveDefault
 	}
-	return nil, cobra.ShellCompDirectiveError
+	return nil, completion.BashCompDirectiveError
 }
diff --git a/cmd/helm/install.go b/cmd/helm/install.go
index b489c262d..44f7336c0 100644
--- a/cmd/helm/install.go
+++ b/cmd/helm/install.go
@@ -26,6 +26,7 @@ import (
 	"github.com/spf13/pflag"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/chart/loader"
@@ -113,9 +114,6 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "install a chart",
 		Long:  installDesc,
 		Args:  require.MinimumNArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return compInstall(args, toComplete, client)
-		},
 		RunE: func(_ *cobra.Command, args []string) error {
 			rel, err := runInstall(args, client, valueOpts, out)
 			if err != nil {
@@ -126,6 +124,11 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		return compInstall(args, toComplete, client)
+	})
+
 	addInstallFlags(cmd.Flags(), client, valueOpts)
 	bindOutputFlag(cmd, &outfmt)
 	bindPostRenderFlag(cmd, &client.PostRenderer)
@@ -239,7 +242,7 @@ func isChartInstallable(ch *chart.Chart) (bool, error) {
 }
 
 // Provide dynamic auto-completion for the install and template commands
-func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) {
+func compInstall(args []string, toComplete string, client *action.Install) ([]string, completion.BashCompDirective) {
 	requiredArgs := 1
 	if client.GenerateName {
 		requiredArgs = 0
@@ -247,5 +250,5 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
 	if len(args) == requiredArgs {
 		return compListCharts(toComplete, true)
 	}
-	return nil, cobra.ShellCompDirectiveNoFileComp
+	return nil, completion.BashCompDirectiveNoFileComp
 }
diff --git a/cmd/helm/list.go b/cmd/helm/list.go
index 18e5ffe45..08d6beb79 100644
--- a/cmd/helm/list.go
+++ b/cmd/helm/list.go
@@ -26,6 +26,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/cli/output"
 	"helm.sh/helm/v3/pkg/release"
@@ -185,8 +186,8 @@ func (r *releaseListWriter) WriteYAML(out io.Writer) error {
 }
 
 // Provide dynamic auto-completion for release names
-func compListReleases(toComplete string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) {
-	cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug)
+func compListReleases(toComplete string, cfg *action.Configuration) ([]string, completion.BashCompDirective) {
+	completion.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete))
 
 	client := action.NewList(cfg)
 	client.All = true
@@ -196,7 +197,7 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
 	client.SetStateMask()
 	results, err := client.Run()
 	if err != nil {
-		return nil, cobra.ShellCompDirectiveDefault
+		return nil, completion.BashCompDirectiveDefault
 	}
 
 	var choices []string
@@ -204,5 +205,5 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c
 		choices = append(choices, res.Name)
 	}
 
-	return choices, cobra.ShellCompDirectiveNoFileComp
+	return choices, completion.BashCompDirectiveNoFileComp
 }
diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go
index e439c8407..36de50135 100644
--- a/cmd/helm/load_plugins.go
+++ b/cmd/helm/load_plugins.go
@@ -32,6 +32,7 @@ import (
 	"github.com/spf13/cobra"
 	"sigs.k8s.io/yaml"
 
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/plugin"
 )
 
@@ -96,7 +97,6 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
 			// This passes all the flags to the subcommand.
 			DisableFlagParsing: true,
 		}
-
 		// TODO: Make sure a command with this name does not already exist.
 		baseCmd.AddCommand(c)
 
@@ -105,7 +105,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
 		// We only do this when necessary (for the "completion" and "__complete" commands) to avoid the
 		// risk of a rogue plugin affecting Helm's normal behavior.
 		subCmd, _, err := baseCmd.Find(os.Args[1:])
-		if (err == nil && (subCmd.Name() == "completion" || subCmd.Name() == cobra.ShellCompRequestCmd)) ||
+		if (err == nil && (subCmd.Name() == "completion" || subCmd.Name() == completion.CompRequestCmd)) ||
 			/* for the tests */ subCmd == baseCmd.Root() {
 			loadCompletionForPlugin(c, plug)
 		}
@@ -247,9 +247,9 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
 		// Only setup dynamic completion if there are no sub-commands.  This avoids
 		// calling plugin.complete at every completion, which greatly simplifies
 		// development of plugin.complete for plugin developers.
-		baseCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		completion.RegisterValidArgsFunc(baseCmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 			return pluginDynamicComp(plugin, cmd, args, toComplete)
-		}
+		})
 	}
 
 	// Create fake flags.
@@ -322,12 +322,12 @@ func loadFile(path string) (*pluginCommand, error) {
 // pluginDynamicComp call the plugin.complete script of the plugin (if available)
 // to obtain the dynamic completion choices.  It must pass all the flags and sub-commands
 // specified in the command-line to the plugin.complete executable (except helm's global flags)
-func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 	md := plug.Metadata
 
 	u, err := processParent(cmd, args)
 	if err != nil {
-		return nil, cobra.ShellCompDirectiveError
+		return nil, completion.BashCompDirectiveError
 	}
 
 	// We will call the dynamic completion script of the plugin
@@ -343,12 +343,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t
 	}
 	plugin.SetupPluginEnv(settings, md.Name, plug.Dir)
 
-	cobra.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv), settings.Debug)
+	completion.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv))
 	buf := new(bytes.Buffer)
 	if err := callPluginExecutable(md.Name, main, argv, buf); err != nil {
 		// The dynamic completion file is optional for a plugin, so this error is ok.
-		cobra.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error()), settings.Debug)
-		return nil, cobra.ShellCompDirectiveDefault
+		completion.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error()))
+		return nil, completion.BashCompDirectiveDefault
 	}
 
 	var completions []string
@@ -361,12 +361,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t
 
 	// Check if the last line of output is of the form :<integer>, which
 	// indicates the BashCompletionDirective.
-	directive := cobra.ShellCompDirectiveDefault
+	directive := completion.BashCompDirectiveDefault
 	if len(completions) > 0 {
 		lastLine := completions[len(completions)-1]
 		if len(lastLine) > 1 && lastLine[0] == ':' {
 			if strInt, err := strconv.Atoi(lastLine[1:]); err == nil {
-				directive = cobra.ShellCompDirective(strInt)
+				directive = completion.BashCompDirective(strInt)
 				completions = completions[:len(completions)-1]
 			}
 		}
diff --git a/cmd/helm/plugin_uninstall.go b/cmd/helm/plugin_uninstall.go
index b2290fb9b..66cdaccdc 100644
--- a/cmd/helm/plugin_uninstall.go
+++ b/cmd/helm/plugin_uninstall.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/plugin"
 )
 
@@ -38,12 +39,6 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
 		Use:     "uninstall <plugin>...",
 		Aliases: []string{"rm", "remove"},
 		Short:   "uninstall one or more Helm plugins",
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp
-		},
 		PreRunE: func(cmd *cobra.Command, args []string) error {
 			return o.complete(args)
 		},
@@ -51,6 +46,15 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
 			return o.run(out)
 		},
 	}
+
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp
+	})
+
 	return cmd
 }
 
diff --git a/cmd/helm/plugin_update.go b/cmd/helm/plugin_update.go
index c46444e0d..23f840b4a 100644
--- a/cmd/helm/plugin_update.go
+++ b/cmd/helm/plugin_update.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/plugin"
 	"helm.sh/helm/v3/pkg/plugin/installer"
 )
@@ -39,12 +40,6 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
 		Use:     "update <plugin>...",
 		Aliases: []string{"up"},
 		Short:   "update one or more Helm plugins",
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp
-		},
 		PreRunE: func(cmd *cobra.Command, args []string) error {
 			return o.complete(args)
 		},
@@ -52,6 +47,15 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
 			return o.run(out)
 		},
 	}
+
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp
+	})
+
 	return cmd
 }
 
diff --git a/cmd/helm/pull.go b/cmd/helm/pull.go
index d10c629db..16cd10467 100644
--- a/cmd/helm/pull.go
+++ b/cmd/helm/pull.go
@@ -23,6 +23,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 )
 
@@ -50,12 +51,6 @@ func newPullCmd(out io.Writer) *cobra.Command {
 		Aliases: []string{"fetch"},
 		Long:    pullDesc,
 		Args:    require.MinimumNArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListCharts(toComplete, false)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			client.Settings = settings
 			if client.Version == "" && client.Devel {
@@ -74,6 +69,14 @@ func newPullCmd(out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListCharts(toComplete, false)
+	})
+
 	f := cmd.Flags()
 	f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
 	f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
diff --git a/cmd/helm/repo_remove.go b/cmd/helm/repo_remove.go
index e6e9cb681..5dad4e5e0 100644
--- a/cmd/helm/repo_remove.go
+++ b/cmd/helm/repo_remove.go
@@ -26,6 +26,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/helmpath"
 	"helm.sh/helm/v3/pkg/repo"
 )
@@ -44,9 +45,6 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
 		Aliases: []string{"rm"},
 		Short:   "remove one or more chart repositories",
 		Args:    require.MinimumNArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			o.repoFile = settings.RepositoryConfig
 			o.repoCache = settings.RepositoryCache
@@ -54,6 +52,12 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
 			return o.run(out)
 		},
 	}
+
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		return compListRepos(toComplete, args), completion.BashCompDirectiveNoFileComp
+	})
+
 	return cmd
 }
 
diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go
index 3b336d0ff..745e910b2 100644
--- a/cmd/helm/rollback.go
+++ b/cmd/helm/rollback.go
@@ -25,6 +25,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 )
 
@@ -46,12 +47,6 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "roll back a release to a previous revision",
 		Long:  rollbackDesc,
 		Args:  require.MinimumNArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) > 1 {
 				ver, err := strconv.Atoi(args[1])
@@ -70,6 +65,14 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 	f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
 	f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
diff --git a/cmd/helm/root.go b/cmd/helm/root.go
index 69f5855d9..143745f29 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -20,7 +20,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"log"
 	"strings"
 
 	"github.com/spf13/cobra"
@@ -28,6 +27,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/clientcmd"
 
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/internal/experimental/registry"
 	"helm.sh/helm/v3/pkg/action"
 )
@@ -70,22 +70,24 @@ By default, the default directories depend on the Operating System. The defaults
 
 func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:          "helm",
-		Short:        "The Helm package manager for Kubernetes.",
-		Long:         globalUsage,
-		SilenceUsage: true,
+		Use:                    "helm",
+		Short:                  "The Helm package manager for Kubernetes.",
+		Long:                   globalUsage,
+		SilenceUsage:           true,
+		BashCompletionFunction: completion.GetBashCustomFunction(),
 	}
 	flags := cmd.PersistentFlags()
 
 	settings.AddFlags(flags)
 
 	// Setup shell completion for the namespace flag
-	err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	flag := flags.Lookup("namespace")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if client, err := actionConfig.KubernetesClientSet(); err == nil {
 			// Choose a long enough timeout that the user notices somethings is not working
 			// but short enough that the user is not made to wait very long
 			to := int64(3)
-			cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug)
+			completion.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to))
 
 			nsNames := []string{}
 			if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil {
@@ -94,19 +96,16 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 						nsNames = append(nsNames, ns.Name)
 					}
 				}
-				return nsNames, cobra.ShellCompDirectiveNoFileComp
+				return nsNames, completion.BashCompDirectiveNoFileComp
 			}
 		}
-		return nil, cobra.ShellCompDirectiveDefault
+		return nil, completion.BashCompDirectiveDefault
 	})
 
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	// Setup shell completion for the kube-context flag
-	err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		cobra.CompDebugln("About to get the different kube-contexts", settings.Debug)
+	flag = flags.Lookup("kube-context")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		completion.CompDebugln("About to get the different kube-contexts")
 
 		loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
 		if len(settings.KubeConfig) > 0 {
@@ -121,15 +120,11 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 					ctxs = append(ctxs, name)
 				}
 			}
-			return ctxs, cobra.ShellCompDirectiveNoFileComp
+			return ctxs, completion.BashCompDirectiveNoFileComp
 		}
-		return nil, cobra.ShellCompDirectiveNoFileComp
+		return nil, completion.BashCompDirectiveNoFileComp
 	})
 
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	// We can safely ignore any errors that flags.Parse encounters since
 	// those errors will be caught later during the call to cmd.Execution.
 	// This call is required to gather configuration information prior to
@@ -169,6 +164,9 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 
 		// Hidden documentation generator command: 'helm docs'
 		newDocsCmd(out),
+
+		// Setup the special hidden __complete command to allow for dynamic auto-completion
+		completion.NewCompleteCmd(settings, out),
 	)
 
 	// Add *experimental* subcommands
diff --git a/cmd/helm/search_repo.go b/cmd/helm/search_repo.go
index dd530379b..c7f0da974 100644
--- a/cmd/helm/search_repo.go
+++ b/cmd/helm/search_repo.go
@@ -32,6 +32,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/search"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/cli/output"
 	"helm.sh/helm/v3/pkg/helmpath"
 	"helm.sh/helm/v3/pkg/repo"
@@ -289,8 +290,8 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
 
 // Provide dynamic auto-completion for commands that operate on charts (e.g., helm show)
 // When true, the includeFiles argument indicates that completion should include local files (e.g., local charts)
-func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.ShellCompDirective) {
-	cobra.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete), settings.Debug)
+func compListCharts(toComplete string, includeFiles bool) ([]string, completion.BashCompDirective) {
+	completion.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete))
 
 	noSpace := false
 	noFile := false
@@ -311,7 +312,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
 			noSpace = true
 		}
 	}
-	cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug)
+	completion.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions))
 
 	// Now handle completions for url prefixes
 	for _, url := range []string{"https://", "http://", "file://"} {
@@ -327,7 +328,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
 			noSpace = true
 		}
 	}
-	cobra.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions), settings.Debug)
+	completion.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions))
 
 	// Finally, provide file completion if we need to.
 	// We only do this if:
@@ -346,22 +347,22 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
 			}
 		}
 	}
-	cobra.CompDebugln(fmt.Sprintf("Completions after files: %v", completions), settings.Debug)
+	completion.CompDebugln(fmt.Sprintf("Completions after files: %v", completions))
 
 	// If the user didn't provide any input to completion,
 	// we provide a hint that a path can also be used
 	if includeFiles && len(toComplete) == 0 {
 		completions = append(completions, "./", "/")
 	}
-	cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug)
+	completion.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions))
 
-	directive := cobra.ShellCompDirectiveDefault
+	directive := completion.BashCompDirectiveDefault
 	if noFile {
-		directive = directive | cobra.ShellCompDirectiveNoFileComp
+		directive = directive | completion.BashCompDirectiveNoFileComp
 	}
 	if noSpace {
-		directive = directive | cobra.ShellCompDirectiveNoSpace
-		// The cobra.ShellCompDirective flags do not work for zsh right now.
+		directive = directive | completion.BashCompDirectiveNoSpace
+		// The completion.BashCompDirective flags do not work for zsh right now.
 		// We handle it ourselves instead.
 		completions = compEnforceNoSpace(completions)
 	}
@@ -379,7 +380,7 @@ func compEnforceNoSpace(completions []string) []string {
 	// We only do this if there is a single choice for completion.
 	if len(completions) == 1 {
 		completions = append(completions, completions[0]+".")
-		cobra.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions), settings.Debug)
+		completion.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions))
 	}
 	return completions
 }
diff --git a/cmd/helm/show.go b/cmd/helm/show.go
index c78069a09..91c35a53c 100644
--- a/cmd/helm/show.go
+++ b/cmd/helm/show.go
@@ -23,6 +23,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 )
 
@@ -62,19 +63,18 @@ func newShowCmd(out io.Writer) *cobra.Command {
 	}
 
 	// Function providing dynamic auto-completion
-	validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if len(args) != 0 {
-			return nil, cobra.ShellCompDirectiveNoFileComp
+			return nil, completion.BashCompDirectiveNoFileComp
 		}
 		return compListCharts(toComplete, true)
 	}
 
 	all := &cobra.Command{
-		Use:               "all [CHART]",
-		Short:             "show all information of the chart",
-		Long:              showAllDesc,
-		Args:              require.ExactArgs(1),
-		ValidArgsFunction: validArgsFunc,
+		Use:   "all [CHART]",
+		Short: "show all information of the chart",
+		Long:  showAllDesc,
+		Args:  require.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			client.OutputFormat = action.ShowAll
 			output, err := runShow(args, client)
@@ -87,11 +87,10 @@ func newShowCmd(out io.Writer) *cobra.Command {
 	}
 
 	valuesSubCmd := &cobra.Command{
-		Use:               "values [CHART]",
-		Short:             "show the chart's values",
-		Long:              showValuesDesc,
-		Args:              require.ExactArgs(1),
-		ValidArgsFunction: validArgsFunc,
+		Use:   "values [CHART]",
+		Short: "show the chart's values",
+		Long:  showValuesDesc,
+		Args:  require.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			client.OutputFormat = action.ShowValues
 			output, err := runShow(args, client)
@@ -104,11 +103,10 @@ func newShowCmd(out io.Writer) *cobra.Command {
 	}
 
 	chartSubCmd := &cobra.Command{
-		Use:               "chart [CHART]",
-		Short:             "show the chart's definition",
-		Long:              showChartDesc,
-		Args:              require.ExactArgs(1),
-		ValidArgsFunction: validArgsFunc,
+		Use:   "chart [CHART]",
+		Short: "show the chart's definition",
+		Long:  showChartDesc,
+		Args:  require.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			client.OutputFormat = action.ShowChart
 			output, err := runShow(args, client)
@@ -121,11 +119,10 @@ func newShowCmd(out io.Writer) *cobra.Command {
 	}
 
 	readmeSubCmd := &cobra.Command{
-		Use:               "readme [CHART]",
-		Short:             "show the chart's README",
-		Long:              readmeChartDesc,
-		Args:              require.ExactArgs(1),
-		ValidArgsFunction: validArgsFunc,
+		Use:   "readme [CHART]",
+		Short: "show the chart's README",
+		Long:  readmeChartDesc,
+		Args:  require.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			client.OutputFormat = action.ShowReadme
 			output, err := runShow(args, client)
@@ -141,6 +138,9 @@ func newShowCmd(out io.Writer) *cobra.Command {
 	for _, subCmd := range cmds {
 		addShowFlags(subCmd, client)
 		showCommand.AddCommand(subCmd)
+
+		// Register the completion function for each subcommand
+		completion.RegisterValidArgsFunc(subCmd, validArgsFunc)
 	}
 
 	return showCommand
diff --git a/cmd/helm/status.go b/cmd/helm/status.go
index 5913d08df..2efb2006c 100644
--- a/cmd/helm/status.go
+++ b/cmd/helm/status.go
@@ -19,13 +19,13 @@ package main
 import (
 	"fmt"
 	"io"
-	"log"
 	"strings"
 	"time"
 
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/cli/output"
@@ -53,12 +53,6 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "display the status of the named release",
 		Long:  statusHelp,
 		Args:  require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			rel, err := client.Run(args[0])
 			if err != nil {
@@ -72,21 +66,25 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 
 	f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision")
-
-	err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	flag := f.Lookup("revision")
+	completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
 		if len(args) == 1 {
 			return compListRevisions(cfg, args[0])
 		}
-		return nil, cobra.ShellCompDirectiveNoFileComp
+		return nil, completion.BashCompDirectiveNoFileComp
 	})
 
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	bindOutputFlag(cmd, &outfmt)
 
 	return cmd
diff --git a/cmd/helm/template.go b/cmd/helm/template.go
index 0fc29e189..a4438b50c 100644
--- a/cmd/helm/template.go
+++ b/cmd/helm/template.go
@@ -28,6 +28,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/cli/values"
@@ -55,9 +56,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "locally render templates",
 		Long:  templateDesc,
 		Args:  require.MinimumNArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return compInstall(args, toComplete, client)
-		},
 		RunE: func(_ *cobra.Command, args []string) error {
 			client.DryRun = true
 			client.ReleaseName = "RELEASE-NAME"
@@ -138,6 +136,11 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		return compInstall(args, toComplete, client)
+	})
+
 	f := cmd.Flags()
 	addInstallFlags(f, client, valueOpts)
 	f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates")
diff --git a/cmd/helm/uninstall.go b/cmd/helm/uninstall.go
index 509918e53..85fa822bd 100644
--- a/cmd/helm/uninstall.go
+++ b/cmd/helm/uninstall.go
@@ -24,6 +24,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 )
 
@@ -47,12 +48,6 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short:      "uninstall a release",
 		Long:       uninstallDesc,
 		Args:       require.MinimumNArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			for i := 0; i < len(args); i++ {
 
@@ -70,6 +65,14 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 	f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall")
 	f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go
index 1db899eae..f8103b485 100644
--- a/cmd/helm/upgrade.go
+++ b/cmd/helm/upgrade.go
@@ -25,6 +25,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/cli/output"
@@ -71,15 +72,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		Short: "upgrade a release",
 		Long:  upgradeDesc,
 		Args:  require.ExactArgs(2),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) == 0 {
-				return compListReleases(toComplete, cfg)
-			}
-			if len(args) == 1 {
-				return compListCharts(toComplete, true)
-			}
-			return nil, cobra.ShellCompDirectiveNoFileComp
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			client.Namespace = settings.Namespace()
 
@@ -163,6 +155,17 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) == 0 {
+			return compListReleases(toComplete, cfg)
+		}
+		if len(args) == 1 {
+			return compListCharts(toComplete, true)
+		}
+		return nil, completion.BashCompDirectiveNoFileComp
+	})
+
 	f := cmd.Flags()
 	f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
 	f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
diff --git a/internal/completion/complete.go b/internal/completion/complete.go
new file mode 100644
index 000000000..545f5b0dd
--- /dev/null
+++ b/internal/completion/complete.go
@@ -0,0 +1,399 @@
+/*
+Copyright The Helm Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package completion
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"strings"
+
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/pkg/cli"
+)
+
+// ==================================================================================
+// The below code supports dynamic shell completion in Go.
+// This should ultimately be pushed down into Cobra.
+// ==================================================================================
+
+// CompRequestCmd Hidden command to request completion results from the program.
+// Used by the shell completion script.
+const CompRequestCmd = "__complete"
+
+// Global map allowing to find completion functions for commands or flags.
+var validArgsFunctions = map[interface{}]func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective){}
+
+// BashCompDirective is a bit map representing the different behaviors the shell
+// can be instructed to have once completions have been provided.
+type BashCompDirective int
+
+const (
+	// BashCompDirectiveError indicates an error occurred and completions should be ignored.
+	BashCompDirectiveError BashCompDirective = 1 << iota
+
+	// BashCompDirectiveNoSpace indicates that the shell should not add a space
+	// after the completion even if there is a single completion provided.
+	BashCompDirectiveNoSpace
+
+	// BashCompDirectiveNoFileComp indicates that the shell should not provide
+	// file completion even when no completion is provided.
+	// This currently does not work for zsh or bash < 4
+	BashCompDirectiveNoFileComp
+
+	// BashCompDirectiveDefault indicates to let the shell perform its default
+	// behavior after completions have been provided.
+	BashCompDirectiveDefault BashCompDirective = 0
+)
+
+// GetBashCustomFunction returns the bash code to handle custom go completion
+// This should eventually be provided by Cobra
+func GetBashCustomFunction() string {
+	return fmt.Sprintf(`
+__helm_custom_func()
+{
+    __helm_debug "${FUNCNAME[0]}: c is $c, words[@] is ${words[@]}, #words[@] is ${#words[@]}"
+    __helm_debug "${FUNCNAME[0]}: cur is ${cur}, cword is ${cword}, words is ${words}"
+
+    local out requestComp lastParam lastChar
+    requestComp="${words[0]} %[1]s ${words[@]:1}"
+
+    lastParam=${words[$((${#words[@]}-1))]}
+    lastChar=${lastParam:$((${#lastParam}-1)):1}
+    __helm_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}"
+
+    if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
+        # If the last parameter is complete (there is a space following it)
+        # We add an extra empty parameter so we can indicate this to the go method.
+        __helm_debug "${FUNCNAME[0]}: Adding extra empty parameter"
+        requestComp="${requestComp} \"\""
+    fi
+
+    __helm_debug "${FUNCNAME[0]}: calling ${requestComp}"
+    # Use eval to handle any environment variables and such
+    out=$(eval ${requestComp} 2>/dev/null)
+
+    # Extract the directive int at the very end of the output following a :
+    directive=${out##*:}
+    # Remove the directive
+    out=${out%%:*}
+    if [ "${directive}" = "${out}" ]; then
+        # There is not directive specified
+        directive=0
+    fi
+    __helm_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
+    __helm_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
+
+    if [ $((${directive} & %[2]d)) -ne 0 ]; then
+        __helm_debug "${FUNCNAME[0]}: received error, completion failed"
+    else
+        if [ $((${directive} & %[3]d)) -ne 0 ]; then
+            if [[ $(type -t compopt) = "builtin" ]]; then
+                __helm_debug "${FUNCNAME[0]}: activating no space"
+                compopt -o nospace
+            fi
+        fi
+        if [ $((${directive} & %[4]d)) -ne 0 ]; then
+            if [[ $(type -t compopt) = "builtin" ]]; then
+                __helm_debug "${FUNCNAME[0]}: activating no file completion"
+                compopt +o default
+            fi
+        fi
+
+        while IFS='' read -r comp; do
+            COMPREPLY+=("$comp")
+        done < <(compgen -W "${out[*]}" -- "$cur")
+    fi
+}
+`, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp)
+}
+
+// RegisterValidArgsFunc should be called to register a function to provide argument completion for a command
+func RegisterValidArgsFunc(cmd *cobra.Command, f func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective)) {
+	if _, exists := validArgsFunctions[cmd]; exists {
+		log.Fatal(fmt.Sprintf("RegisterValidArgsFunc: command '%s' already registered", cmd.Name()))
+	}
+	validArgsFunctions[cmd] = f
+}
+
+// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag
+func RegisterFlagCompletionFunc(flag *pflag.Flag, f func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective)) {
+	if _, exists := validArgsFunctions[flag]; exists {
+		log.Fatal(fmt.Sprintf("RegisterFlagCompletionFunc: flag '%s' already registered", flag.Name))
+	}
+	validArgsFunctions[flag] = f
+
+	// Make sure the completion script call the __helm_custom_func for the registered flag.
+	// This is essential to make the = form work. E.g., helm -n=<TAB> or helm status --output=<TAB>
+	if flag.Annotations == nil {
+		flag.Annotations = map[string][]string{}
+	}
+	flag.Annotations[cobra.BashCompCustom] = []string{"__helm_custom_func"}
+}
+
+var debug = true
+
+// Returns a string listing the different directive enabled in the specified parameter
+func (d BashCompDirective) string() string {
+	var directives []string
+	if d&BashCompDirectiveError != 0 {
+		directives = append(directives, "BashCompDirectiveError")
+	}
+	if d&BashCompDirectiveNoSpace != 0 {
+		directives = append(directives, "BashCompDirectiveNoSpace")
+	}
+	if d&BashCompDirectiveNoFileComp != 0 {
+		directives = append(directives, "BashCompDirectiveNoFileComp")
+	}
+	if len(directives) == 0 {
+		directives = append(directives, "BashCompDirectiveDefault")
+	}
+
+	if d > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp {
+		return fmt.Sprintf("ERROR: unexpected BashCompDirective value: %d", d)
+	}
+	return strings.Join(directives, ", ")
+}
+
+// NewCompleteCmd add a special hidden command that an be used to request completions
+func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command {
+	debug = settings.Debug
+	return &cobra.Command{
+		Use:                   fmt.Sprintf("%s [command-line]", CompRequestCmd),
+		DisableFlagsInUseLine: true,
+		Hidden:                true,
+		DisableFlagParsing:    true,
+		Args:                  require.MinimumNArgs(1),
+		Short:                 "Request shell completion choices for the specified command-line",
+		Long: fmt.Sprintf("%s is a special command that is used by the shell completion logic\n%s",
+			CompRequestCmd, "to request completion choices for the specified command-line."),
+		Run: func(cmd *cobra.Command, args []string) {
+			CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args))
+
+			// The last argument, which is not complete, should not be part of the list of arguments
+			toComplete := args[len(args)-1]
+			trimmedArgs := args[:len(args)-1]
+
+			// Find the real command for which completion must be performed
+			finalCmd, finalArgs, err := cmd.Root().Find(trimmedArgs)
+			if err != nil {
+				// Unable to find the real command. E.g., helm invalidCmd <TAB>
+				CompDebugln(fmt.Sprintf("Unable to find a command for arguments: %v", trimmedArgs))
+				return
+			}
+
+			CompDebugln(fmt.Sprintf("Found final command '%s', with finalArgs %v", finalCmd.Name(), finalArgs))
+
+			var flag *pflag.Flag
+			if !finalCmd.DisableFlagParsing {
+				// We only do flag completion if we are allowed to parse flags
+				// This is important for helm plugins which need to do their own flag completion.
+				flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
+				if err != nil {
+					// Error while attempting to parse flags
+					CompErrorln(err.Error())
+					return
+				}
+			}
+
+			// Parse the flags and extract the arguments to prepare for calling the completion function
+			if err = finalCmd.ParseFlags(finalArgs); err != nil {
+				CompErrorln(fmt.Sprintf("Error while parsing flags from args %v: %s", finalArgs, err.Error()))
+				return
+			}
+
+			// We only remove the flags from the arguments if DisableFlagParsing is not set.
+			// This is important for helm plugins, which need to receive all flags.
+			// The plugin completion code will do its own flag parsing.
+			if !finalCmd.DisableFlagParsing {
+				finalArgs = finalCmd.Flags().Args()
+				CompDebugln(fmt.Sprintf("Args without flags are '%v' with length %d", finalArgs, len(finalArgs)))
+			}
+
+			// Find completion function for the flag or command
+			var key interface{}
+			var keyStr string
+			if flag != nil {
+				key = flag
+				keyStr = flag.Name
+			} else {
+				key = finalCmd
+				keyStr = finalCmd.Name()
+			}
+			completionFn, ok := validArgsFunctions[key]
+			if !ok {
+				CompErrorln(fmt.Sprintf("Dynamic completion not supported/needed for flag or command: %s", keyStr))
+				return
+			}
+
+			CompDebugln(fmt.Sprintf("Calling completion method for subcommand '%s' with args '%v' and toComplete '%s'", finalCmd.Name(), finalArgs, toComplete))
+			completions, directive := completionFn(finalCmd, finalArgs, toComplete)
+			for _, comp := range completions {
+				// Print each possible completion to stdout for the completion script to consume.
+				fmt.Fprintln(out, comp)
+			}
+
+			if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp {
+				directive = BashCompDirectiveDefault
+			}
+
+			// As the last printout, print the completion directive for the
+			// completion script to parse.
+			// The directive integer must be that last character following a single :
+			// The completion script expects :directive
+			fmt.Fprintf(out, ":%d\n", directive)
+
+			// Print some helpful info to stderr for the user to understand.
+			// Output from stderr should be ignored from the completion script.
+			fmt.Fprintf(os.Stderr, "Completion ended with directive: %s\n", directive.string())
+		},
+	}
+}
+
+func isFlag(arg string) bool {
+	return len(arg) > 0 && arg[0] == '-'
+}
+
+func checkIfFlagCompletion(finalCmd *cobra.Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
+	var flagName string
+	trimmedArgs := args
+	flagWithEqual := false
+	if isFlag(lastArg) {
+		if index := strings.Index(lastArg, "="); index >= 0 {
+			flagName = strings.TrimLeft(lastArg[:index], "-")
+			lastArg = lastArg[index+1:]
+			flagWithEqual = true
+		} else {
+			return nil, nil, "", errors.New("Unexpected completion request for flag")
+		}
+	}
+
+	if len(flagName) == 0 {
+		if len(args) > 0 {
+			prevArg := args[len(args)-1]
+			if isFlag(prevArg) {
+				// If the flag contains an = it means it has already been fully processed
+				if index := strings.Index(prevArg, "="); index < 0 {
+					flagName = strings.TrimLeft(prevArg, "-")
+
+					// Remove the uncompleted flag or else Cobra could complain about
+					// an invalid value for that flag e.g., helm status --output j<TAB>
+					trimmedArgs = args[:len(args)-1]
+				}
+			}
+		}
+	}
+
+	if len(flagName) == 0 {
+		// Not doing flag completion
+		return nil, trimmedArgs, lastArg, nil
+	}
+
+	flag := findFlag(finalCmd, flagName)
+	if flag == nil {
+		// Flag not supported by this command, nothing to complete
+		err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName)
+		return nil, nil, "", err
+	}
+
+	if !flagWithEqual {
+		if len(flag.NoOptDefVal) != 0 {
+			// We had assumed dealing with a two-word flag but the flag is a boolean flag.
+			// In that case, there is no value following it, so we are not really doing flag completion.
+			// Reset everything to do argument completion.
+			trimmedArgs = args
+			flag = nil
+		}
+	}
+
+	return flag, trimmedArgs, lastArg, nil
+}
+
+func findFlag(cmd *cobra.Command, name string) *pflag.Flag {
+	flagSet := cmd.Flags()
+	if len(name) == 1 {
+		// First convert the short flag into a long flag
+		// as the cmd.Flag() search only accepts long flags
+		if short := flagSet.ShorthandLookup(name); short != nil {
+			CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found flag '%s' which we will change to '%s'", name, short.Name))
+			name = short.Name
+		} else {
+			set := cmd.InheritedFlags()
+			if short = set.ShorthandLookup(name); short != nil {
+				CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found inherited flag '%s' which we will change to '%s'", name, short.Name))
+				name = short.Name
+			} else {
+				return nil
+			}
+		}
+	}
+	return cmd.Flag(name)
+}
+
+// CompDebug prints the specified string to the same file as where the
+// completion script prints its logs.
+// Note that completion printouts should never be on stdout as they would
+// be wrongly interpreted as actual completion choices by the completion script.
+func CompDebug(msg string) {
+	msg = fmt.Sprintf("[Debug] %s", msg)
+
+	// Such logs are only printed when the user has set the environment
+	// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
+	if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
+		f, err := os.OpenFile(path,
+			os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+		if err == nil {
+			defer f.Close()
+			f.WriteString(msg)
+		}
+	}
+
+	if debug {
+		// Must print to stderr for this not to be read by the completion script.
+		fmt.Fprintln(os.Stderr, msg)
+	}
+}
+
+// CompDebugln prints the specified string with a newline at the end
+// to the same file as where the completion script prints its logs.
+// Such logs are only printed when the user has set the environment
+// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
+func CompDebugln(msg string) {
+	CompDebug(fmt.Sprintf("%s\n", msg))
+}
+
+// CompError prints the specified completion message to stderr.
+func CompError(msg string) {
+	msg = fmt.Sprintf("[Error] %s", msg)
+
+	CompDebug(msg)
+
+	// If not already printed by the call to CompDebug().
+	if !debug {
+		// Must print to stderr for this not to be read by the completion script.
+		fmt.Fprintln(os.Stderr, msg)
+	}
+}
+
+// CompErrorln prints the specified completion message to stderr with a newline at the end.
+func CompErrorln(msg string) {
+	CompError(fmt.Sprintf("%s\n", msg))
+}
diff --git a/internal/experimental/registry/reference.go b/internal/experimental/registry/reference.go
index ebab29a7e..ced6cf33a 100644
--- a/internal/experimental/registry/reference.go
+++ b/internal/experimental/registry/reference.go
@@ -86,7 +86,6 @@ func (ref *Reference) FullName() string {
 
 // validate makes sure the ref meets our criteria
 func (ref *Reference) validate() error {
-
 	err := ref.validateRepo()
 	if err != nil {
 		return err
@@ -101,7 +100,7 @@ func (ref *Reference) validateRepo() error {
 	}
 	// Makes sure the repo results in a parsable URL (similar to what is done
 	// with containerd reference parsing)
-	_, err := url.Parse("//" + ref.Repo)
+	_, err := url.Parse(ref.Repo)
 	return err
 }
 


Test patch:
diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go
index 37cac6186..036d96794 100644
--- a/cmd/helm/release_testing.go
+++ b/cmd/helm/release_testing.go
@@ -24,6 +24,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"helm.sh/helm/v3/cmd/helm/require"
+	"helm.sh/helm/v3/internal/completion"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/cli/output"
 )
@@ -45,12 +46,6 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
 		Short: "run tests for a release",
 		Long:  releaseTestHelp,
 		Args:  require.ExactArgs(1),
-		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			if len(args) != 0 {
-				return nil, cobra.ShellCompDirectiveNoFileComp
-			}
-			return compListReleases(toComplete, cfg)
-		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			client.Namespace = settings.Namespace()
 			rel, runErr := client.Run(args[0])
@@ -77,6 +72,14 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
 		},
 	}
 
+	// Function providing dynamic auto-completion
+	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, completion.BashCompDirectiveNoFileComp
+		}
+		return compListReleases(toComplete, cfg)
+	})
+
 	f := cmd.Flags()
 	f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
 	f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)")
diff --git a/cmd/helm/testdata/output/output-comp.txt b/cmd/helm/testdata/output/output-comp.txt
index be574756b..de5f16f1d 100644
--- a/cmd/helm/testdata/output/output-comp.txt
+++ b/cmd/helm/testdata/output/output-comp.txt
@@ -2,4 +2,3 @@ table
 json
 yaml
 :0
-Completion ended with directive: ShellCompDirectiveDefault
diff --git a/cmd/helm/testdata/output/plugin_args_comp.txt b/cmd/helm/testdata/output/plugin_args_comp.txt
index 007112d31..8fb01cc23 100644
--- a/cmd/helm/testdata/output/plugin_args_comp.txt
+++ b/cmd/helm/testdata/output/plugin_args_comp.txt
@@ -3,4 +3,3 @@ Namespace: default
 Num args received: 1
 Args received: 
 :4
-Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/plugin_args_flag_comp.txt b/cmd/helm/testdata/output/plugin_args_flag_comp.txt
index c7a09e3fa..92f0e58a8 100644
--- a/cmd/helm/testdata/output/plugin_args_flag_comp.txt
+++ b/cmd/helm/testdata/output/plugin_args_flag_comp.txt
@@ -3,4 +3,3 @@ Namespace: default
 Num args received: 2
 Args received: --myflag 
 :4
-Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/plugin_args_many_args_comp.txt b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt
index f3c386b6d..86fa768bb 100644
--- a/cmd/helm/testdata/output/plugin_args_many_args_comp.txt
+++ b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt
@@ -3,4 +3,3 @@ Namespace: mynamespace
 Num args received: 2
 Args received: --myflag start
 :2
-Completion ended with directive: ShellCompDirectiveNoSpace
diff --git a/cmd/helm/testdata/output/plugin_args_ns_comp.txt b/cmd/helm/testdata/output/plugin_args_ns_comp.txt
index 26cd79b98..e12867daa 100644
--- a/cmd/helm/testdata/output/plugin_args_ns_comp.txt
+++ b/cmd/helm/testdata/output/plugin_args_ns_comp.txt
@@ -3,4 +3,3 @@ Namespace: mynamespace
 Num args received: 1
 Args received: 
 :2
-Completion ended with directive: ShellCompDirectiveNoSpace
diff --git a/cmd/helm/testdata/output/plugin_echo_bad_directive.txt b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt
index 8038b9525..f4b86cd47 100644
--- a/cmd/helm/testdata/output/plugin_echo_bad_directive.txt
+++ b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt
@@ -3,4 +3,3 @@ Namespace: default
 Num args received: 1
 Args received: 
 :0
-Completion ended with directive: ShellCompDirectiveDefault
diff --git a/cmd/helm/testdata/output/plugin_echo_no_directive.txt b/cmd/helm/testdata/output/plugin_echo_no_directive.txt
index 7001be0e9..6266dd4d9 100644
--- a/cmd/helm/testdata/output/plugin_echo_no_directive.txt
+++ b/cmd/helm/testdata/output/plugin_echo_no_directive.txt
@@ -3,4 +3,3 @@ Namespace: mynamespace
 Num args received: 1
 Args received: 
 :0
-Completion ended with directive: ShellCompDirectiveDefault
diff --git a/cmd/helm/testdata/output/revision-comp.txt b/cmd/helm/testdata/output/revision-comp.txt
index d06947d9d..b4799f059 100644
--- a/cmd/helm/testdata/output/revision-comp.txt
+++ b/cmd/helm/testdata/output/revision-comp.txt
@@ -3,4 +3,3 @@
 10
 11
 :0
-Completion ended with directive: ShellCompDirectiveDefault
diff --git a/cmd/helm/testdata/output/revision-wrong-args-comp.txt b/cmd/helm/testdata/output/revision-wrong-args-comp.txt
index 8d9fad576..b6f867176 100644
--- a/cmd/helm/testdata/output/revision-wrong-args-comp.txt
+++ b/cmd/helm/testdata/output/revision-wrong-args-comp.txt
@@ -1,2 +1,1 @@
 :4
-Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/status-comp.txt b/cmd/helm/testdata/output/status-comp.txt
index 8d4d21df7..c97882964 100644
--- a/cmd/helm/testdata/output/status-comp.txt
+++ b/cmd/helm/testdata/output/status-comp.txt
@@ -1,4 +1,3 @@
 aramis
 athos
 :4
-Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/cmd/helm/testdata/output/status-wrong-args-comp.txt b/cmd/helm/testdata/output/status-wrong-args-comp.txt
index 8d9fad576..b6f867176 100644
--- a/cmd/helm/testdata/output/status-wrong-args-comp.txt
+++ b/cmd/helm/testdata/output/status-wrong-args-comp.txt
@@ -1,2 +1,1 @@
 :4
-Completion ended with directive: ShellCompDirectiveNoFileComp
diff --git a/internal/experimental/registry/reference_test.go b/internal/experimental/registry/reference_test.go
index aae03ad99..bb53ebab8 100644
--- a/internal/experimental/registry/reference_test.go
+++ b/internal/experimental/registry/reference_test.go
@@ -81,13 +81,6 @@ func TestParseReference(t *testing.T) {
 	is.Equal("1.5.0", ref.Tag)
 	is.Equal("myrepo:5001/mychart:1.5.0", ref.FullName())
 
-	s = "127.0.0.1:5001/mychart:1.5.0"
-	ref, err = ParseReference(s)
-	is.NoError(err)
-	is.Equal("127.0.0.1:5001/mychart", ref.Repo)
-	is.Equal("1.5.0", ref.Tag)
-	is.Equal("127.0.0.1:5001/mychart:1.5.0", ref.FullName())
-
 	s = "localhost:5000/mychart:latest"
 	ref, err = ParseReference(s)
 	is.NoError(err)
diff --git a/internal/urlutil/urlutil_test.go b/internal/urlutil/urlutil_test.go
index 8e99c1bfb..82acc40fe 100644
--- a/internal/urlutil/urlutil_test.go
+++ b/internal/urlutil/urlutil_test.go
@@ -56,6 +56,9 @@ func TestEqual(t *testing.T) {
 		{"/foo", "/foo", true},
 		{"/foo", "/foo/", true},
 		{"/foo/.", "/foo/", true},
+		{"%/1234", "%/1234", true},
+		{"%/1234", "%/123", false},
+		{"/1234", "%/1234", false},
 	} {
 		if tt.match != Equal(tt.a, tt.b) {
 			t.Errorf("Expected %q==%q to be %t", tt.a, tt.b, tt.match)