11package plumbing
22
33import (
4+ "fmt"
5+ "gopkg.in/src-d/enry.v1"
46 "io"
57 "log"
68 "strings"
@@ -18,8 +20,11 @@ import (
1820type TreeDiff struct {
1921 core.NoopMerger
2022 SkipDirs []string
23+ Languages map [string ]bool
24+
2125 previousTree * object.Tree
2226 previousCommit plumbing.Hash
27+ repository * git.Repository
2328}
2429
2530const (
@@ -31,6 +36,13 @@ const (
3136 // ConfigTreeDiffBlacklistedDirs s the name of the configuration option
3237 // (TreeDiff.Configure()) which allows to set blacklisted directories.
3338 ConfigTreeDiffBlacklistedDirs = "TreeDiff.BlacklistedDirs"
39+ // ConfigTreeDiffLanguages is the name of the configuration option (TreeDiff.Configure())
40+ // which sets the list of programming languages to analyze. Language names are at
41+ // https://doc.bblf.sh/languages.html Names are joined with a comma ",".
42+ // "all" is the special name which disables this filter.
43+ ConfigTreeDiffLanguages = "TreeDiff.Languages"
44+ // allLanguages denotes passing all files in.
45+ allLanguages = "all"
3446)
3547
3648var defaultBlacklistedDirs = []string {"vendor/" , "vendors/" , "node_modules/" }
@@ -67,7 +79,15 @@ func (treediff *TreeDiff) ListConfigurationOptions() []core.ConfigurationOption
6779 Description : "List of blacklisted directories. Separated by comma \" ,\" ." ,
6880 Flag : "blacklisted-dirs" ,
6981 Type : core .StringsConfigurationOption ,
70- Default : defaultBlacklistedDirs },
82+ Default : defaultBlacklistedDirs }, {
83+ Name : ConfigTreeDiffLanguages ,
84+ Description : fmt .Sprintf (
85+ "List of programming languages to analyze. Separated by comma \" ,\" . " +
86+ "Names are at https://doc.bblf.sh/languages.html \" %s\" is the special name " +
87+ "which disables this filter and lets all the files through." , allLanguages ),
88+ Flag : "languages" ,
89+ Type : core .StringsConfigurationOption ,
90+ Default : []string {allLanguages }},
7191 }
7292 return options [:]
7393}
@@ -77,12 +97,26 @@ func (treediff *TreeDiff) Configure(facts map[string]interface{}) {
7797 if val , exist := facts [ConfigTreeDiffEnableBlacklist ]; exist && val .(bool ) {
7898 treediff .SkipDirs = facts [ConfigTreeDiffBlacklistedDirs ].([]string )
7999 }
100+ if val , exists := facts [ConfigTreeDiffLanguages ].(string ); exists {
101+ treediff .Languages = map [string ]bool {}
102+ for _ , lang := range strings .Split (val , "," ) {
103+ treediff .Languages [strings .TrimSpace (lang )] = true
104+ }
105+ } else if treediff .Languages == nil {
106+ treediff .Languages = map [string ]bool {}
107+ treediff .Languages [allLanguages ] = true
108+ }
80109}
81110
82111// Initialize resets the temporary caches and prepares this PipelineItem for a series of Consume()
83112// calls. The repository which is going to be analysed is supplied as an argument.
84113func (treediff * TreeDiff ) Initialize (repository * git.Repository ) {
85114 treediff .previousTree = nil
115+ treediff .repository = repository
116+ if treediff .Languages == nil {
117+ treediff .Languages = map [string ]bool {}
118+ treediff .Languages [allLanguages ] = true
119+ }
86120}
87121
88122// Consume runs this PipelineItem on the next commit data.
@@ -124,6 +158,13 @@ func (treediff *TreeDiff) Consume(deps map[string]interface{}) (map[string]inter
124158 }
125159 return err
126160 }
161+ pass , err := treediff .checkLanguage (file .Name , file .Hash )
162+ if err != nil {
163+ return err
164+ }
165+ if ! pass {
166+ continue
167+ }
127168 diff = append (diff , & object.Change {
128169 To : object.ChangeEntry {Name : file .Name , Tree : tree , TreeEntry : object.TreeEntry {
129170 Name : file .Name , Mode : file .Mode , Hash : file .Hash }}})
@@ -137,21 +178,29 @@ func (treediff *TreeDiff) Consume(deps map[string]interface{}) (map[string]inter
137178 treediff .previousTree = tree
138179 treediff .previousCommit = commit .Hash
139180
140- if len (treediff .SkipDirs ) > 0 {
141- // filter without allocation
142- filteredDiff := make ([]* object.Change , 0 , len (diff ))
143- OUTER:
144- for _ , change := range diff {
145- for _ , dir := range treediff .SkipDirs {
146- if strings .HasPrefix (change .To .Name , dir ) || strings .HasPrefix (change .From .Name , dir ) {
147- continue OUTER
148- }
181+ // filter without allocation
182+ filteredDiff := make ([]* object.Change , 0 , len (diff ))
183+ OUTER:
184+ for _ , change := range diff {
185+ for _ , dir := range treediff .SkipDirs {
186+ if strings .HasPrefix (change .To .Name , dir ) || strings .HasPrefix (change .From .Name , dir ) {
187+ continue OUTER
149188 }
150- filteredDiff = append (filteredDiff , change )
151189 }
152-
153- diff = filteredDiff
190+ var changeEntry object.ChangeEntry
191+ if change .To .Tree == nil {
192+ changeEntry = change .From
193+ } else {
194+ changeEntry = change .To
195+ }
196+ pass , _ := treediff .checkLanguage (changeEntry .Name , changeEntry .TreeEntry .Hash )
197+ if ! pass {
198+ continue
199+ }
200+ filteredDiff = append (filteredDiff , change )
154201 }
202+
203+ diff = filteredDiff
155204 return map [string ]interface {}{DependencyTreeChanges : diff }, nil
156205}
157206
@@ -160,6 +209,28 @@ func (treediff *TreeDiff) Fork(n int) []core.PipelineItem {
160209 return core .ForkCopyPipelineItem (treediff , n )
161210}
162211
212+ // checkLanguage returns whether the blob corresponds to the list of required languages.
213+ func (treediff * TreeDiff ) checkLanguage (name string , blobHash plumbing.Hash ) (bool , error ) {
214+ if treediff .Languages [allLanguages ] {
215+ return true , nil
216+ }
217+ blob , err := treediff .repository .BlobObject (blobHash )
218+ if err != nil {
219+ return false , err
220+ }
221+ reader , err := blob .Reader ()
222+ if err != nil {
223+ return false , err
224+ }
225+ buffer := make ([]byte , 1024 )
226+ _ , err = reader .Read (buffer )
227+ if err != nil {
228+ return false , err
229+ }
230+ lang := enry .GetLanguage (name , buffer )
231+ return treediff .Languages [lang ], nil
232+ }
233+
163234func init () {
164235 core .Registry .Register (& TreeDiff {})
165236}
0 commit comments