@@ -3,9 +3,11 @@ package action
33import (
44 "bytes"
55 "fmt"
6+ "io"
67 "log/slog"
78 "os"
89 "path/filepath"
10+ "strings"
911
1012 "github.com/project-chip/alchemy/asciidoc"
1113 "github.com/project-chip/alchemy/cmd/action/github"
@@ -18,6 +20,14 @@ import (
1820 "github.com/sethvargo/go-githubactions"
1921)
2022
23+ type EnforcementLevel string
24+
25+ const (
26+ // EnforcementMandatory applies to new files with suggestions or existing discoballed files with suggestions.
27+ EnforcementMandatory EnforcementLevel = "discoball-mandatory"
28+ EnforcementOptional EnforcementLevel = "discoball-optional"
29+ )
30+
2131type Disco struct {
2232}
2333
@@ -40,8 +50,8 @@ func (c *Disco) Run(cc *cli.Context) (err error) {
4050 if pr == nil {
4151 return nil
4252 }
43- var changedFiles [] string
44- changedFiles , err = github .GetPRChangedFiles (cc , githubContext , action , pr )
53+ var changedFiles map [ string ]github. FileStatus
54+ changedFiles , err = github .GetPRChangedFilesWithStatus (cc , githubContext , action , pr )
4555 if err != nil {
4656 return fmt .Errorf ("failed on getting pull request changes: %w" , err )
4757 }
@@ -52,9 +62,26 @@ func (c *Disco) Run(cc *cli.Context) (err error) {
5262 }
5363
5464 var changedDocs []string
55- for _ , path := range changedFiles {
65+ fileEnforcementLevel := make (map [string ]EnforcementLevel )
66+ for path , status := range changedFiles {
5667 if filepath .Ext (path ) == ".adoc" {
5768 changedDocs = append (changedDocs , path )
69+ if status == github .FileStatusAdded {
70+ fileEnforcementLevel [path ] = EnforcementMandatory
71+ continue
72+ }
73+ fullPath := filepath .Join (githubContext .Workspace , path )
74+ b , err := os .ReadFile (fullPath )
75+ if err != nil {
76+ slog .Warn ("failed to read original file to check for discoballed marker" , "path" , fullPath , "error" , err )
77+ fileEnforcementLevel [path ] = EnforcementOptional
78+ continue
79+ }
80+ if strings .Contains (string (b ), ":alchemy-discoballed:" ) {
81+ fileEnforcementLevel [path ] = EnforcementMandatory
82+ } else {
83+ fileEnforcementLevel [path ] = EnforcementOptional
84+ }
5885 }
5986 }
6087
@@ -66,8 +93,15 @@ func (c *Disco) Run(cc *cli.Context) (err error) {
6693
6794 pipelineOptions := pipeline.ProcessingOptions {NoProgress : true }
6895
69- var out bytes.Buffer
70- writer := files .NewPatcher [string ]("Generating patch file..." , & out )
96+ var outMandatory bytes.Buffer
97+ var outOptional bytes.Buffer
98+ writer := files .NewPatcher [string ]("Generating patch file..." , & outMandatory )
99+ writer .GetWriter = func (path string ) io.Writer {
100+ if fileEnforcementLevel [path ] == EnforcementMandatory {
101+ return & outMandatory
102+ }
103+ return & outOptional
104+ }
71105 writer .Root = githubContext .Workspace
72106
73107 parserOptions := spec.ParserOptions {
@@ -90,17 +124,54 @@ func (c *Disco) Run(cc *cli.Context) (err error) {
90124 return fmt .Errorf ("failed disco-balling: %s" , message )
91125 }
92126
93- if out .Len () > 0 {
94- slog .Info ("Setting disco_status to patched" )
95- action .SetOutput ("disco_status" , "patched" )
127+ hasMandatory := outMandatory .Len () > 0
128+ hasOptional := outOptional .Len () > 0
129+
130+ if hasMandatory {
131+ action .SetOutput ("has_violations" , "true" )
132+ var violations []string
133+ for _ , path := range writer .ModifiedFiles {
134+ if fileEnforcementLevel [path ] == EnforcementMandatory {
135+ status := changedFiles [path ]
136+ var msg string
137+ if status == github .FileStatusAdded {
138+ msg = fmt .Sprintf ("a new file is added to PR and it has alchemy discoball suggestions: %s" , path )
139+ } else {
140+ msg = fmt .Sprintf ("a file that already had :alchemy-discoballed: has fixes suggested: %s" , path )
141+ }
142+ violations = append (violations , msg )
143+ }
144+ }
145+ for _ , v := range violations {
146+ action .Errorf ("%s" , v )
147+ }
148+ action .SetOutput ("violation_reason" , fmt .Sprintf ("Found %d files with violations. See logs for details." , len (violations )))
149+ } else {
150+ action .SetOutput ("has_violations" , "false" )
151+ }
96152
97- err = os .WriteFile ("disco.patch" , out .Bytes (), os .ModeAppend | 0644 )
153+ if hasMandatory {
154+ slog .Info ("Setting mandatory patch outputs" )
155+ err = os .WriteFile ("disco-mandatory.patch" , outMandatory .Bytes (), os .ModeAppend | 0644 )
98156 if err != nil {
99- return fmt .Errorf ("failed saving patch: %v" , err )
157+ return fmt .Errorf ("failed saving mandatory patch: %v" , err )
100158 }
101- action .SetOutput ("patch_name" , "disco_patch" )
102- action .SetOutput ("patch_path" , "disco.patch" )
103- action .SetOutput ("template_name" , "disco/patched" )
159+ action .SetOutput ("mandatory_patch_name" , "disco_mandatory_patch" )
160+ action .SetOutput ("mandatory_patch_path" , "disco-mandatory.patch" )
161+ }
162+
163+ if hasOptional {
164+ slog .Info ("Setting optional patch outputs" )
165+ err = os .WriteFile ("disco-optional.patch" , outOptional .Bytes (), os .ModeAppend | 0644 )
166+ if err != nil {
167+ return fmt .Errorf ("failed saving optional patch: %v" , err )
168+ }
169+ action .SetOutput ("optional_patch_name" , "disco_optional_patch" )
170+ action .SetOutput ("optional_patch_path" , "disco-optional.patch" )
171+ }
172+
173+ if hasMandatory || hasOptional {
174+ action .SetOutput ("template_name" , "disco/combined" )
104175 } else {
105176 slog .Info ("Setting disco_status to unpatched" )
106177 action .SetOutput ("disco_status" , "unpatched" )
0 commit comments