@@ -10,22 +10,38 @@ import (
1010 "log"
1111 "net/http"
1212 "os"
13+ "regexp"
1314 "strings"
15+ "time"
1416
1517 "github.com/bradleyfalzon/ghinstallation"
1618 "github.com/google/go-github/github"
1719)
1820
21+ type Config struct {
22+ Src string `json:"src"`
23+ Dest string `json:"dest"`
24+ Branches []string `json:"branches"`
25+ }
26+ type Branch struct {
27+ Owner string
28+ Repo string
29+ Branch string
30+ Base string
31+ }
32+
1933func main () {
2034 privateKey := []byte (os .Getenv ("PRIVATE_KEY" ))
2135 var appID , installationID int64
2236 var files , message string
2337 var dryRun bool
38+ var autoMerge bool
2439 flag .Int64Var (& appID , "app_id" , 0 , "github app id" )
2540 flag .Int64Var (& installationID , "installation_id" , 0 , "github installation id" )
2641 flag .StringVar (& message , "message" , "chore: Sync by .github" , "commit message" )
2742 flag .StringVar (& files , "files" , "" , "config files, separated by spaces" )
2843 flag .BoolVar (& dryRun , "dryRun" , false , "dry run" )
44+ flag .BoolVar (& autoMerge , "autoMerge" , true , "auto merge" )
2945 flag .Parse ()
3046 if appID == 0 || installationID == 0 || len (message ) == 0 || len (files ) == 0 {
3147 flag .PrintDefaults ()
@@ -45,88 +61,163 @@ func main() {
4561 if err != nil {
4662 log .Fatal (err )
4763 }
48- var configs []struct {
49- Src string `json:"src"`
50- Dest string `json:"dest"`
51- Branches []string `json:"branches"`
52- }
64+ var configs []Config
5365 err = json .Unmarshal (data , & configs )
5466 if err != nil {
5567 log .Fatal (err )
5668 }
5769 log .Println ("Config" , file )
70+
71+ mergeBranch := map [string ]Branch {}
72+ cleanupBranch := map [string ]Branch {}
5873 for _ , config := range configs {
59- arr := strings . SplitN (config .Dest , "/" , 3 )
60- if len ( arr ) != 3 {
61- log .Fatal ("wrong dist. example: username/repo/file" )
74+ owner , repo , path , err := split (config .Dest )
75+ if err != nil {
76+ log .Fatal (err )
6277 }
63- owner := arr [0 ]
64- repo := arr [1 ]
65- path := arr [2 ]
6678 log .Printf ("\t Sync %s to %s/%s/%s" , config .Src , owner , repo , path )
6779 if dryRun {
6880 continue
6981 }
70- err = sendFile (ctx , client , config .Src , owner , repo , path , message , config .Branches )
82+
83+ var syncBranches []string
84+ branches , _ , err := client .Repositories .ListBranches (ctx , owner , repo , nil )
7185 if err != nil {
7286 log .Fatal (err )
7387 }
88+ // match branch
89+ if len (config .Branches ) == 0 {
90+ for i := range branches {
91+ syncBranches = append (syncBranches , * branches [i ].Name )
92+ }
93+ } else {
94+ for i := range config .Branches {
95+ reg := regexp .MustCompile (config .Branches [i ])
96+ match := false
97+ for j := range branches {
98+ if reg .Match ([]byte (* branches [j ].Name )) {
99+ match = true
100+ break
101+ }
102+ }
103+ if match {
104+ syncBranches = append (syncBranches , * branches [i ].Name )
105+ }
106+ }
107+ }
108+ for i := range syncBranches {
109+ key := fmt .Sprintf ("%s/%s/%s" , owner , repo , syncBranches [i ])
110+ var branch Branch
111+ branch , ok := cleanupBranch [key ]
112+ if ! ok {
113+ tempBranch := fmt .Sprintf ("sync/t_%d/%s" , time .Now ().Unix (), syncBranches [i ])
114+ tempRef := fmt .Sprintf ("refs/heads/%s" , tempBranch )
115+ ref , _ , err := client .Git .GetRef (ctx , owner , repo , fmt .Sprintf ("heads/%s" , syncBranches [i ]))
116+ if err != nil {
117+ log .Fatal (err )
118+ }
119+ ref .Ref = github .String (tempRef )
120+ _ , _ , err = client .Git .CreateRef (ctx , owner , repo , ref )
121+ if err != nil {
122+ log .Fatal (err )
123+ }
124+ branch = Branch {Owner : owner , Repo : repo , Base : syncBranches [i ], Branch : tempBranch }
125+ cleanupBranch [key ] = branch
126+ }
127+
128+ changed , err := sendFile (ctx , client , config .Src , owner , repo , path , message , branch .Branch )
129+ if err != nil {
130+ log .Fatal (err )
131+ }
132+ if changed {
133+ log .Printf ("\t \t Branch Sync: %s TempBranch: %s\n " , branch .Base , branch .Branch )
134+ mergeBranch [key ] = branch
135+ } else {
136+ log .Printf ("\t \t Branch No Change: %s TempBranch: %s\n " , branch .Base , branch .Branch )
137+ }
138+ }
139+ }
140+
141+ for _ , branch := range mergeBranch {
142+ pr , _ , err := client .PullRequests .Create (ctx , branch .Owner , branch .Repo , & github.NewPullRequest {
143+ Title : & message ,
144+ Head : github .String (branch2Ref (branch .Branch )),
145+ Base : github .String (branch2Ref (branch .Base )),
146+ MaintainerCanModify : github .Bool (true ),
147+ })
148+ if err != nil {
149+ log .Println ("create pull request: %w" , err )
150+ continue
151+ }
152+ if autoMerge {
153+ _ , _ , err = client .PullRequests .Merge (ctx ,
154+ branch .Owner , branch .Repo ,
155+ pr .GetNumber (), message ,
156+ & github.PullRequestOptions {SHA : pr .GetHead ().GetSHA (), MergeMethod : "squash" },
157+ )
158+ if err != nil {
159+ log .Println ("merge pull request: %w" , err )
160+ continue
161+ }
162+ }
163+ }
164+ if autoMerge {
165+ for _ , branch := range cleanupBranch {
166+ _ , err := client .Git .DeleteRef (ctx , branch .Owner , branch .Repo , branch2Ref (branch .Branch ))
167+ if err != nil {
168+ log .Println ("delete ref faild: %w" , err )
169+ }
170+ }
74171 }
75172 }
173+ }
76174
175+ func branch2Ref (branch string ) string {
176+ return fmt .Sprintf ("refs/heads/%s" , branch )
77177}
78178
79- func sendFile (ctx context.Context , client * github.Client , localFile string , owner , repo , path , message string , syncBranches []string ) error {
80- if syncBranches == nil {
179+ func split (dest string ) (owner , repo , path string , err error ) {
180+ arr := strings .SplitN (dest , "/" , 3 )
181+ if len (arr ) != 3 {
182+ return "" , "" , "" , fmt .Errorf ("wrong dist. example: owner/repo/file" )
183+ }
184+ return arr [0 ], arr [1 ], arr [2 ], nil
185+ }
81186
82- branches , _ , err := client .Repositories .ListBranches (ctx , owner , repo , nil )
83- if err != nil {
84- return err
85- }
86- for i := range branches {
87- syncBranches = append (syncBranches , * branches [i ].Name )
187+ func sendFile (ctx context.Context , client * github.Client , localFile string , owner , repo , path , message string , branch string ) (_changed bool , _err error ) {
188+ fileContent , _ , resp , err := client .Repositories .GetContents (
189+ ctx , owner , repo , path ,
190+ & github.RepositoryContentGetOptions {Ref : branch },
191+ )
192+ if err != nil {
193+ if resp .StatusCode != http .StatusNotFound {
194+ return false , fmt .Errorf ("get content: %w" , err )
88195 }
89196 }
90- for _ , branche := range syncBranches {
91- fileContent , _ , resp , err := client .Repositories .GetContents (
92- ctx , owner , repo , path ,
93- & github.RepositoryContentGetOptions {Ref : branche },
94- )
95- if err != nil {
96- if resp .StatusCode != http .StatusNotFound {
97- panic (err )
98- }
99- }
100- var latestSha string
101- if fileContent != nil {
102- latestSha = fileContent .GetSHA ()
103- }
104- content , err := os .ReadFile (localFile )
105- if err != nil {
106- panic (err )
107- }
108- sha := sha1 .New ()
109- sha .Write ([]byte (fmt .Sprintf ("blob %d" , len (content ))))
110- sha .Write ([]byte {0 })
111- sha .Write (content )
112- currentSha := hex .EncodeToString (sha .Sum (nil ))
113- if string (latestSha ) == currentSha {
114- log .Println ("\t \t Branche" , branche , " no change" )
115- continue
116- }
117- log .Println ("\t \t Branche" , branche , owner , repo , path , message )
118- _ , _ , err = client .Repositories .UpdateFile (
119- ctx , owner , repo , path ,
120- & github.RepositoryContentFileOptions {
121- Message : & message ,
122- Content : content ,
123- SHA : & latestSha ,
124- Branch : & branche ,
125- },
126- )
127- if err != nil {
128- return err
129- }
197+ var latestSha string
198+ if fileContent != nil {
199+ latestSha = fileContent .GetSHA ()
200+ }
201+ content , err := os .ReadFile (localFile )
202+ if err != nil {
203+ return false , fmt .Errorf ("read file: %w" , err )
204+ }
205+ sha := sha1 .New ()
206+ sha .Write ([]byte (fmt .Sprintf ("blob %d" , len (content ))))
207+ sha .Write ([]byte {0 })
208+ sha .Write (content )
209+ currentSha := hex .EncodeToString (sha .Sum (nil ))
210+ if string (latestSha ) == currentSha {
211+ return false , nil
130212 }
131- return nil
213+ _ , _ , err = client .Repositories .UpdateFile (
214+ ctx , owner , repo , path ,
215+ & github.RepositoryContentFileOptions {
216+ Message : & message ,
217+ Content : content ,
218+ SHA : & latestSha ,
219+ Branch : & branch ,
220+ },
221+ )
222+ return true , nil
132223}
0 commit comments