From dcbe29c7a18887497b4857e64665d25be43f7c7e Mon Sep 17 00:00:00 2001
From: Nick Galbreath <nickg@signalsciences.com>
Date: Thu, 22 Oct 2015 16:37:52 -0500
Subject: [PATCH] Vendor license files

Add functions to test if a file is likely to be legal notice and copies
those files, up through the root of a dependency to the vendor location.

Closes #245
---
 Changelog.md    |  4 +++
 license.go      | 56 +++++++++++++++++++++++++++++++++
 license_test.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++
 rewrite_test.go |  4 +--
 save.go         | 37 ++++++++++++++++++++++
 save_test.go    | 50 +++++++++++++++++++++++++----
 update_test.go  |  4 +--
 version.go      |  2 +-
 8 files changed, 229 insertions(+), 11 deletions(-)
 create mode 100644 license.go
 create mode 100644 license_test.go

diff --git a/Changelog.md b/Changelog.md
index 62c26a8..b7bfb79 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,7 @@
+# v20 2016/11/13
+
+* Attempt to include license files when vendoring. (@client9)
+
 # v19 2016/11/3
 
 * Fix conflict error message. Revisions were swapped. Also better selection of package that needs update.
diff --git a/license.go b/license.go
new file mode 100644
index 0000000..3c56301
--- /dev/null
+++ b/license.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+	"strings"
+)
+
+// LicenseFilePrefix is a list of filename prefixes that indicate it
+//  might contain a software license
+var LicenseFilePrefix = []string{
+	"license",
+	"copying",
+	"unlicense",
+	"copyright",
+	"copyleft",
+}
+
+// LegalFileSubstring are substrings that indicate the file is likely
+// to contain some type of legal declaration.  "legal" is often used
+// that it might moved to LicenseFilePrefix
+var LegalFileSubstring = []string{
+	"legal",
+	"notice",
+	"disclaimer",
+	"patent",
+	"third-party",
+	"thirdparty",
+}
+
+// IsLicenseFile returns true if the filename might be contain a
+// software license
+func IsLicenseFile(filename string) bool {
+	lowerfile := strings.ToLower(filename)
+	for _, prefix := range LicenseFilePrefix {
+		if strings.HasPrefix(lowerfile, prefix) {
+			return true
+		}
+	}
+	return false
+}
+
+// IsLegalFile returns true if the file is likely to contain some type
+// of of legal declaration or licensing information
+func IsLegalFile(filename string) bool {
+	lowerfile := strings.ToLower(filename)
+	for _, prefix := range LicenseFilePrefix {
+		if strings.HasPrefix(lowerfile, prefix) {
+			return true
+		}
+	}
+	for _, substring := range LegalFileSubstring {
+		if strings.Index(lowerfile, substring) != -1 {
+			return true
+		}
+	}
+	return false
+}
diff --git a/license_test.go b/license_test.go
new file mode 100644
index 0000000..15e4b09
--- /dev/null
+++ b/license_test.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+	"testing"
+)
+
+// most of these were found via google searches
+//  site:github.com FILENAME
+//
+func TestLicenseFiles(t *testing.T) {
+	var testcases = []struct {
+		filename string
+		license  bool
+		legal    bool
+	}{
+		{"license", true, true},
+		{"License", true, true},
+		{"LICENSE.md", true, true},
+		{"LICENSE.rst", true, true},
+		{"LICENSE.txt", true, true},
+		{"copying", true, true},
+		{"COPYING.txt", true, true},
+		{"unlicense", true, true},
+		{"copyright", true, true},
+		{"COPYRIGHT.txt", true, true},
+		{"copyleft", true, true},
+		{"COPYLEFT.txt", true, true},
+		{"copyleft.txt", true, true},
+		{"Copyleft.txt", true, true},
+		{"copyleft-next-0.2.1.txt", true, true},
+		{"legal", false, true},
+		{"notice", false, true},
+		{"NOTICE", false, true},
+		{"disclaimer", false, true},
+		{"patent", false, true},
+		{"patents", false, true},
+		{"third-party", false, true},
+		{"thirdparty", false, true},
+		{"thirdparty.txt", false, true},
+		{"license-ThirdParty.txt", true, true},
+		{"LICENSE-ThirdParty.txt", true, true},
+		{"THIRDPARTY.md", false, true},
+		{"third-party.md", false, true},
+		{"THIRD-PARTY.txt", false, true},
+		{"extensions-third-party.md", false, true},
+		{"ThirdParty.md", false, true},
+		{"third-party-licenses.md", false, true},
+		{"0070-01-01-third-party.md", false, true},
+		{"LEGAL.txt", false, true},
+		{"legal.txt", false, true},
+		{"Legal.md", false, true},
+		{"LEGAL.md", false, true},
+		{"legal.rst", false, true},
+		{"Legal.rtf", false, true},
+		{"legal.rtf", false, true},
+		{"PATENTS.TXT", false, true},
+		{"ttf-PATENTS.txt", false, true},
+		{"patents.txt", false, true},
+		{"INRIA-DISCLAIMER.txt", false, true},
+
+		{"MPL-2.0-no-copyleft-exception.txt", false, false},
+	}
+
+	for pos, tt := range testcases {
+		license := IsLicenseFile(tt.filename)
+		if tt.license != license {
+			if license {
+				t.Errorf("%d/file %q is not marked as license", pos, tt.filename)
+			} else {
+				t.Errorf("%d/file %q was marked incorrectly as a license", pos, tt.filename)
+			}
+		}
+
+		legal := IsLegalFile(tt.filename)
+		if tt.legal != legal {
+			if legal {
+				t.Errorf("%d/File %q is not marked as legal file", pos, tt.filename)
+			} else {
+				t.Errorf("%d/File %q was marked incorrectly as a legal file", pos, tt.filename)
+			}
+		}
+	}
+}
diff --git a/rewrite_test.go b/rewrite_test.go
index 715bdc7..fb4228e 100644
--- a/rewrite_test.go
+++ b/rewrite_test.go
@@ -242,7 +242,7 @@ func TestRewrite(t *testing.T) {
 
 	const gopath = "godeptest"
 	defer os.RemoveAll(gopath)
-	for _, test := range cases {
+	for pos, test := range cases {
 		err := os.RemoveAll(gopath)
 		if err != nil {
 			t.Fatal(err)
@@ -261,6 +261,6 @@ func TestRewrite(t *testing.T) {
 			t.Errorf("Unexpected tempfiles: %+v", tempFiles)
 		}
 
-		checkTree(t, &node{src, "", test.want})
+		checkTree(t, pos, &node{src, "", test.want})
 	}
 }
diff --git a/save.go b/save.go
index 84d8830..8e017c0 100644
--- a/save.go
+++ b/save.go
@@ -267,6 +267,8 @@ func removeSrc(srcdir string, deps []Dependency) error {
 }
 
 func copySrc(dir string, deps []Dependency) error {
+	// mapping to see if we visited a parent directory already
+	visited := make(map[string]bool)
 	ok := true
 	for _, dep := range deps {
 		srcdir := filepath.Join(dep.ws, "src")
@@ -280,6 +282,8 @@ func copySrc(dir string, deps []Dependency) error {
 			log.Println(err)
 			ok = false
 		}
+
+		// copy actual dependency
 		vf := dep.vcs.listFiles(dep.dir)
 		w := fs.Walk(dep.dir)
 		for w.Step() {
@@ -289,10 +293,43 @@ func copySrc(dir string, deps []Dependency) error {
 				ok = false
 			}
 		}
+
+		// Look for legal files in root
+		//  some packages are imports as a sub-package but license info
+		//  is at root:  exampleorg/common has license file in exampleorg
+		//
+		if dep.ImportPath == dep.root {
+			// we are already at root
+			continue
+		}
+
+		// prevent copying twice This could happen if we have
+		//   two subpackages listed someorg/common and
+		//   someorg/anotherpack which has their license in
+		//   the parent dir of someorg
+		rootdir := filepath.Join(srcdir, filepath.FromSlash(dep.root))
+		if visited[rootdir] {
+			continue
+		}
+		visited[rootdir] = true
+		vf = dep.vcs.listFiles(rootdir)
+		w = fs.Walk(rootdir)
+		for w.Step() {
+			fname := filepath.Base(w.Path())
+			if IsLegalFile(fname) {
+				err = copyPkgFile(vf, dir, srcdir, w)
+				if err != nil {
+					log.Println(err)
+					ok = false
+				}
+			}
+		}
 	}
+
 	if !ok {
 		return errorCopyingSourceCode
 	}
+
 	return nil
 }
 
diff --git a/save_test.go b/save_test.go
index 6aca045..716466b 100644
--- a/save_test.go
+++ b/save_test.go
@@ -1075,6 +1075,44 @@ func TestSave(t *testing.T) {
 				},
 			},
 		},
