Repository: kptdev/kpt

Issue:
mdtogo Drops License Text When Regenerated
When `mdtogo` is run with `make generate` the License text inserted at the beginning of the generated files is removed. This causes files with no other changes to be modified and requires runs of `mdtogo` to include manual changes to re-insert this text.

Discussion:
@etefera: `make generate` uses `addlicense` to insert the license text after the go docs are generated https://github.com/GoogleContainerTools/kpt/blob/8be3f718c015a5fe190bebf356079a25afe0ca57/Makefile#L48 but `addlicense` began skipping adding licenses to generated files with https://github.com/google/addlicense/pull/41.
It may be worth using a version of `addlicense` that predates that change or adding configurable behavior to the `addlicense` project.@CsatariGergely: This is not relevant after the documentation rearchitecturing. 

Solution patch:
diff --git a/Makefile b/Makefile
index 197a953d..4ba877c7 100644
--- a/Makefile
+++ b/Makefile
@@ -41,8 +41,6 @@ generate:
 	rm -rf internal/docs/generated
 	mkdir internal/docs/generated
 	GOBIN=$(GOBIN) go generate ./...
-	which addlicense || go get github.com/google/addlicense
-	$(GOBIN)/addlicense -y 2021 -l apache internal/docs/generated
 	go fmt ./internal/docs/generated/...
 
 tidy:
diff --git a/go.sum b/go.sum
index b3b1c883..b7d24827 100644
--- a/go.sum
+++ b/go.sum
@@ -226,6 +226,7 @@ github.com/go-openapi/validate v0.19.8 h1:YFzsdWIDfVuLvIOF+ZmKjVg1MbPJ1QgY9PihMw
 github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
 github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@@ -373,6 +374,7 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
+github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno=
 github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
diff --git a/internal/cmdrender/pipeline_function.go b/internal/cmdrender/pipeline_function.go
index 779fabf4..4d1a5eaf 100644
--- a/internal/cmdrender/pipeline_function.go
+++ b/internal/cmdrender/pipeline_function.go
@@ -23,8 +23,8 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/GoogleContainerTools/kpt/internal/cmdrender/runtime"
 	"github.com/GoogleContainerTools/kpt/internal/errors"
+	"github.com/GoogleContainerTools/kpt/internal/fnruntime"
 	"github.com/GoogleContainerTools/kpt/internal/types"
 	kptfilev1alpha2 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1alpha2"
 	"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
@@ -40,13 +40,13 @@ func newFnRunner(ctx context.Context, f *kptfilev1alpha2.Function, pkgPath types
 		return nil, err
 	}
 
