-
Notifications
You must be signed in to change notification settings - Fork 0
/
check_consistency.go
128 lines (115 loc) · 3.97 KB
/
check_consistency.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// CheckConsistency verifies that the following conditions are satisfied:
//
// - all "repositoryRoot" directories listed in vendor.json exist in _vendor/;
// - all the directories in _vendor/ belong to some "repositoryRoot";
// - there are no stray files outside of repository roots in _vendor/ (other
// than _vendor/.gitignore).
//
// It doesn't check contents (files & dirs) of the repository roots - this is
// responsibility of func CheckPatched().
// (use-cases.md 6.1.2.1)
func CheckConsistency() error {
// NOTE: this function operates strictly on files in git's "staging area" (index).
// ANY MODIFICATIONS MUST KEEP THIS INVARIANT.
// TODO(mateuszc): check that all pkgs sharing same "repositoryRoot" have same "revision" & "revisionTime"
// Make sure we're in project's root dir (with .git/, vendor.json, and _vendor/)
exist := Exist{}.Dir(".git").File(JsonPath).Dir(VendorPath)
if exist.Err != nil {
return exist.Err
}
// Parse *vendor.json*, sort by pkg path.
// (use-cases.md 6.1.2.1.2)
pkgs, err := ReadStagedVendorFile(JsonPath)
if err != nil {
return err
}
if pkgs == nil {
return fmt.Errorf("file not found: %s", JsonPath)
}
// Build a tree from RepositoryRoots
repoRoots := Tree{}
unvisitedRoots := set{}
for _, p := range pkgs.Packages {
unvisitedRoots.Add(p.RepositoryRoot)
err := repoRoots.Put(p.RepositoryRoot)
if err != nil {
return err
}
}
// We want to check that all RepositoryRoots from vendor.json (from index) are in git (index), and that there are no files in _vendor/
// out of RepositoryRoots (except _vendor/.gitignore).
// Note: we're not interested in files under RepositoryRoots (they will be checked by CheckPatched()).
// (use-cases.md 6.1.2.1.3)
// NOTE(mateuszc): we walk only dirs known to git; there may be untracked dirs, we're not interested in them
err = git{}.WalkStaged(".", VendorPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == GitignorePath {
return nil
}
expected := repoRoots.Get(path)
if expected == nil {
return fmt.Errorf("unexpected file/directory in git, but not in %s: %s", JsonPath, path)
}
if !info.IsDir() {
return fmt.Errorf("unexpected file in git, not in any %s repositoryRoot: %s", JsonPath, path)
}
if expected.IsEmpty() { // repository root
// FIXME(mateuszc): if has *.git/.hg/.bzr* subdir, then verify revision-id match with *vendor.json*; if failed, report **error**
// (see *vendo-check-patched* for error message details); (use-cases.md 6.1.2.1.3.3)
delete(unvisitedRoots, path)
return filepath.SkipDir
} else {
return nil
}
})
if err != nil {
return err
}
// if any pkg in *vendor.json* is not visited, then report **error**;
// (use-cases.md 6.1.2.1.4)
if len(unvisitedRoots) > 0 {
return fmt.Errorf("following %s repositoryRoots not found in git: %s", JsonPath, strings.Join(unvisitedRoots.ToSlice(), " "))
}
// TODO(mateuszc): check that any *.git/.hg/.bzr* subdirs, if present, are at locations noted in $PKG_REPO_ROOT fields;
return nil
}
type Tree map[string]Tree
func (t Tree) Put(path string) error {
// FIXME(mateuszc): also add checks for path cleanness, slash-ness, absolute paths or not in _vendor/src
if path == "" {
// TODO(mateuszc): make the error msg here more generic, add vendor-related context in caller
return fmt.Errorf(`empty "repositoryRoot" for import %s in %s`, path, JsonPath)
}
path = filepath.ToSlash(path)
segments := strings.Split(path, "/")
subtree := t
for _, seg := range segments {
if subtree[seg] == nil {
subtree[seg] = Tree{}
}
subtree = subtree[seg]
}
return nil
}
func (t Tree) Get(path string) Tree {
// FIXME(mateuszc): handling of path=="" (and maybe "." too?)
segments := strings.Split(path, "/")
subtree := t
for _, seg := range segments {
subtree = subtree[seg]
if subtree == nil {
return nil
}
}
return subtree
}
func (t Tree) IsEmpty() bool { return len(t) == 0 }