+		{ // Copy legal files in parent and dependency directory
+			cwd: "C",
+			start: []*node{
+				{
+					"C",
+					"",
+					[]*node{
+						{"main.go", pkg("main", "D/P", "D/Q"), nil},
+						{"+git", "", nil},
+					},
+				},
+				{
+					"D",
+					"",
+					[]*node{
+						{"LICENSE", pkg("D"), nil},
+						{"P/main.go", pkg("P"), nil},
+						{"P/LICENSE", pkg("P"), nil},
+						{"Q/main.go", pkg("Q"), nil},
+						{"+git", "D1", nil},
+					},
+				},
+			},
+			want: []*node{
+				{"C/main.go", pkg("main", "D/P", "D/Q"), nil},
+				{"C/Godeps/_workspace/src/D/LICENSE", pkg("D"), nil},
+				{"C/Godeps/_workspace/src/D/P/main.go", pkg("P"), nil},
+				{"C/Godeps/_workspace/src/D/P/LICENSE", pkg("P"), nil},
+				{"C/Godeps/_workspace/src/D/Q/main.go", pkg("Q"), nil},
+			},
+			wdep: Godeps{
+				ImportPath: "C",
+				Deps: []Dependency{
+					{ImportPath: "D/P", Comment: "D1"},
+					{ImportPath: "D/Q", Comment: "D1"},
+				},
+			},
+		},
 	}
 
 	wd, err := os.Getwd()