-	cfn := &runtime.ContainerFn{
+	cfn := &fnruntime.ContainerFn{
 		Path:  pkgPath,
 		Image: f.Image,
 		Ctx:   ctx,
 	}
 
-	cfnw := &runtime.ContainerFnWrapper{
+	cfnw := &fnruntime.ContainerFnWrapper{
 		Fn: cfn,
 	}
 
diff --git a/internal/docs/generated/fndocs/docs.go b/internal/docs/generated/fndocs/docs.go
index a2c3ca41..2af860fa 100644
--- a/internal/docs/generated/fndocs/docs.go
+++ b/internal/docs/generated/fndocs/docs.go
@@ -1,17 +1,3 @@
-// Copyright 2019 Google LLC
-//
-// 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.
-
 // Code generated by "mdtogo"; DO NOT EDIT.
 package fndocs
 
@@ -42,8 +28,7 @@ var FnExamples = `
 
 var DocShort = `Display the documentation for a function`
 var DocLong = `
-` + "`" + `kpt fn doc` + "`" + ` invokes ` + "`" + `docker run` + "`" + ` to extract the help text. It requires the
-image to set the default entrypoint which supports ` + "`" + `--help` + "`" + ` flag.
+` + "`" + `kpt fn doc` + "`" + ` invokes the function container with ` + "`" + `--help` + "`" + ` flag.
 
   kpt fn doc --image=IMAGE
 
diff --git a/internal/docs/generated/livedocs/docs.go b/internal/docs/generated/livedocs/docs.go
index 68d289a4..97e64fee 100644
--- a/internal/docs/generated/livedocs/docs.go
+++ b/internal/docs/generated/livedocs/docs.go
@@ -1,17 +1,3 @@
-// Copyright 2019 Google LLC
-//
-// 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.
-
 // Code generated by "mdtogo"; DO NOT EDIT.
 package livedocs
 
@@ -61,6 +47,22 @@ Flags:
     This determines the output format of the command. The default value is
     events, which will print the events as they happen. The other option is
     table, which will show the output in a table format.
+  
+  --server-side:
+    Boolean which sends the entire resource to the server during apply instead of
+    calculating a client-side patch. Default value is false (client-side). Available
+    in version v0.36.0 and above. If not available, the user will see: "error: unknown flag".
+  
+  --field-manager:
+    String specifying the **owner** of the fields being applied. Only usable
+    when --server-side flag is specified. Default value is kubectl. Available in
+    version v0.36.0 and above. If not available, the user will see: "error: unknown flag".
+  
+  --force-conflicts:
+    Boolean which forces overwrite of field conflicts during apply due to
+    different field managers. Only usable when --server-side flag is specified.
+    Default value is false (error and failure when field managers conflict).
+    Available in v0.36.0 and above. If not available, the user will see: "error: unknown flag".
 `
 var ApplyExamples = `
   # apply resources and prune
@@ -158,6 +160,8 @@ var InitExamples = `
   # initialize a package
   kpt live init my-dir/
 
+
+
   # initialize a package with a specific name for the group of resources
   kpt live init --namespace=test my-dir/
 `
@@ -176,6 +180,24 @@ Flags:
 
   --destroy:
     If true, dry-run deletion of all resources.
+  
+  --server-side:
+    Boolean which performs the dry-run by sending the resource to the server.
+    Default value is false (client-side dry-run). Available
+    in version v0.36.0 and above. If not available, the user will see:
+    "error: unknown flag".
+  
+  --field-manager:
+    String that can be set if --server-side flag is also set, which defines
+    the resources field owner during dry-run. Available
+    in version v0.36.0 and above. If not available, the user will see:
+    "error: unknown flag".
+  
+  --force-conflicts:
+    Boolean that can be set if --server-side flag is also set, which overrides
+    field ownership conflicts during dry-run. Available
+    in version v0.36.0 and above. If not available, the user will see:
+    "error: unknown flag".
 `
 var PreviewExamples = `
   # preview apply for a package
@@ -230,7 +252,4 @@ var StatusExamples = `
 
   # Monitor status for a set of resources based on manifests. Output in table format:
   kpt live status my-app/ --poll-until=forever --output=table
-
-  # Check status for a set of resources read from stdin with output in events format
-  kpt cfg cat my-app | kpt live status
 `
diff --git a/internal/docs/generated/overview/docs.go b/internal/docs/generated/overview/docs.go
index 385b7186..d61901af 100644
--- a/internal/docs/generated/overview/docs.go
+++ b/internal/docs/generated/overview/docs.go
@@ -1,17 +1,3 @@
-// Copyright 2019 Google LLC
-//
-// 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.
-
 // Code generated by "mdtogo"; DO NOT EDIT.
 package overview
 
@@ -29,7 +15,7 @@ syntax and pattern of inputs and outputs.
 `
 var ReferenceExamples = `
   # get a package
-  $ kpt pkg get https://github.com/GoogleContainerTools/kpt.git/package-examples/helloworld-set@v0.5.0 helloworld
+  $ kpt pkg get https://github.com/GoogleContainerTools/kpt.git/package-examples/helloworld-set@next helloworld
   fetching package /package-examples/helloworld-set from \
     https://github.com/GoogleContainerTools/kpt to helloworld
 
diff --git a/internal/docs/generated/pkgdocs/docs.go b/internal/docs/generated/pkgdocs/docs.go
index b5697d5e..67a8c107 100644
--- a/internal/docs/generated/pkgdocs/docs.go
+++ b/internal/docs/generated/pkgdocs/docs.go
@@ -1,48 +1,13 @@
-// Copyright 2019 Google LLC
-//
-// 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.
-
 // Code generated by "mdtogo"; DO NOT EDIT.
 package pkgdocs
 
 var PkgShort = `Fetch, update, and sync configuration files using git`
 var PkgLong = `
-|              Reads From | Writes To                |
-|-------------------------|--------------------------|
-| git repository          | local directory          |
-
-The ` + "`" + `pkg` + "`" + ` command group contains subcommands which read remote upstream
-git repositories, and write local directories.  They are focused on
-providing porcelain on top of workflows which would otherwise require
-wrapping git to pull clone subdirectories and perform updates by merging
-resources rather than files.
+The ` + "`" + `pkg` + "`" + ` command group contains subcommands for fetching and updating ` + "`" + `kpt` + "`" + ` package
+from git repositories.
 `
 var PkgExamples = `
-  # create your workspace
-  $ mkdir hello-world-workspace
-  $ cd hello-world-workspace
-  $ git init
-  
-  # get the package
-  $ export SRC_REPO=https://github.com/GoogleContainerTools/kpt.git
-  $ kpt pkg get $SRC_REPO/package-examples/helloworld-set@v0.3.0 helloworld
-  
-  # add helloworld to your workspace
-  $ git add .
-  $ git commit -am "Add hello world to my workspace."
-  
-  # pull in upstream updates by merging Resources
-  $ kpt pkg update helloworld@v0.5.0 --strategy=resource-merge
+
 `
 
 var CatShort = `Print the resources in a file/directory`
@@ -78,18 +43,19 @@ var CatExamples = `
     name: nginx-service
 `
 
-var DiffShort = `Diff a local package against upstream`
+var DiffShort = `Show differences between a local package and upstream.`
 var DiffLong = `
   kpt pkg diff [PKG_PATH@VERSION] [flags]
 
 Args:
 
   PKG_PATH:
-    Local package path to compare. Command will fail if the directory doesn't exist, or does not
+    Local package path to compare. diff will fail if the directory doesn't exist, or does not
     contain a Kptfile. Defaults to the current working directory.
   
   VERSION:
-    A git tag, branch, ref or commit. Specified after the local_package with @ -- pkg_dir@version.
+    A git tag, branch, or commit. Specified after the local_package with @, for 
+    example my-package@master.
     Defaults to the local package version that was last fetched.
 
 Flags:
@@ -98,77 +64,81 @@ Flags:
     The type of changes to view (local by default). Following types are
     supported:
   
-    local: shows changes in local package relative to upstream source package
-           at original version
-    remote: shows changes in upstream source package at target version
-            relative to original version
-    combined: shows changes in local package relative to upstream source
-              package at target version
-    3way: shows changes in local package and source package at target version
-          relative to original version side by side
+    local: Shows changes in local package relative to upstream source package
+           at original version.
+    remote: Shows changes in upstream source package at target version
+            relative to original version.
+    combined: Shows changes in local package relative to upstream source
+              package at target version.
+    3way: Shows changes in local package and source package at target version
+          relative to original version side by side.
   
   --diff-tool:
-    Commandline tool (diff by default) for showing the changes.
+    Command line diffing tool ('diff' by default) for showing the changes.
     Note that it overrides the KPT_EXTERNAL_DIFF environment variable.
     
-    # Show changes using 'meld' commandline tool
+    # Show changes using 'meld' commandline tool.
     kpt pkg diff @master --diff-tool meld
   
   --diff-opts:
-    Commandline options to use with the diffing tool.
+    Commandline options to use with the command line diffing tool.
     Note that it overrides the KPT_EXTERNAL_DIFF_OPTS environment variable.
-    # Show changes using "diff" with recurive options
+    
+    # Show changes using the diff command with recursive options.
     kpt pkg diff @master --diff-tool meld --diff-opts "-r"
 
 Environment Variables:
 
   KPT_EXTERNAL_DIFF:
-     Commandline diffing tool (diff by default) that will be used to show
-     changes.
-     # Use meld to show changes
-     KPT_EXTERNAL_DIFF=meld kpt pkg diff
+    Commandline diffing tool ('diff; by default) that will be used to show
+    changes.
+    
+    # Use meld to show changes
+    KPT_EXTERNAL_DIFF=meld kpt pkg diff
   
   KPT_EXTERNAL_DIFF_OPTS:
-     Commandline options to use for the diffing tool. For ex.
-     # Using "-a" diff option
-     KPT_EXTERNAL_DIFF_OPTS="-a" kpt pkg diff --diff-tool meld
+    Commandline options to use for the diffing tool. For ex.
+    # Using "-a" diff option
+    KPT_EXTERNAL_DIFF_OPTS="-a" kpt pkg diff --diff-tool meld
+     
+  KPT_CACHE_DIR:
+    Controls where to cache remote packages when fetching them.
+    Defaults to <HOME>/.kpt/repos/
+    On macOS and Linux <HOME> is determined by the $HOME env variable, while on
+    Windows it is given by the %USERPROFILE% env variable.
 `
 var DiffExamples = `
-<!-- @pkgDiff @verifyExamples-->
-  # Show changes in current package relative to upstream source package
+  # Show changes in current package relative to upstream source package.
   kpt pkg diff
 
   # Show changes in current package relative to upstream source package
   # using meld tool with auto compare option.
   kpt pkg diff --diff-tool meld --diff-tool-opts "-a"
 
-<!-- @pkgDiff @verifyExamples-->
   # Show changes in upstream source package between current version and
-  # target version
+  # target version.
   kpt pkg diff @v0.4.0 --diff-type remote
 
-<!-- @pkgDiff @verifyExamples-->
-  # Show changes in current package relative to target version
+  # Show changes in current package relative to target version.
   kpt pkg diff @v0.4.0 --diff-type combined
 
   # Show 3way changes between the local package, upstream package at original
-  # version and upstream package at target version using meld
+  # version and upstream package at target version using meld.
   kpt pkg diff @v0.4.0 --diff-type 3way --diff-tool meld --diff-tool-opts "-a"
 `
 
 var GetShort = `Fetch a package from a git repo.`
 var GetLong = `
   kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] [LOCAL_DEST_DIRECTORY] [flags]
-  
+
+Args:
   REPO_URI:
     URI of a git repository containing 1 or more packages as subdirectories.
     In most cases the .git suffix should be specified to delimit the REPO_URI
     from the PKG_PATH, but this is not required for widely recognized repo
-    prefixes.  If get cannot parse the repo for the directory and version,
+    prefixes. If get cannot parse the repo for the directory and version,
     then it will print an error asking for '.git' to be specified as part of
     the argument.
-    e.g. https://github.com/kubernetes/examples.git
-    Specify - to read Resources from stdin and write to a LOCAL_DEST_DIRECTORY
   
   PKG_PATH:
     Path to remote subdirectory containing Kubernetes resource configuration
@@ -178,72 +148,80 @@ var GetLong = `
   
   VERSION:
     A git tag, branch, ref or commit for the remote version of the package
-    to fetch.  Defaults to the repository master branch.
-    e.g. @master
+    to fetch. Defaults to the default branch of the repository.
   
   LOCAL_DEST_DIRECTORY:
-    The local directory to write the package to. Defaults to the current working directory.
-    e.g. ./my-cockroachdb-copy
-  
-      * If the directory does NOT exist: create the specified directory
-        and write the package contents to it
-      * If the directory DOES exist: create a NEW directory under the
-        specified one, defaulting the name to the Base of REPO/PKG_PATH
-      * If the directory DOES exist and already contains a directory with
-        the same name of the one that would be created: fail
+    The local directory to write the package to. Defaults to a subdirectory of the
+    current working directory named after the upstream package.
+
+Flags:
+  --strategy:
+    Defines which strategy should be used to update the package. It defaults to
+    'resource-merge'.
+    
+      * resource-merge: Perform a structural comparison of the original /
+        updated resources, and merge the changes into the local package.
+      * fast-forward: Fail without updating if the local package was modified
+        since it was fetched.
+      * force-delete-replace: Wipe all the local changes to the package and replace
+        it with the remote version.
+
+Env Vars:
+  KPT_CACHE_DIR:
+    Controls where to cache remote packages when fetching them.
+    Defaults to <HOME>/.kpt/repos/
+    On macOS and Linux <HOME> is determined by the $HOME env variable, while on
+    Windows it is given by the %USERPROFILE% env variable.
 `
 var GetExamples = `
-<!-- @pkgGet @verifyExamples-->
-  # fetch package cockroachdb from github.com/kubernetes/examples/staging/cockroachdb
-  # creates directory ./cockroachdb/ containing the package contents
-  kpt pkg get https://github.com/kubernetes/examples.git/staging/cockroachdb@master ./
-
-<!-- @pkgGet @verifyExamples-->
-  # fetch a cockroachdb
-  # if ./my-package doesn't exist, creates directory ./my-package/ containing
-  # the package contents
+  # Fetch package cockroachdb from github.com/kubernetes/examples/staging/cockroachdb
+  # This creates a new subdirectory 'cockroachdb' for the downloaded package.
+  kpt pkg get https://github.com/kubernetes/examples.git/staging/cockroachdb@master
+
+  # Fetch package cockroachdb from github.com/kubernetes/examples/staging/cockroachdb
+  # This will create a new directory 'my-package' for the downloaded package if it
+  # doesn't already exist.
   kpt pkg get https://github.com/kubernetes/examples.git/staging/cockroachdb@master ./my-package/
 
-<!-- @pkgGet @verifyExamples-->
-  # fetch package examples from github.com/kubernetes/examples
-  # creates directory ./examples fetched from the provided commit hash
-  kpt pkg get https://github.com/kubernetes/examples.git/@6fe2792 ./
+  # Fetch package examples from github.com/kubernetes/examples at the specified
+  # git hash. 
+  # This will create a new directory 'examples' for the package.
+  kpt pkg get https://github.com/kubernetes/examples.git/@6fe2792
 `
 
-var InitShort = `Initialize an empty package`
+var InitShort = `Initialize an empty package.`
 var InitLong = `
   kpt pkg init [DIR] [flags]
 
 Args:
 
   DIR:
-    Init fails if DIR does not already exist. Defaults to the current working directory.
+    init fails if DIR does not already exist. Defaults to the current working directory.
 
 Flags:
 
   --description
-    short description of the package. (default "sample description")
+    Short description of the package. (default "sample description")
   
-  --name
-    package name.  defaults to the directory base name.
+  --keywords
+    A list of keywords describing the package.
   
-  --tag
-    list of tags for the package.
-  
-  --url
-    link to page with information about the package.
+  --site
+    Link to page with information about the package.
 `
 var InitExamples = `
-<!-- @pkgInit @verifyStaleExamples-->
-  # writes Kptfile package meta if not found
-  mkdir my-pkg
-  kpt pkg init my-pkg --tag kpt.dev/app=cockroachdb \
+  # Creates a new Kptfile with metadata in the cockroachdb directory.
+  mkdir cockroachdb
+  kpt pkg init cockroachdb --keywords "cockroachdb,nosql,db"  \
       --description "my cockroachdb implementation"
+
+  # Creates a new Kptfile without metadata in the current directory.
+  kpt pkg init
 `
 
 var TreeShort = `Render resources using a tree structure`
 var TreeLong = `
-  kpt pkg tree [DIR | -] [flags]
+  kpt pkg tree [DIR | -]
 
 Args:
 
@@ -252,73 +230,16 @@ Args:
 
 Flags:
 
-  --args:
-    if true, print the container args field
-  
-  --command:
-    if true, print the container command field
-  
-  --env:
-    if true, print the container env field
   
-  --field:
-    dot-separated path to a field to print
-  
-  --image:
-    if true, print the container image fields
-  
-  --name:
-    if true, print the container name fields
-  
-  --ports:
-    if true, print the container port fields
-  
-  --replicas:
-    if true, print the replica field
-  
-  --resources:
-    if true, print the resource reservations
 `
 var TreeExamples = `
-<!-- @pkgTree @verifyExamples-->
   # print Resources using directory structure
   kpt pkg tree
-
-<!-- @pkgTree @verifyExamples-->
-  # print replicas, container name, and container image and fields for Resources
-  kpt pkg tree --replicas --image --name
-
-<!-- @pkgTree @verifyExamples-->
-  # print all common Resource fields
-  kpt pkg tree --all
-
-<!-- @pkgTree @verifyExamples-->
-  # print the "foo"" annotation
-  kpt pkg tree --field "metadata.annotations.foo"
-
-<!-- @pkgTree @verifyStaleExamples-->
-  # print the status of resources piped from kubectl output with status.condition 
-  type of "Completed"
-  kubectl get all -o yaml | kpt pkg tree - \
-    --field="status.conditions[type=Completed].status"
-
-<!-- @pkgTree @verifyStaleExamples-->
-  # print live Resources from a cluster using owners for graph structure
-  kubectl get all -o yaml | kpt pkg tree --replicas --name --image
-
-<!-- @pkgTree @verifyStaleExamples-->
-  # print live Resources with status condition fields
-  kubectl get all -o yaml | kpt pkg tree \
-    --name --image --replicas \
-    --field="status.conditions[type=Completed].status" \
-    --field="status.conditions[type=Complete].status" \
-    --field="status.conditions[type=Ready].status" \
-    --field="status.conditions[type=ContainersReady].status"
 `
 
-var UpdateShort = `Apply upstream package updates`
+var UpdateShort = `Apply upstream package updates.`
 var UpdateLong = `
-  kpt pkg update [PKG_PATH@VERSION] [flags]
+  kpt pkg update [PKG_PATH][@VERSION] [flags]
 
 Args:
 
@@ -329,7 +250,7 @@ Args:
   VERSION:
     A git tag, branch, ref or commit. Specified after the local_package
     with @ -- pkg@version.
-    Defaults the local package version that was last fetched.
+    Defaults the ref specified in the Upstream section of the package Kptfile.
   
     Version types:
       * branch: update the local contents to the tip of the remote branch
@@ -339,43 +260,36 @@ Args:
 Flags:
 
   --strategy:
-    Controls how changes to the local package are handled.  Defaults to fast-forward.
+    Defines which strategy should be used to update the package. This will change
+    the update strategy for the current kpt package for the current and future 
+    updates. If a strategy is not provided, the strategy specified in the package
+    Kptfile will be used.
   
-      * resource-merge: perform a structural comparison of the original /
-        updated Resources, and merge the changes into the local package.
-      * fast-forward: fail without updating if the local package was modified
+      * resource-merge: Perform a structural comparison of the original /
+        updated resources, and merge the changes into the local package.
+      * fast-forward: Fail without updating if the local package was modified
         since it was fetched.
-      * alpha-git-patch: use 'git format-patch' and 'git am' to apply a
-        patch of the changes between the source version and destination
-        version.
-      * force-delete-replace: WIPE ALL LOCAL CHANGES TO THE PACKAGE.
-        DELETE the local package at local_pkg_dir/ and replace it
-        with the remote version.
-  
-  -r, --repo:
-    Git repo url for updating contents.  Defaults to the repo the package
-    was fetched from.
-  
-  --dry-run
-    Print the 'alpha-git-patch' strategy patch rather than merging it.
+      * force-delete-replace: Wipe all the local changes to the package and replace
+        it with the remote version.
 
 Env Vars:
 
   KPT_CACHE_DIR:
-    Controls where to cache remote packages when fetching them to update
-    local packages.
-    Defaults to ~/.kpt/repos/
+    Controls where to cache remote packages when fetching them.
+    Defaults to <HOME>/.kpt/repos/
+    On macOS and Linux <HOME> is determined by the $HOME env variable, while on
+    Windows it is given by the %USERPROFILE% env variable.
 `
 var UpdateExamples = `
-  # update my-package-dir/
+  # Update package in the current directory.
   git add . && git commit -m 'some message'
-  kpt pkg update my-package-dir/
+  kpt pkg update
 
-  # update my-package-dir/ to match the v1.3 branch or tag
+  # Update my-package-dir/ to match the v1.3 branch or tag.
   git add . && git commit -m 'some message'
   kpt pkg update my-package-dir/@v1.3
 
-  # update applying a git patch
+  # Update with the fast-forward strategy.
   git add . && git commit -m "package updates"
-  kpt pkg  update my-package-dir/@master --strategy alpha-git-patch
+  kpt pkg update my-package-dir/@master --strategy fast-forward
 `
diff --git a/internal/errors/errors.go b/internal/errors/errors.go
index ec973a49..84882b86 100644
--- a/internal/errors/errors.go
+++ b/internal/errors/errors.go
@@ -248,3 +248,20 @@ func UnwrapKioError(err error) error {
 	}
 	return kioErr.Err
 }
+
+// UnwrapErrors unwraps any *Error errors in the chain and returns
+// the first error it finds that is of a different type. If no such error
+// can be found, the last return value will be false.
+//nolint
+func UnwrapErrors(err error) (error, bool) {
+	for {
+		if err == nil {
+			return nil, false
+		}
+		e, ok := err.(*Error)
+		if !ok {
+			return err, true
+		}
+		err = e.Err
+	}
+}
diff --git a/internal/errors/resolver/fn.go b/internal/errors/resolver/fn.go
new file mode 100644
index 00000000..7f0a6de6
--- /dev/null
+++ b/internal/errors/resolver/fn.go
@@ -0,0 +1,45 @@
+// Copyright 2021 Google LLC
+//
+// 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 resolver
+
+import (
+	goerrors "errors"
+
+	"github.com/GoogleContainerTools/kpt/internal/errors"
+)
+
+//nolint:gochecknoinits
+func init() {
+	AddErrorResolver(&fnExecErrorResolver{})
+}
+
+// gitExecErrorResolver is an implementation of the ErrorResolver interface
+// that can produce error messages for errors of the FnExecError type.
+type fnExecErrorResolver struct{}
+
+func (*fnExecErrorResolver) Resolve(err error) (ResolvedResult, bool) {
+	kioErr := errors.UnwrapKioError(err)
+
+	var fnErr *errors.FnExecError
+	if !goerrors.As(kioErr, &fnErr) {
+		return ResolvedResult{}, false
+	}
+	// TODO: write complete details to a file
+
+	return ResolvedResult{
+		Message:  fnErr.String(),
+		ExitCode: 1,
+	}, true
+}
diff --git a/internal/errors/resolver/git.go b/internal/errors/resolver/git.go
index 49f0b04d..4de5fdfd 100644
--- a/internal/errors/resolver/git.go
+++ b/internal/errors/resolver/git.go
@@ -19,14 +19,12 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/GoogleContainerTools/kpt/internal/errors"
 	"github.com/GoogleContainerTools/kpt/internal/gitutil"
 )
 
 //nolint:gochecknoinits
 func init() {
 	AddErrorResolver(&gitExecErrorResolver{})
-	AddErrorResolver(&fnExecErrorResolver{})
 }
 
 const (
@@ -39,33 +37,37 @@ against repo {{ printf "%q " .repo }}
 for reference {{ printf "%q " .ref }}
 {{- end }}
 
-{{- if or (gt (len .stdout) 0) (gt (len .stderr) 0)}}
-{{ printf "\nDetails:" }}
-{{- end }}
-
-{{- if gt (len .stdout) 0 }}
-{{ printf "%s" .stdout }}
-{{- end }}
-
-{{- if gt (len .stderr) 0 }}
-{{ printf "%s" .stderr }}
-{{- end }}
+{{- template "ExecOutputDetails" . }}
 `
 
 	unknownRefGitExecError = `
 Error: Unknown ref {{ printf "%q" .ref }}. Please verify that the reference exists in upstream repo {{ printf "%q" .repo }}.
 
-{{- if or (gt (len .stdout) 0) (gt (len .stderr) 0)}}
-{{ printf "\nDetails:" }}
-{{- end }}
+{{- template "ExecOutputDetails" . }}
+`
 
-{{- if gt (len .stdout) 0 }}
-{{ printf "%s" .stdout }}
-{{- end }}
+	noGitBinaryError = `
+Error: No git executable found. kpt requires git to be installed and available in the path.
 
-{{- if gt (len .stderr) 0 }}
-{{ printf "%s" .stderr }}
-{{- end }}
+{{- template "ExecOutputDetails" . }}
+`
+
+	httpsAuthRequired = `
+Error: Repository {{ printf "%q" .repo }} requires authentication. kpt does not support this for the 'https' protocol. Please use the 'git' protocol instead.
+
+{{- template "ExecOutputDetails" . }}
+`
+
+	repositoryUnavailable = `
+Error: Unable to access repository {{ printf "%q" .repo }}.
+
+{{- template "ExecOutputDetails" . }}
+`
+
+	repositoryNotFound = `
+Error: Repository {{ printf "%q" .repo }} not found.
+
+{{- template "ExecOutputDetails" . }}
 `
 )
 
@@ -88,12 +90,17 @@ func (*gitExecErrorResolver) Resolve(err error) (ResolvedResult, bool) {
 		"stderr": gitExecErr.StdErr,
 	}
 	var msg string
-	switch {
-	// TODO(mortent): Checking the content of the output at this level seems a bit awkward. We might
-	// consider doing this the the gitutil package and use some kind of error code to signal
-	// the different error cases to higher levels in the stack.
-	case strings.Contains(gitExecErr.StdErr, " unknown revision or path not in the working tree"):
+	switch gitExecErr.Type {
+	case gitutil.UnknownReference:
 		msg = ExecuteTemplate(unknownRefGitExecError, tmplArgs)
+	case gitutil.GitExecutableNotFound:
+		msg = ExecuteTemplate(noGitBinaryError, tmplArgs)
+	case gitutil.HTTPSAuthRequired:
+		msg = ExecuteTemplate(httpsAuthRequired, tmplArgs)
+	case gitutil.RepositoryUnavailable:
+		msg = ExecuteTemplate(repositoryUnavailable, tmplArgs)
+	case gitutil.RepositoryNotFound:
+		msg = ExecuteTemplate(repositoryNotFound, tmplArgs)
 	default:
 		msg = ExecuteTemplate(genericGitExecError, tmplArgs)
 	}
@@ -102,22 +109,3 @@ func (*gitExecErrorResolver) Resolve(err error) (ResolvedResult, bool) {
 		ExitCode: 1,
 	}, true
 }
-
-// gitExecErrorResolver is an implementation of the ErrorResolver interface
-// that can produce error messages for errors of the FnExecError type.
-type fnExecErrorResolver struct{}
-
-func (*fnExecErrorResolver) Resolve(err error) (ResolvedResult, bool) {
-	kioErr := errors.UnwrapKioError(err)
-
-	var fnErr *errors.FnExecError
-	if !goerrors.As(kioErr, &fnErr) {
-		return ResolvedResult{}, false
-	}
-	// TODO: write complete details to a file
-
-	return ResolvedResult{
-		Message:  fnErr.String(),
-		ExitCode: 1,
-	}, true
-}
diff --git a/internal/errors/resolver/resolver.go b/internal/errors/resolver/resolver.go
index e701ca58..460ac96e 100644
--- a/internal/errors/resolver/resolver.go
+++ b/internal/errors/resolver/resolver.go
@@ -14,13 +14,6 @@
 
 package resolver
 
-import (
-	"bytes"
-	"fmt"
-	"strings"
-	"text/template"
-)
-
 // errorResolvers is the list of known resolvers for kpt errors.
 var errorResolvers []ErrorResolver
 
@@ -43,22 +36,6 @@ func ResolveError(err error) (ResolvedResult, bool) {
 	return ResolvedResult{}, false
 }
 
-// ExecuteTemplate takes the provided template string and data, and renders
-// the template. If something goes wrong, it panics.
-func ExecuteTemplate(text string, data interface{}) string {
-	tmpl, tmplErr := template.New("kpterror").Parse(text)
-	if tmplErr != nil {
-		panic(fmt.Errorf("error creating template: %w", tmplErr))
-	}
-
-	var b bytes.Buffer
-	execErr := tmpl.Execute(&b, data)
-	if execErr != nil {
-		panic(fmt.Errorf("error executing template: %w", execErr))
-	}
-	return strings.TrimSpace(b.String())
-}
-
 type ResolvedResult struct {
 	Message  string
 	ExitCode int
diff --git a/internal/errors/resolver/template.go b/internal/errors/resolver/template.go
new file mode 100644
index 00000000..96326a10
--- /dev/null
+++ b/internal/errors/resolver/template.go
@@ -0,0 +1,63 @@
+// Copyright 2021 Google LLC
+//
+// 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 resolver
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"text/template"
+)
+
+var baseTemplate = func() *template.Template {
+	tmpl := template.New("base")
+	tmpl = template.Must(tmpl.Parse(detailsHelperTemplate))
+	return tmpl
+}()
+
+var (
+	// detailsHelperTemplate is a helper subtemplate that is available to
+	// the top-level templates. It is useful when including information from
+	// execing other commands in the error message.
+	detailsHelperTemplate = `
+{{- define "ExecOutputDetails" }}
+{{- if or (gt (len .stdout) 0) (gt (len .stderr) 0)}}
+{{ printf "\nDetails:" }}
+{{- end }}
+
+{{- if gt (len .stdout) 0 }}
+{{ printf "%s" .stdout }}
+{{- end }}
+
+{{- if gt (len .stderr) 0 }}
+{{ printf "%s" .stderr }}
+{{- end }}
+{{ end }}
+`
+)
+
+// ExecuteTemplate takes the provided template string and data, and renders
+// the template. If something goes wrong, it panics.
+func ExecuteTemplate(text string, data interface{}) string {
+	tmpl := template.Must(baseTemplate.Clone())
+	template.Must(tmpl.Parse(text))
+
+	var b bytes.Buffer
+	execErr := tmpl.Execute(&b, data)
+	if execErr != nil {
+		panic(fmt.Errorf("error executing template: %w", execErr))
+	}
+	return strings.TrimSpace(b.String())
+}
diff --git a/internal/cmdrender/runtime/container.go b/internal/fnruntime/container.go
similarity index 99%
rename from internal/cmdrender/runtime/container.go
rename to internal/fnruntime/container.go
index dd8c6f54..bf5e5620 100644
--- a/internal/cmdrender/runtime/container.go
+++ b/internal/fnruntime/container.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package runtime
+package fnruntime
 
 import (
 	"bytes"
diff --git a/internal/gitutil/errors.go b/internal/gitutil/errors.go
new file mode 100644
index 00000000..d1e9d2d2
--- /dev/null
+++ b/internal/gitutil/errors.go
@@ -0,0 +1,102 @@
+// Copyright 2021 Google LLC
+//
+// 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 gitutil
+
+import (
+	"regexp"
+	"strings"
+
+	"github.com/GoogleContainerTools/kpt/internal/errors"
+)
+
+// GitExecErrorType is used to enumerate git errors.
+type GitExecErrorType int
+
+const (
+	// Unknown is used when we can't classify an error into any of the other
+	// categories.
+	Unknown GitExecErrorType = iota
+	// GitExecutableNotFound means the git executable wasn't available.
+	GitExecutableNotFound
+	// UnknownReference means that provided reference (tag, branch) wasn't
+	// found
+	UnknownReference
+	// HTTPSAuthRequired means we try to access the repo using the https
+	// protocol, but the repo required authentication.
+	HTTPSAuthRequired
+	// RepositoryNotFound means the provided repo uri doesn't seem to point
+	// to a valid git repo.
+	RepositoryNotFound
+	// RepositoryUnavailable means we weren't able to connect to the provided
+	// uri.
+	RepositoryUnavailable
+)
+
+// GitExecError is an error type returned if kpt encounters an error while
+// executing a git command. It includes information about the command that
+// was executed and the output from git.
+type GitExecError struct {
+	Type    GitExecErrorType
+	Args    []string
+	Err     error
+	Command string
+	Repo    string
+	Ref     string
+	StdErr  string
+	StdOut  string
+}
+
+func (e *GitExecError) Error() string {
+	b := new(strings.Builder)
+	b.WriteString(e.Err.Error())
+	b.WriteString(": ")
+	b.WriteString(e.StdErr)
+	return b.String()
+}
+
+// AmendGitExecError provides a way to amend the GitExecError returned by
+// the GitLocalRunner.run command.
+func AmendGitExecError(err error, f func(e *GitExecError)) {
+	var gitExecErr *GitExecError
+	if errors.As(err, &gitExecErr) {
+		f(gitExecErr)
+	}
+}
+
+// determineErrorType looks at the output to stderr after executing a git
+// command and tries to categorize the error.
+func determineErrorType(stdErr string) GitExecErrorType {
+	switch {
+	case strings.Contains(stdErr, "unknown revision or path not in the working tree"):
+		return UnknownReference
+	case strings.Contains(stdErr, "could not read Username"):
+		return HTTPSAuthRequired
+	case strings.Contains(stdErr, "Could not resolve host"):
+		return RepositoryUnavailable
+	case matches(`fatal: repository '.*' not found`, stdErr):
+		return RepositoryNotFound
+	}
+	return Unknown
+}
+
+func matches(pattern, s string) bool {
+	matched, err := regexp.Match(pattern, []byte(s))
+	if err != nil {
+		// This should only return an error if the pattern is invalid, so
+		// we just panic if that happens.
+		panic(err)
+	}
+	return matched
+}
diff --git a/internal/gitutil/gitutil.go b/internal/gitutil/gitutil.go
index e64b6520..6e391014 100644
--- a/internal/gitutil/gitutil.go
+++ b/internal/gitutil/gitutil.go
@@ -41,8 +41,10 @@ func NewLocalGitRunner(pkg string) (*GitLocalRunner, error) {
 	const op errors.Op = "gitutil.NewLocalGitRunner"
 	p, err := exec.LookPath("git")
 	if err != nil {
-		return nil, errors.E(op, errors.Git,
-			fmt.Errorf("no 'git' program on path: %w", err))
+		return nil, errors.E(op, errors.Git, &GitExecError{
+			Type: GitExecutableNotFound,
+			Err:  err,
+		})
 	}
 
 	return &GitLocalRunner{
@@ -95,7 +97,9 @@ func (g *GitLocalRunner) run(ctx context.Context, verbose bool, command string,
 	fullArgs := append([]string{command}, args...)
 	cmd := exec.CommandContext(ctx, g.gitPath, fullArgs...)
 	cmd.Dir = g.Dir
-	cmd.Env = os.Environ()
+	// Disable git prompting the user for credentials.
+	cmd.Env = append(os.Environ(),
+		"GIT_TERMINAL_PROMPT=0")
 
 	cmdStdout := &bytes.Buffer{}
 	cmdStderr := &bytes.Buffer{}
@@ -118,6 +122,7 @@ func (g *GitLocalRunner) run(ctx context.Context, verbose bool, command string,
 	}
 	if err != nil {
 		return RunResult{}, errors.E(op, errors.Git, &GitExecError{
+			Type:    determineErrorType(cmdStderr.String()),
 			Args:    args,
 			Command: command,
 			Err:     err,
@@ -131,31 +136,6 @@ func (g *GitLocalRunner) run(ctx context.Context, verbose bool, command string,
 	}, nil
 }
 
-type GitExecError struct {
-	Args    []string
-	Err     error
-	Command string
-	Repo    string
-	Ref     string
-	StdErr  string
-	StdOut  string
-}
-
-func (e *GitExecError) Error() string {
-	b := new(strings.Builder)
-	b.WriteString(e.Err.Error())
-	b.WriteString(": ")
-	b.WriteString(e.StdErr)
-	return b.String()
-}
-
-func AmendGitExecError(err error, f func(e *GitExecError)) {
-	var gitExecErr *GitExecError
-	if errors.As(err, &gitExecErr) {
-		f(gitExecErr)
-	}
-}
-
 // NewGitUpstreamRepo returns a new GitUpstreamRepo for an upstream package.
 func NewGitUpstreamRepo(ctx context.Context, uri string) (*GitUpstreamRepo, error) {
 	const op errors.Op = "gitutil.NewGitUpstreamRepo"
diff --git a/internal/util/cmdutil/cmdutil.go b/internal/util/cmdutil/cmdutil.go
index 60296ac8..01a56d6c 100644
--- a/internal/util/cmdutil/cmdutil.go
+++ b/internal/util/cmdutil/cmdutil.go
@@ -23,7 +23,6 @@ import (
 	"strings"
 
 	"github.com/GoogleContainerTools/kpt/internal/errors"
-	goerrors "github.com/go-errors/errors"
 	"github.com/spf13/cobra"
 )
 
@@ -40,13 +39,12 @@ func FixDocs(old, new string, c *cobra.Command) {
 	c.Example = strings.ReplaceAll(c.Example, old, new)
 }
 
-func PrintErrorStacktrace(err error) {
+func PrintErrorStacktrace() bool {
 	e := os.Getenv(StackTraceOnErrors)
 	if StackOnError || e == trueString || e == "1" {
-		if err, ok := err.(*goerrors.Error); ok {
-			fmt.Fprintf(os.Stderr, "%s", err.Stack())
-		}
+		return true
 	}
+	return false
 }
 
 // StackOnError if true, will print a stack trace on failure.
diff --git a/internal/util/parse/parse.go b/internal/util/parse/parse.go
index 88835fe4..296be83e 100644
--- a/internal/util/parse/parse.go
+++ b/internal/util/parse/parse.go
@@ -162,7 +162,7 @@ func getDest(v, repo, subdir string) (string, error) {
 		parent := filepath.Dir(v)
 		if _, err := os.Stat(parent); os.IsNotExist(err) {
 			// error -- fetch to directory where parent does not exist
-			return "", errors.Errorf("parent directory %s does not exist", parent)
+			return "", errors.Errorf("parent directory %q does not exist", parent)
 		}
 		// fetch to a specific directory -- don't default the name
 		return v, nil
@@ -180,7 +180,7 @@ func getDest(v, repo, subdir string) (string, error) {
 
 	// make sure the destination directory does not yet exist yet
 	if _, err := os.Stat(v); !os.IsNotExist(err) {
-		return "", errors.Errorf("destination directory %s already exists", v)
+		return "", errors.Errorf("destination directory %q already exists", v)
 	}
 	return v, nil
 }
diff --git a/main.go b/main.go
index 9c02a751..ed0d3359 100644
--- a/main.go
+++ b/main.go
@@ -15,7 +15,7 @@
 //go:generate $GOBIN/mdtogo site/reference/live internal/docs/generated/livedocs --license=none --recursive=true --strategy=cmdDocs
 //go:generate $GOBIN/mdtogo site/reference/pkg internal/docs/generated/pkgdocs --license=none --recursive=true --strategy=cmdDocs
 //go:generate $GOBIN/mdtogo site/reference/fn internal/docs/generated/fndocs --license=none --recursive=true --strategy=cmdDocs
-//go:generate $GOBIN/mdtogo site/reference internal/docs/generated/overview --license=none --strategy=cmdDocs
+//go:generate $GOBIN/mdtogo site/reference/README.md internal/docs/generated/overview --license=none --strategy=cmdDocs
 package main
 
 import (
@@ -26,6 +26,7 @@ import (
 
 	"github.com/GoogleContainerTools/kpt/internal/errors"
 	"github.com/GoogleContainerTools/kpt/internal/errors/resolver"
+	"github.com/GoogleContainerTools/kpt/internal/util/cmdutil"
 	"github.com/GoogleContainerTools/kpt/run"
 	"github.com/spf13/cobra"
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
@@ -75,8 +76,7 @@ func runMain() int {
 func handleErr(cmd *cobra.Command, err error) int {
 	// First attempt to see if we can resolve the error into a specific
 	// error message.
-	re, resolved := resolver.ResolveError(err)
-	if resolved {
+	if re, resolved := resolver.ResolveError(err); resolved {
 		fmt.Fprintf(cmd.ErrOrStderr(), "%s \n", re.Message)
 		return re.ExitCode
 	}
@@ -84,6 +84,11 @@ func handleErr(cmd *cobra.Command, err error) int {
 	// Then try to see if it is of type *errors.Error
 	var kptErr *errors.Error
 	if errors.As(err, &kptErr) {
+		unwrapped, ok := errors.UnwrapErrors(kptErr)
+		if ok && !cmdutil.PrintErrorStacktrace() {
+			fmt.Fprintf(cmd.ErrOrStderr(), "Error: %s \n", unwrapped.Error())
+			return 1
+		}
 		fmt.Fprintf(cmd.ErrOrStderr(), "%s \n", kptErr.Error())
 		return 1
 	}
diff --git a/mdtogo/cmddocs/cmddocs.go b/mdtogo/cmddocs/cmddocs.go
index d7a8f424..b9790cd4 100644
--- a/mdtogo/cmddocs/cmddocs.go
+++ b/mdtogo/cmddocs/cmddocs.go
@@ -41,6 +41,8 @@ func ParseCmdDocs(files []string) []doc {
 }
 
 var (
+	hugoHideDirectives = regexp.MustCompile("(?ms){{% hide %}}.+?{{% /hide %}}")
+	mdTag              = regexp.MustCompile(`<!--.+-->`)
 	// Capture content between opening and closing tags like <!--mdtogo:(Variable)(Short)>(Content)<!--mdtogo-->
 	mdtogoTag = regexp.MustCompile(`<!--mdtogo:(\S*)(Short|Long|Examples)-->([\s\S]*?)<!--mdtogo-->`)
 	// Capture content within a tag like <!--mdtogo:(Variable)(Short) (Content)-->
@@ -100,6 +102,7 @@ func cleanName(name string) string {
 func cleanUpContent(text string) string {
 	var lines []string
 
+	text = hugoHideDirectives.ReplaceAllString(text, "")
 	scanner := bufio.NewScanner(bytes.NewBufferString(strings.Trim(text, "\n")))
 
 	indent := false
@@ -121,6 +124,10 @@ func cleanUpContent(text string) string {
 			line = fmt.Sprintf("%s:", strings.TrimSpace(line))
 		}
 
+		if mdTag.MatchString(line) {
+			continue
+		}
+
 		lines = append(lines, line)
 	}
 
diff --git a/package-examples/nginx/deployment.yaml b/package-examples/nginx/deployment.yaml
index 4fcc317b..fd64bd3e 100644
--- a/package-examples/nginx/deployment.yaml
+++ b/package-examples/nginx/deployment.yaml
@@ -27,6 +27,6 @@ spec:
     spec:
       containers:
         - name: nginx
-          image: nginx:1.14.2
+          image: 'nginx:1.14.2'
           ports:
             - containerPort: 80
diff --git a/package-examples/wordpress/deployment/deployment.yaml b/package-examples/wordpress/deployment/deployment.yaml
index a8b80672..fccdbd81 100644
--- a/package-examples/wordpress/deployment/deployment.yaml
+++ b/package-examples/wordpress/deployment/deployment.yaml
@@ -11,8 +11,7 @@
 # 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.
-
-apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
+apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: wordpress
@@ -23,8 +22,6 @@ spec:
     matchLabels:
       app: wordpress
       tier: frontend
-  strategy:
-    type: Recreate
   template:
     metadata:
       labels:
@@ -32,8 +29,11 @@ spec:
         tier: frontend
     spec:
       containers:
-        - image: wordpress:4.8-apache # {"$kpt-set":"image-tag"}
-          name: wordpress
+        - name: wordpress
+          image: wordpress:4.8-apache # kpt-set: ${wp-image}:${wp-tag}
+          ports:
+            - name: wordpress
+              containerPort: 80
           env:
             - name: WORDPRESS_DB_HOST
               value: wordpress-mysql
@@ -42,9 +42,6 @@ spec:
                 secretKeyRef:
                   name: mysql-pass
                   key: password
-          ports:
-            - containerPort: 80
-              name: wordpress
           volumeMounts:
             - name: wordpress-persistent-storage
               mountPath: /var/www/html
@@ -52,3 +49,5 @@ spec:
         - name: wordpress-persistent-storage
           persistentVolumeClaim:
             claimName: wp-pv-claim
+  strategy:
+    type: Recreate
diff --git a/package-examples/wordpress/deployment/volume.yaml b/package-examples/wordpress/deployment/volume.yaml
index 30ae80fc..8f21d6ac 100644
--- a/package-examples/wordpress/deployment/volume.yaml
+++ b/package-examples/wordpress/deployment/volume.yaml
@@ -11,7 +11,6 @@
 # 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.
-
 apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
@@ -19,8 +18,8 @@ metadata:
   labels:
     app: wordpress
 spec:
-  accessModes:
-    - ReadWriteOnce
   resources:
     requests:
       storage: 20Gi
+  accessModes:
+    - ReadWriteOnce
diff --git a/package-examples/wordpress/mysql/deployment.yaml b/package-examples/wordpress/mysql/deployment.yaml
index 98ea923a..812872ad 100644
--- a/package-examples/wordpress/mysql/deployment.yaml
+++ b/package-examples/wordpress/mysql/deployment.yaml
@@ -11,7 +11,6 @@
 # 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.
-
 apiVersion: v1
 kind: Service
 metadata:
@@ -19,11 +18,11 @@ metadata:
   labels:
     app: wordpress
 spec:
-  ports:
-  - port: 3306
   selector:
     app: wordpress
     tier: mysql
+  ports:
+    - port: 3306
   clusterIP: None
 ---
 apiVersion: v1
@@ -33,11 +32,11 @@ metadata:
   labels:
     app: wordpress
 spec:
-  accessModes:
-  - ReadWriteOnce
   resources:
     requests:
       storage: 20Gi
+  accessModes:
+    - ReadWriteOnce
 ---
 apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
 kind: Deployment
@@ -50,8 +49,6 @@ spec:
     matchLabels:
       app: wordpress
       tier: mysql
-  strategy:
-    type: Recreate
   template:
     metadata:
       labels:
@@ -59,21 +56,23 @@ spec:
         tier: mysql
     spec:
       containers:
-      - image: mysql:5.6 # kpt-set: ${ms-image}:${ms-tag}
-        name: mysql
-        env:
-        - name: MYSQL_ROOT_PASSWORD
-          valueFrom:
-            secretKeyRef:
-              name: mysql-pass
-              key: password
-        ports:
-        - containerPort: 3306
-          name: mysql
-        volumeMounts:
-        - name: mysql-persistent-storage
-          mountPath: /var/lib/mysql
+        - name: mysql
+          image: mysql:5.6 # kpt-set: ${ms-image}:${ms-tag}
+          ports:
+            - name: mysql
+              containerPort: 3306
+          env:
+            - name: MYSQL_ROOT_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: mysql-pass
+                  key: password
+          volumeMounts:
+            - name: mysql-persistent-storage
+              mountPath: /var/lib/mysql
       volumes:
-      - name: mysql-persistent-storage
-        persistentVolumeClaim:
-          claimName: mysql-pv-claim
+        - name: mysql-persistent-storage
+          persistentVolumeClaim:
+            claimName: mysql-pv-claim
+  strategy:
+    type: Recreate
diff --git a/package-examples/wordpress/service.yaml b/package-examples/wordpress/service.yaml
index f9059239..49b6f5be 100644
--- a/package-examples/wordpress/service.yaml
+++ b/package-examples/wordpress/service.yaml
@@ -11,7 +11,6 @@
 # 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.
-
 apiVersion: v1
 kind: Service
 metadata:
@@ -19,9 +18,9 @@ metadata:
   labels:
     app: wordpress
 spec:
-  ports:
-    - port: 80
+  type: LoadBalancer
   selector:
     app: wordpress
     tier: frontend
-  type: LoadBalancer
+  ports:
+    - port: 80
diff --git a/site/book/01-getting-started/02-quickstart.md b/site/book/01-getting-started/02-quickstart.md
index d3fde2a9..66c83321 100644
--- a/site/book/01-getting-started/02-quickstart.md
+++ b/site/book/01-getting-started/02-quickstart.md
@@ -32,7 +32,7 @@ will explain the `Kptfile` in detail.
 Initialize a local Git repo and commit the forked copy of the package:
 
 ```shell
-git init; git add .; git commit -am "Pristine nginx package"
+$ git init; git add .; git commit -am "Pristine nginx package"
 ```
 
 ## Customize the package
@@ -127,7 +127,7 @@ the upstream changes with changes to your local package.
 First, commit your local changes:
 
 ```shell
-git add .; git commit -am "My customizations"
+$ git add .; git commit -am "My customizations"
 ```
 
 Then update to version `v0.3`:
diff --git a/site/book/03-packages/01-getting-a-package.md b/site/book/03-packages/01-getting-a-package.md
index 049eb43e..0c37dddc 100644
--- a/site/book/03-packages/01-getting-a-package.md
+++ b/site/book/03-packages/01-getting-a-package.md
@@ -105,10 +105,9 @@ follows the familiar structure of KRM resources, the name of the package is also
 `metadata.name` field. This must always be the name of the directory, and kpt will update it
 automatically when forking a package. In this case, `metadata.name` is set to `mywordpress`.
 
-The name of a package is unique within its parent package, but it may not be unique in a deeply
-nested package hierarchy (i.e. Depth > 2). The _unique identifier_ for a package is defined as the
-relative path from the top package to the subpackage. For example, we could have two subpackages
-with the name `mysql` having the following identifiers:
+In general, the package name is not unique. The _unique identifier_ for a
+package is defined as the relative path from the top package to the subpackage. For example, we
+could have two subpackages with the name `mysql` having the following identifiers:
 
 - `wordpress/backend/mysql`
 - `wordpress/frontend/mysql`
diff --git a/site/book/03-packages/03-editing-a-package.md b/site/book/03-packages/03-editing-a-package.md
index 434589ee..f2ba31b1 100644
--- a/site/book/03-packages/03-editing-a-package.md
+++ b/site/book/03-packages/03-editing-a-package.md
@@ -16,6 +16,14 @@ the package. This process can be manual or automated depending on your use case.
 We will cover package composition later in this chapter. For now, let's focus on editing resources
 _within_ a package.
 
+## Initialize the local repo
+
+Before we make any changes to package, we should first initialize and commit the pristine package:
+
+```shell
+$ git init; git add .; git commit -am "Pristine wordpress package"
+```
+
 ## Manual edits
 
 As mentioned earlier, you can manually edit or author KRM resources using your favorite editor.
@@ -39,11 +47,25 @@ For example, setting a label on all the resources in the `wordpress` package can
 using the following function:
 
 ```shell
-$ cd wordpress
-$ kpt fn eval --image gcr.io/kpt-fn/set-label:v0.1 -- env=dev
+$ kpt fn eval --image gcr.io/kpt-fn/set-label:v0.1 wordpress -- env=dev
 ```
 
 [Chapter 4] discusses different ways of running functions in detail.
 
+## Commit your changes
+
+Regardless of how you edited the package, you can see the changes compared to the
+pristine copy:
+
+```shell
+$ git diff
+```
+
+If you're happy with the changes, commit them:
+
+```shell
+$ git add .; git commit -am "My customizations"
+```
+
 [cloud code]: https://cloud.google.com/code
 [chapter 4]: /book/04-using-functions/
diff --git a/site/book/03-packages/04-updating-a-package.md b/site/book/03-packages/04-updating-a-package.md
index 8796d3d8..b154501b 100644
--- a/site/book/03-packages/04-updating-a-package.md
+++ b/site/book/03-packages/04-updating-a-package.md
@@ -39,6 +39,12 @@ strategy is used which performs a structural comparison of the resource using Op
 
 > Refer to the [command reference][update-doc] for more details.
 
-[update-doc]: /reference/pkg/update/
-
 TODO(#1827): Handling merge conflicts
+
+Once you have successfully updated the package, commit the changes:
+
+```shell
+$ git add .; git commit -am "Updated wordpress to v0.2"
+```
+
+[update-doc]: /reference/pkg/update/
diff --git a/site/book/03-packages/06-composing-a-package.md b/site/book/03-packages/06-composing-a-package.md
index 4983a6f1..0f4f075f 100644
--- a/site/book/03-packages/06-composing-a-package.md
+++ b/site/book/03-packages/06-composing-a-package.md
@@ -36,9 +36,8 @@ We're going to add back the `mysql` subpackage using the two different approache
 ### Create a new package
 
 ```shell
-$ cd wordpress
-$ mkdir mysql
-$ kpt pkg init mysql
+$ mkdir wordpress/mysql
+$ kpt pkg init wordpress/mysql
 # author resources in mysql
 ```
 
@@ -47,12 +46,10 @@ This creates a [dependent package].
 ### Get an existing package
 
 ```shell
-$ cd wordpress
-$ kpt pkg get https://github.com/kubernetes/website.git/content/en/examples/application/mysql@snapshot-initial-v1.20
+$ rm -rf wordpress/mysql
+$ kpt pkg get https://github.com/kubernetes/website.git/content/en/examples/application/mysql@snapshot-initial-v1.20 wordpress/mysql
 ```
 
-TODO(#1829): This can be simplified.
-
 This creates an [independent package]. If you wish to make this a dependent package, you
 can delete the `upstream` and `upstreamLock` sections of the `Kptfile` in `mysql` directory.
 
diff --git a/site/book/03-packages/07-publishing-a-package.md b/site/book/03-packages/07-publishing-a-package.md
index ced7482a..59f2d8bc 100644
--- a/site/book/03-packages/07-publishing-a-package.md
+++ b/site/book/03-packages/07-publishing-a-package.md
@@ -30,7 +30,7 @@ You can then fetch the published package:
 $ kpt pkg get <MY_REPO_URL>/@v0.1
 ```
 
-## Per-directory Versioning
+## Monorepo Versioning
 
 You may have a Git repo containing multiple packages. kpt provides a tagging
 convention to enable packages to be independently versioned.
diff --git a/site/reference/live/status/README.md b/site/reference/live/status/README.md
index ecc737d6..4861a954 100644
--- a/site/reference/live/status/README.md
+++ b/site/reference/live/status/README.md
@@ -62,6 +62,7 @@ kpt live status my-app/
 # Monitor status for a set of resources based on manifests. Output in table format:
 kpt live status my-app/ --poll-until=forever --output=table
 ```
+<!--mdtogo-->
 
 ### Synopsis
 <!--mdtogo:Long-->
diff --git a/site/reference/pkg/README.md b/site/reference/pkg/README.md
index 249aaba5..20bc1662 100644
--- a/site/reference/pkg/README.md
+++ b/site/reference/pkg/README.md
@@ -14,3 +14,6 @@ description: >
 The `pkg` command group contains subcommands for fetching and updating `kpt` package
 from git repositories.
 <!--mdtogo-->
+
+<!--mdtogo:Examples-->
+<!--mdtogo-->



Test patch:
diff --git a/e2e/testdata/fn-render/missing-fnconfig/.expected/config.yaml b/e2e/testdata/fn-render/missing-fnconfig/.expected/config.yaml
index 33fb02e0..4f9310d9 100644
--- a/e2e/testdata/fn-render/missing-fnconfig/.expected/config.yaml
+++ b/e2e/testdata/fn-render/missing-fnconfig/.expected/config.yaml
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 exitCode: 1
-stdErr: 'fn gcr.io/kpt-fn/set-label:unstable: missing function config "labelconfig1.yaml"'
+stdErr: 'Error: missing function config "labelconfig1.yaml"'
 stdOut: |
   [RUNNING] "gcr.io/kpt-fn/set-label:unstable"
   [PASS] "gcr.io/kpt-fn/set-label:unstable"
diff --git a/internal/cmdrender/runtime/container_test.go b/internal/fnruntime/container_test.go
similarity index 92%
rename from internal/cmdrender/runtime/container_test.go
rename to internal/fnruntime/container_test.go
index 21b29524..866e0611 100644
--- a/internal/cmdrender/runtime/container_test.go
+++ b/internal/fnruntime/container_test.go
@@ -14,14 +14,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package runtime_test
+package fnruntime_test
 
 import (
 	"bytes"
 	"context"
 	"testing"
 
-	"github.com/GoogleContainerTools/kpt/internal/cmdrender/runtime"
+	"github.com/GoogleContainerTools/kpt/internal/fnruntime"
 	"github.com/GoogleContainerTools/kpt/internal/printer"
 	"github.com/stretchr/testify/assert"
 )
@@ -50,7 +50,7 @@ func TestContainerFn(t *testing.T) {
 		ctx := context.Background()
 		t.Run(tt.name, func(t *testing.T) {
 			outBuff, errBuff := &bytes.Buffer{}, &bytes.Buffer{}
-			instance := runtime.ContainerFn{
+			instance := fnruntime.ContainerFn{
 				Ctx:   printer.WithContext(ctx, printer.New(outBuff, errBuff)),
 				Image: tt.image,
 			}
diff --git a/internal/testutil/setup_manager.go b/internal/testutil/setup_manager.go
index ef60ed5e..dadb2015 100644
--- a/internal/testutil/setup_manager.go
+++ b/internal/testutil/setup_manager.go
@@ -65,6 +65,11 @@ type Content struct {
 	Pkg          *pkgbuilder.RootPkg
 	Tag          string
 	Message      string
+
+	// UpdateFunc is invoked after the repo has been updated with the new
+	// content, but before it is committed. This allows for making changes
+	// that isn't supported by the pkgbuilder (like creating symlinks).
+	UpdateFunc func(path string) error
 }
 
 // Init initializes test data
@@ -139,6 +144,7 @@ type GitDirectory interface {
 	ReplaceData(data string) error
 	Commit(message string) (string, error)
 	Tag(tagName string) error
+	CustomUpdate(updateFunc func(string) error) error
 }
 
 func UpdateGitDir(t *testing.T, name string, gitDir GitDirectory, changes []Content, repos map[string]*TestGitRepo) error {
@@ -165,6 +171,13 @@ func UpdateGitDir(t *testing.T, name string, gitDir GitDirectory, changes []Cont
 			return err
 		}
 
+		if content.UpdateFunc != nil {
+			err = gitDir.CustomUpdate(content.UpdateFunc)
+			if !assert.NoError(t, err) {
+				return err
+			}
+		}
+
 		sha, err := gitDir.Commit(content.Message)
 		if !assert.NoError(t, err) {
 			return err
diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go
index 67922e81..2dc03a50 100644
--- a/internal/testutil/testutil.go
+++ b/internal/testutil/testutil.go
@@ -319,6 +319,12 @@ func (g *TestGitRepo) ReplaceData(data string) error {
 	return replaceData(g.RepoDirectory, data)
 }
 
+// CustomUpdate executes the provided update function and passes in the
+// path to the directory of the repository.
+func (g *TestGitRepo) CustomUpdate(f func(string) error) error {
+	return f(g.RepoDirectory)
+}
+
 // SetupTestGitRepo initializes a new git repository and populates it with data from a source
 func (g *TestGitRepo) SetupTestGitRepo(name string, data []Content, repos map[string]*TestGitRepo) error {
 	defaultBranch := "main"
@@ -775,6 +781,12 @@ func (w *TestWorkspace) ReplaceData(data string) error {
 	return replaceData(filepath.Join(w.WorkspaceDirectory, w.PackageDir), data)
 }
 
+// CustomUpdate executes the provided update function and passes in the
+// path to the directory of the repository.
+func (w *TestWorkspace) CustomUpdate(f func(string) error) error {
+	return f(w.WorkspaceDirectory)
+}
+
 // Commit performs a git commit
 func (w *TestWorkspace) Commit(message string) (string, error) {
 	return commit(w.WorkspaceDirectory, message)
diff --git a/internal/util/get/get_test.go b/internal/util/get/get_test.go
index fc7b00b0..bb9e0c0a 100644
--- a/internal/util/get/get_test.go
+++ b/internal/util/get/get_test.go
@@ -1351,21 +1351,19 @@ func TestCommand_Run_symlinks(t *testing.T) {
 							WithKptfile().
 							WithResource(pkgbuilder.ConfigMapResource),
 					),
+				UpdateFunc: func(path string) error {
+					// Create symlink in the upstream repo.
+					return os.Symlink(filepath.Join(path, "subpkg"),
+						filepath.Join(path, "subpkg-sym"))
+				},
 			},
 		},
 	})
 	defer clean()
 	upstreamRepo := repos[testutil.Upstream]
 
-	// Create a symlink in the upstream repo
-	err := os.Symlink(filepath.Join(upstreamRepo.RepoDirectory, "subpkg"),
-		filepath.Join(upstreamRepo.RepoDirectory, "subpkg-sym"))
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-
 	destinationDir := filepath.Join(w.WorkspaceDirectory, upstreamRepo.RepoName)
-	err = Command{
+	err := Command{
 		Git: &kptfilev1alpha2.Git{
 			Repo:      upstreamRepo.RepoDirectory,
 			Directory: "/",
diff --git a/internal/util/update/update_test.go b/internal/util/update/update_test.go
index 45d3a9a9..ed32fb1a 100644
--- a/internal/util/update/update_test.go
+++ b/internal/util/update/update_test.go
@@ -815,6 +815,125 @@ func TestCommand_Run_failInvalidRef(t *testing.T) {
 	}
 }
 
+func TestCommand_Run_manualChange(t *testing.T) {
+	g := &testutil.TestSetupManager{
+		T: t,
+		ReposChanges: map[string][]testutil.Content{
+			testutil.Upstream: {
+				{
+					Pkg: pkgbuilder.NewRootPkg().
+						WithResource(pkgbuilder.DeploymentResource),
+					Branch: masterBranch,
+					Tag:    "v1",
+				},
+				{
+					Pkg: pkgbuilder.NewRootPkg().
+						WithResource(pkgbuilder.ConfigMapResource),
+					Tag: "v2",
+				},
+				{
+					Pkg: pkgbuilder.NewRootPkg().
+						WithResource(pkgbuilder.SecretResource),
+					Tag: "v3",
+				},
+			},
+		},
+		GetRef: "v1",
+		LocalChanges: []testutil.Content{
+			{
+				Pkg: pkgbuilder.NewRootPkg().
+					WithKptfile(
+						pkgbuilder.NewKptfile().
+							WithUpstreamRef(testutil.Upstream, "/", "v3", "resource-merge").
+							WithUpstreamLockRef(testutil.Upstream, "/", "v1", 0),
+					).
+					WithResource(pkgbuilder.DeploymentResource),
+			},
+		},
+	}
+	defer g.Clean()
+	if !g.Init() {
+		return
+	}
+
+	err := Command{
+		Pkg: pkgtest.CreatePkgOrFail(t, g.LocalWorkspace.FullPackagePath()),
+	}.Run(fake.CtxWithNilPrinter())
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
+
+	expLocal := pkgbuilder.NewRootPkg().
+		WithKptfile(
+			pkgbuilder.NewKptfile().
+				WithUpstreamRef(testutil.Upstream, "/", "v3", "resource-merge").
+				WithUpstreamLockRef(testutil.Upstream, "/", "v3", 2),
+		).
+		WithResource(pkgbuilder.SecretResource)
+	expectedPath := expLocal.ExpandPkgWithName(t,
+		g.LocalWorkspace.PackageDir, testutil.ToReposInfo(g.Repos))
+
+	testutil.KptfileAwarePkgEqual(t, expectedPath, g.LocalWorkspace.FullPackagePath())
+}
+
+func TestCommand_Run_symlinks(t *testing.T) {
+	g := &testutil.TestSetupManager{
+		T: t,
+		ReposChanges: map[string][]testutil.Content{
+			testutil.Upstream: {
+				{
+					Branch: masterBranch,
+					Pkg: pkgbuilder.NewRootPkg().
+						WithResource(pkgbuilder.DeploymentResource),
+				},
+				{
+					Pkg: pkgbuilder.NewRootPkg().
+						WithResource(pkgbuilder.SecretResource).
+						WithSubPackages(
+							pkgbuilder.NewSubPkg("subpkg").
+								WithKptfile().
+								WithResource(pkgbuilder.ConfigMapResource),
+						),
+					UpdateFunc: func(path string) error {
+						// Create symlink in the upstream repo.
+						return os.Symlink(filepath.Join(path, "subpkg"),
+							filepath.Join(path, "subpkg-sym"))
+					},
+				},
+			},
+		},
+		GetRef: masterBranch,
+	}
+	defer g.Clean()
+	if !g.Init() {
+		return
+	}
+	upstreamRepo := g.Repos[testutil.Upstream]
+
+	err := Command{
+		Pkg: pkgtest.CreatePkgOrFail(t, g.LocalWorkspace.FullPackagePath()),
+	}.Run(fake.CtxWithNilPrinter())
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
+
+	expectedPkg := pkgbuilder.NewRootPkg().
+		WithKptfile(
+			pkgbuilder.NewKptfile().
+				WithUpstreamRef(testutil.Upstream, "/", "master", "resource-merge").
+				WithUpstreamLockRef(testutil.Upstream, "/", "master", 1),
+		).
+		WithResource(pkgbuilder.SecretResource).
+		WithSubPackages(
+			pkgbuilder.NewSubPkg("subpkg").
+				WithKptfile().
+				WithResource(pkgbuilder.ConfigMapResource),
+		)
+	expectedPath := expectedPkg.ExpandPkgWithName(t, upstreamRepo.RepoName, testutil.ToReposInfo(g.Repos))
+
+	testutil.KptfileAwarePkgEqual(t, expectedPath, g.LocalWorkspace.FullPackagePath())
+}
+
 func TestCommand_Run_badStrategy(t *testing.T) {
 	strategy := kptfilev1alpha2.UpdateStrategyType("foo")