zip: exclude VCS directories from CreateFromDir

Fixes golang/go#35372

Change-Id: Id6a9da3755e7da66f908f917d766e473a9108622
Reviewed-on: https://go-review.googlesource.com/c/mod/+/205837
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/zip/testdata/create_from_dir/exclude_vcs.txt b/zip/testdata/create_from_dir/exclude_vcs.txt
new file mode 100644
index 0000000..5c9c966
--- /dev/null
+++ b/zip/testdata/create_from_dir/exclude_vcs.txt
@@ -0,0 +1,11 @@
+path=example.com/m
+version=v1.0.0
+hash=h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+-- .bzr/exclude --
+exclude
+-- .git/exclude --
+exclude
+-- .hg/exclude --
+exclude
+-- .svn/exclude --
+exclude
diff --git a/zip/zip.go b/zip/zip.go
index d2811de..37c7642 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -234,6 +234,8 @@
 // belong in the module zip. In particular, CreateFromDir will not include
 // files in modules found in subdirectories, most files in vendor directories,
 // or irregular files (such as symbolic links) in the output archive.
+// Additionally, unlike Create, CreateFromDir will not include directories
+// named ".bzr", ".git", ".hg", or ".svn".
 func CreateFromDir(w io.Writer, m module.Version, dir string) (err error) {
 	defer func() {
 		if zerr, ok := err.(*zipError); ok {
@@ -257,6 +259,14 @@
 				return nil
 			}
 
+			// Skip VCS directories.
+			// fossil repos are regular files with arbitrary names, so we don't try
+			// to exclude them.
+			switch filepath.Base(filePath) {
+			case ".bzr", ".git", ".hg", ".svn":
+				return filepath.SkipDir
+			}
+
 			// Skip some subdirectories inside vendor, but maintain bug
 			// golang.org/issue/31562, described in isVendoredPackage.
 			// We would like Create and CreateFromDir to produce the same result