Keep directories when moving glob results

Patterns containing multiple globs or a recurisve glob may match
files with the same name in multiple directories.  Keep the relative
directories of matches after the path entry containing a glob.

Bug: 117295826
Test: zip2zip_test.go
Change-Id: I5d663e546953af374175837551d23f484d568377
diff --git a/cmd/zip2zip/zip2zip.go b/cmd/zip2zip/zip2zip.go
index c4fb3d6..491267b 100644
--- a/cmd/zip2zip/zip2zip.go
+++ b/cmd/zip2zip/zip2zip.go
@@ -148,8 +148,13 @@
 				} else {
 					if pathtools.IsGlob(input) {
 						// If the input is a glob then the output is a directory.
-						_, name := filepath.Split(file.Name)
-						newName = filepath.Join(output, name)
+						rel, err := filepath.Rel(constantPartOfPattern(input), file.Name)
+						if err != nil {
+							return err
+						} else if strings.HasPrefix("../", rel) {
+							return fmt.Errorf("globbed path %q was not in %q", file.Name, constantPartOfPattern(input))
+						}
+						newName = filepath.Join(output, rel)
 					} else {
 						// Otherwise it is a file.
 						newName = output
@@ -277,3 +282,24 @@
 	}
 	return false, nil
 }
+
+func constantPartOfPattern(pattern string) string {
+	ret := ""
+	for pattern != "" {
+		var first string
+		first, pattern = splitFirst(pattern)
+		if pathtools.IsGlob(first) {
+			return ret
+		}
+		ret = filepath.Join(ret, first)
+	}
+	return ret
+}
+
+func splitFirst(path string) (string, string) {
+	i := strings.IndexRune(path, filepath.Separator)
+	if i < 0 {
+		return path, ""
+	}
+	return path[:i], path[i+1:]
+}
diff --git a/cmd/zip2zip/zip2zip_test.go b/cmd/zip2zip/zip2zip_test.go
index ae16494..2c4e005 100644
--- a/cmd/zip2zip/zip2zip_test.go
+++ b/cmd/zip2zip/zip2zip_test.go
@@ -352,6 +352,60 @@
 			"a/b",
 		},
 	},
+	{
+		name: "recursive glob",
+
+		inputFiles: []string{
+			"a/a/a",
+			"a/a/b",
+		},
+		args: []string{"a/**/*:b"},
+		outputFiles: []string{
+			"b/a/a",
+			"b/a/b",
+		},
+	},
+	{
+		name: "glob",
+
+		inputFiles: []string{
+			"a/a/a",
+			"a/a/b",
+			"a/b",
+			"a/c",
+		},
+		args: []string{"a/*:b"},
+		outputFiles: []string{
+			"b/b",
+			"b/c",
+		},
+	},
+	{
+		name: "top level glob",
+
+		inputFiles: []string{
+			"a",
+			"b",
+		},
+		args: []string{"*:b"},
+		outputFiles: []string{
+			"b/a",
+			"b/b",
+		},
+	},
+	{
+		name: "multilple glob",
+
+		inputFiles: []string{
+			"a/a/a",
+			"a/a/b",
+		},
+		args: []string{"a/*/*:b"},
+		outputFiles: []string{
+			"b/a/a",
+			"b/a/b",
+		},
+	},
 }
 
 func errorString(e error) string {
@@ -416,3 +470,45 @@
 		})
 	}
 }
+
+func TestConstantPartOfPattern(t *testing.T) {
+	testCases := []struct{ in, out string }{
+		{
+			in:  "",
+			out: "",
+		},
+		{
+			in:  "a",
+			out: "a",
+		},
+		{
+			in:  "*",
+			out: "",
+		},
+		{
+			in:  "a/a",
+			out: "a/a",
+		},
+		{
+			in:  "a/*",
+			out: "a",
+		},
+		{
+			in:  "a/*/a",
+			out: "a",
+		},
+		{
+			in:  "a/**/*",
+			out: "a",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.in, func(t *testing.T) {
+			got := constantPartOfPattern(test.in)
+			if got != test.out {
+				t.Errorf("want %q, got %q", test.out, got)
+			}
+		})
+	}
+}