@@ -1083,7 +1121,7 @@ func TestSave(t *testing.T) {
 	}
 	const scratch = "godeptest"
 	defer os.RemoveAll(scratch)
-	for _, test := range cases {
+	for pos, test := range cases {
 		err = os.RemoveAll(scratch)
 		if err != nil {
 			t.Fatal(err)
@@ -1120,7 +1158,7 @@ func TestSave(t *testing.T) {
 			panic(err)
 		}
 
-		checkTree(t, &node{src, "", test.want})
+		checkTree(t, pos, &node{src, "", test.want})
 
 		f, err := os.Open(filepath.Join(dir, "Godeps/Godeps.json"))
 		if err != nil {
@@ -1198,7 +1236,7 @@ func makeTree(t *testing.T, tree *node, altpath string) (gopath string) {
 	return gopath
 }
 
-func checkTree(t *testing.T, want *node) {
+func checkTree(t *testing.T, pos int, want *node) {
 	walkTree(want, want.path, func(path string, n *node) {
 		body := n.body.(string)
 		switch {
@@ -1209,17 +1247,17 @@ func checkTree(t *testing.T, want *node) {
 		case n.entries == nil && body == "(absent)":
 			body, err := ioutil.ReadFile(path)
 			if !os.IsNotExist(err) {
-				t.Errorf("checkTree: %s = %s want absent", path, string(body))
+				t.Errorf("%d checkTree: %s = %s want absent", pos, path, string(body))
 				return
 			}
 		case n.entries == nil:
 			gbody, err := ioutil.ReadFile(path)
 			if err != nil {
-				t.Errorf("checkTree: %v", err)
+				t.Errorf("%d checkTree: %v", pos, err)
 				return
 			}
 			if got := string(gbody); got != body {
-				t.Errorf("%s = got: %q want: %q", path, got, body)
+				t.Errorf("%d %s = got: %q want: %q", pos, path, got, body)
 			}
 		default:
 			os.MkdirAll(path, 0770)
diff --git a/update_test.go b/update_test.go
index 2109e4e..9df665e 100644
--- a/update_test.go
+++ b/update_test.go
@@ -372,7 +372,7 @@ func TestUpdate(t *testing.T) {
 	}
 	const gopath = "godeptest"
 	defer os.RemoveAll(gopath)
-	for _, test := range cases {
+	for pos, test := range cases {
 		err = os.RemoveAll(gopath)
 		if err != nil {
 			t.Fatal(err)
@@ -400,7 +400,7 @@ func TestUpdate(t *testing.T) {
 			panic(err)
 		}
 
-		checkTree(t, &node{src, "", test.want})
+		checkTree(t, pos, &node{src, "", test.want})
 
 		f, err := os.Open(filepath.Join(dir, "Godeps/Godeps.json"))
 		if err != nil {
diff --git a/version.go b/version.go
index def2fe2..00a29e6 100644
--- a/version.go
+++ b/version.go
@@ -1,3 +1,3 @@
 package main
 
-const version = 19
+const version = 20
-- 
GitLab