GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package plugin
6
7import (
8 "bytes"
9 "context"
10 "crypto/rand"
11 "errors"
12 "fmt"
13 "os"
14 "path/filepath"
15 "time"
16 "unicode/utf16"
17
18 "github.com/go-git/go-git/v5/plumbing"
19 "github.com/go-git/go-git/v5/plumbing/object"
20 "github.com/go-git/go-git/v5/plumbing/protocol/packp"
21 "github.com/samber/oops"
22 "github.com/tetratelabs/wazero"
23 "github.com/tetratelabs/wazero/api"
24 "github.com/tetratelabs/wazero/imports/assemblyscript"
25 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
26 "github.com/tetratelabs/wazero/sys"
27 pluginLib "gitroot.dev/libs/golang/plugin/model"
28 grfs "gitroot.dev/server/fs"
29 "gitroot.dev/server/logger"
30 "gitroot.dev/server/repository"
31 "gitroot.dev/server/user"
32)
33
34type runtimeInputsKind int
35
36const (
37 runtimeInputsKindDiff runtimeInputsKind = iota
38 runtimeInputsKindWorktree runtimeInputsKind = iota
39)
40
41type runtimeInputs struct {
42 ctx context.Context
43 repoName string
44 kind runtimeInputsKind
45 plugins []Plugin
46 commands []CommandForDiff
47}
48
49type runtime struct {
50 ctx context.Context
51 manager *Manager
52 logger *logger.Logger
53 repo *repository.GitRootRepository
54 repoWriter *repository.GitRootRepositoryWrite
55 plugin Plugin
56 pluginRun PluginRun
57 command *CommandForDiff
58 wazRun wazero.Runtime
59 fsWorktree grfs.UpdatableFs
60 fsWebcontent grfs.UpdatableFs
61 fsCache grfs.UpdatableFs
62 commitHook func(h plumbing.Hash)
63 mergeHook func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string)
64}
65
66func newRuntime(ctx context.Context, manager *Manager, logger *logger.Logger) (*runtime, error) {
67 r := &runtime{
68 ctx: ctx,
69 manager: manager,
70 logger: logger,
71 wazRun: wazero.NewRuntime(ctx),
72 fsWorktree: grfs.NewUpdatableFs(ctx, nil),
73 fsWebcontent: grfs.NewUpdatableFs(ctx, nil),
74 fsCache: grfs.NewUpdatableFs(ctx, nil),
75 commitHook: nil,
76 mergeHook: nil,
77 }
78
79 _, err := assemblyscript.Instantiate(ctx, r.wazRun)
80 if err != nil {
81 return nil, err
82 }
83
84 _, err = r.wazRun.
85 NewHostModuleBuilder("gitroot").
86 NewFunctionBuilder().WithFunc(r.ForgeConf).Export("forgeConf").
87 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(false)).Export("modifyContent").
88 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(true)).Export("modifyContentAS").
89 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(false)).Export("modifyWebContent").
90 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(true)).Export("modifyWebContentAS").
91 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(false)).Export("replaceWebContent").
92 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(true)).Export("replaceWebContentAS").
93 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(false)).Export("modifyCacheContent").
94 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(true)).Export("modifyCacheContentAS").
95 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(false)).Export("commitAll").
96 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(true)).Export("commitAllAS").
97 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(false)).Export("diffWithParent").
98 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(true)).Export("diffWithParentAS").
99 NewFunctionBuilder().WithFunc(r.LogBuilder(false)).Export("log").
100 NewFunctionBuilder().WithFunc(r.LogBuilder(true)).Export("logAS").
101 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(false)).Export("logError").
102 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(true)).Export("logErrorAS").
103 NewFunctionBuilder().WithFunc(r.MergeBuilder(false)).Export("merge").
104 NewFunctionBuilder().WithFunc(r.MergeBuilder(true)).Export("mergeAS").
105 NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(false)).Export("commits").
106 NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(true)).Export("commitsAS").
107 Instantiate(ctx)
108 if err != nil {
109 return nil, err
110 }
111 _, err = wasi_snapshot_preview1.Instantiate(ctx, r.wazRun)
112 if err != nil {
113 return nil, err
114 }
115 return r, nil
116}
117
118func (r *runtime) listen(c chan runtimeInputs) {
119 for i := range c {
120 r.logger.Debug("listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
121 repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.listen"), i.repoName)
122 if err != nil {
123 r.logger.Error("open error in listen", err)
124 repo.Close()
125 continue
126 }
127 repoWriter, err := repo.WillWrite(plumbing.HEAD) //TODO should mount good branc directly inside i.commands
128 if err != nil {
129 r.logger.Error("will write error in listen", err, logger.NewLoggerPair("repo", repo.Name()))
130 repo.Close()
131 continue
132 }
133
134 if i.kind == runtimeInputsKindDiff {
135 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins", repo.Name()))
136 err := r.start(i.ctx, repo, repoWriter, i.plugins, i.commands, func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string) {
137 if cmd != nil {
138 go func() {
139 time.Sleep(10 * time.Millisecond) //TODO find better to defer it until repo.close() is called (to be sur transactional writer has writen in disk)
140 r.manager.backgroundManager.DeleteBranch(i.repoName, toDeleteBranchName)
141 r.manager.backgroundManager.PostPush(pusher, i.repoName, []*packp.Command{cmd})
142 }()
143 }
144 })
145 if err != nil {
146 r.logger.Error("start error", err)
147 }
148 timerStop()
149 } else if i.kind == runtimeInputsKindWorktree {
150 r.logger.Info("start worktree", logger.NewLoggerPair("repo", repo.Name()))
151 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins worktree", repo.Name()))
152 if err := r.worktree(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil {
153 r.logger.Error("start error", err)
154 }
155 timerStop()
156 }
157 repo.Close()
158 }
159}
160
161func (r *runtime) conf(ctx context.Context, plugin Plugin) ([]pluginLib.PluginRun, error) {
162 r.fsWorktree.Clear()
163 r.fsWebcontent.Clear()
164 r.fsCache.Clear()
165 r.repo = nil
166 r.plugin = plugin
167 r.command = nil
168 timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
169
170 r.logger.Debug("start plugin conf", logger.NewLoggerPair("name", plugin.Name))
171 m := r.wazRun.Module(plugin.uuid())
172 if m == nil {
173 r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name))
174 config := wazero.NewModuleConfig().
175 WithStdout(os.Stdout).WithStderr(os.Stderr).
176 WithSysWalltime().
177 WithSysNanotime().
178 WithRandSource(rand.Reader).
179 WithFSConfig(
180 wazero.NewFSConfig().
181 WithFSMount(r.fsWorktree, "worktree").
182 WithFSMount(r.fsWebcontent, "webcontent").
183 WithFSMount(r.fsCache, "cache"))
184 mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config.WithName(plugin.uuid()).WithStartFunctions("_initialize", "install"))
185 if err != nil {
186 if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
187 fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
188 return nil, err
189 } else if !ok {
190 return nil, err
191 }
192 }
193 m = mod
194 } else {
195 r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name))
196 }
197 conf, err := r.callPluginForConf(ctx, m, r.logger.NewSubLogger(plugin.Name))
198 if err != nil {
199 r.logger.Error("finish plugin conf with error", err, logger.NewLoggerPair("name", plugin.Name))
200 }
201
202 r.logger.Debug("finish plugin conf", logger.NewLoggerPair("name", plugin.Name))
203 timerStop()
204 return conf, err
205}
206
207type runAfter struct {
208 cmd *packp.Command
209 pusher user.SimpleUser
210}
211
212func (r *runtime) start(ctx context.Context, repo *repository.GitRootRepository, repoWriter *repository.GitRootRepositoryWrite, plugins []Plugin, commands []CommandForDiff, mergeHook func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string)) error {
213 r.fsWorktree.Update(repoWriter.ToFs(ctx))
214 r.fsWebcontent.Update(r.manager.conf.DataWeb(repo.Name()))
215
216 r.repo = repo
217 r.repoWriter = repoWriter
218 r.command = nil
219
220 r.commitHook = func(hash plumbing.Hash) {
221 command, err := repoWriter.GetLastCommit(hash)
222 if err != nil {
223 r.logger.Error("can't GetLastCommit in start runtime", err)
224 return
225 }
226 newCmd, err := CommandForDiffFromCommitCmd(ctx, r.plugin.commiter.SimpleUser, command, r.command.branch)
227 if err != nil {
228 r.logger.Error("can't CommandForDiffFromCommitCmd in start runtime", err)
229 return
230 }
231 commands = append(commands, newCmd)
232 }
233
234 r.mergeHook = mergeHook
235 defer func() {
236 r.commitHook = nil
237 r.mergeHook = nil
238 }()
239
240 for _, plugin := range plugins {
241 r.fsCache.Update(r.manager.conf.Cache(repo.Name(), plugin.Name))
242 r.plugin = plugin
243 timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
244 r.logger.Debug("start plugin", logger.NewLoggerPair("repo", repo.Name()), logger.NewLoggerPair("name", plugin.Name))
245 m := r.wazRun.Module(plugin.uuid())
246 if m == nil {
247 r.logger.Debug("instantiate plugin", logger.NewLoggerPair("name", plugin.Name))
248 config := wazero.NewModuleConfig().
249 WithSysWalltime().
250 WithSysNanotime().
251 WithRandSource(rand.Reader).
252 WithStdout(os.Stdout).WithStderr(os.Stderr).
253 WithFSConfig(wazero.NewFSConfig().
254 WithFSMount(r.fsWorktree, "worktree").
255 WithFSMount(r.fsWebcontent, "webcontent").
256 WithFSMount(r.fsCache, "cache"))
257 mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config.WithName(plugin.uuid()).WithStartFunctions("_initialize", "install"))
258 if err != nil {
259 if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
260 fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
261 return nil
262 } else if !ok {
263 return err
264 }
265 }
266 m = mod
267 } else {
268 r.logger.Debug("already exist", logger.NewLoggerPair("name", plugin.Name))
269 }
270 l := logger.NewLogger(logger.WASM)
271 l.Debug("memory before", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
272 cp := callPlugin{
273 manager: r.manager,
274 plugin: plugin,
275 repo: repo,
276 repoWriter: repoWriter,
277 module: m,
278 logger: r.logger.NewSubLogger(plugin.Name),
279 }
280 if err := cp.callPluginForDiff(ctx, r, commands); err != nil {
281 r.logger.Error("finish plugin with error", err, logger.NewLoggerPair("name", plugin.Name))
282 }
283 l.Debug("memory after", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
284 r.logger.Debug("finish plugin", logger.NewLoggerPair("name", plugin.Name))
285 timerStop()
286 }
287 return nil
288}
289
290func (r *runtime) ForgeConf(_ context.Context, m api.Module) uint64 {
291 // TODO need issues/a716-conf-external-addr.md to work
292 _, ptrSize, err := r.sendData(m, "{\"domain\": \"\"}") //TODO free ptr (first arg)
293 if err != nil {
294 r.logger.Error("ForgeConf can't send data", err)
295 return 0
296 }
297 return ptrSize
298}
299
300func (r *runtime) ModifyContentBuilder(forAS bool) interface{} {
301 f := func(filename string, content string) {
302 if ok, err := checkWrite(r.pluginRun.write.git, r.fsWorktree, filename); !ok {
303 r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
304 return
305 }
306 r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename))
307 if err := r.repoWriter.Write(filename, []byte(content)); err != nil {
308 r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename))
309 return
310 }
311 }
312
313 if !forAS {
314 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
315 filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize)
316 if err != nil {
317 return
318 }
319 content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize)
320 if err != nil {
321 return
322 }
323 f(filename, content)
324 }
325 } else {
326 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
327 filename, err := r.readASString(m, "ModifyContent filename", filenamePtr)
328 if err != nil {
329 return
330 }
331 content, err := r.readASString(m, "ModifyContent content", contentPtr)
332 if err != nil {
333 return
334 }
335 f(filename, content)
336 }
337 }
338}
339
340func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} {
341 f := func(filename string, content string) {
342 if ok, err := checkWrite(r.pluginRun.write.web, r.fsWebcontent, filename); !ok {
343 r.logger.Warn("plugin can't write in web", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.repo.Name()), logger.NewLoggerPair("branch", r.command.branch), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
344 return
345 }
346 fullPath := r.repo.PathDataWeb(filename)
347 dir, _ := filepath.Split(fullPath)
348 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
349 r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
350 return
351 }
352 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
353 r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename))
354 return
355 }
356 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
357 }
358
359 if !forAS {
360 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
361 filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize)
362 if err != nil {
363 return
364 }
365 content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize)
366 if err != nil {
367 return
368 }
369 f(filename, content)
370 }
371 } else {
372 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
373 filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr)
374 if err != nil {
375 return
376 }
377 content, err := r.readASString(m, "ModifyWebContent content", contentPtr)
378 if err != nil {
379 return
380 }
381 f(filename, content)
382 }
383 }
384}
385
386func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} {
387 f := func(filename string, oldContent string, content string) {
388 if ok, err := checkWrite(r.pluginRun.write.web, r.fsWebcontent, filename); !ok {
389 r.logger.Warn("plugin can't write in web for replace", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
390 return
391 }
392 fullPath := r.repo.PathDataWeb(filename)
393 dir, _ := filepath.Split(fullPath)
394 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
395 r.logger.Error("ReplaceWebContentBuilder can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
396 return
397 }
398 fileContent, err := os.ReadFile(fullPath)
399 if err != nil {
400 r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
401 return
402 }
403 if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
404 r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename))
405 return
406 }
407 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
408 }
409
410 if !forAS {
411 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) {
412 filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize)
413 if err != nil {
414 return
415 }
416 oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize)
417 if err != nil {
418 return
419 }
420 content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize)
421 if err != nil {
422 return
423 }
424 f(filename, oldContent, content)
425 }
426 } else {
427 return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) {
428 filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr)
429 if err != nil {
430 return
431 }
432 oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr)
433 if err != nil {
434 return
435 }
436 content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr)
437 if err != nil {
438 return
439 }
440 f(filename, oldContent, content)
441 }
442 }
443}
444
445func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} {
446 f := func(filename string, content string) {
447 fullPath := filepath.Join(r.manager.conf.PathCache(), r.repo.Name(), r.plugin.Name, filename)
448 dir, _ := filepath.Split(fullPath)
449 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
450 r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
451 return
452 }
453 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
454 r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
455 return
456 }
457 r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
458 }
459 if !forAS {
460 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
461 filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize)
462 if err != nil {
463 return
464 }
465 content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize)
466 if err != nil {
467 return
468 }
469 f(filename, content)
470 }
471 } else {
472 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
473 filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr)
474 if err != nil {
475 return
476 }
477 content, err := r.readASString(m, "ModifyCacheContent content", contentPtr)
478 if err != nil {
479 return
480 }
481 f(filename, content)
482 }
483 }
484}
485
486func (r *runtime) CommitAllBuilder(forAS bool) interface{} {
487 f := func(msg string, err error) {
488 if err != nil {
489 return
490 }
491 if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil {
492 r.logger.Error("commitAll can't commit", err)
493 return
494 } else {
495 r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String()))
496 if r.commitHook != nil && !h.IsZero() {
497 r.commitHook(h)
498 }
499 }
500 }
501 if !forAS {
502 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
503 f(r.readString(m, "commitAll", offset, byteCount))
504 }
505 } else {
506 return func(_ context.Context, m api.Module, offset uint32) {
507 f(r.readASString(m, "commitAll", offset))
508 }
509 }
510}
511
512func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} {
513 f := func(m api.Module, hash string, oldfile string, newfile string) uint64 {
514 r.logger.Info("In diffWithParent before found ancestor")
515 diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(string(hash)), string(oldfile), string(newfile))
516 if err != nil {
517 r.logger.Error("GetDiff", err)
518 return 0
519 }
520 r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr))
521 _, ptrSize, err := r.sendData(m, diffStr) //TODO free ptr (first arg)
522 if err != nil {
523 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr))
524 return 0
525 }
526 return ptrSize
527 }
528 if !forAS {
529 return func(_ context.Context, m api.Module, hashPtr, hashSize, oldFilenamePtr, oldFilenameSize, newFilenamePtr, newFilenameSize uint32) uint64 {
530 hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize)
531 if err != nil {
532 return 0
533 }
534 oldFilename, err := r.readString(m, "DiffWithParent oldFilename", oldFilenamePtr, oldFilenameSize)
535 if err != nil {
536 return 0
537 }
538 newFilename, err := r.readString(m, "DiffWithParent newFilename", newFilenamePtr, newFilenameSize)
539 if err != nil {
540 return 0
541 }
542 return f(m, hash, oldFilename, newFilename)
543 }
544 } else {
545 return func(_ context.Context, m api.Module, hashPtr, oldFilenamePtr, newFilenamePtr uint32) uint64 {
546 hash, err := r.readASString(m, "DiffWithParent hash", hashPtr)
547 if err != nil {
548 return 0
549 }
550 oldFilename, err := r.readASString(m, "DiffWithParent oldFilenamePtr", oldFilenamePtr)
551 if err != nil {
552 return 0
553 }
554 newFilename, err := r.readASString(m, "DiffWithParent newFilename", newFilenamePtr)
555 if err != nil {
556 return 0
557 }
558 return f(m, hash, oldFilename, newFilename)
559 }
560 }
561}
562
563func (r *runtime) LogBuilder(forAS bool) interface{} {
564 f := func(msg string, err error) {
565 if err != nil {
566 return
567 }
568 r.logger.Debug(msg, logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name))
569 }
570 if !forAS {
571 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
572 f(r.readString(m, "log", offset, byteCount))
573 }
574 } else {
575 return func(_ context.Context, m api.Module, offset uint32) {
576 f(r.readASString(m, "log", offset))
577 }
578 }
579}
580
581func (r *runtime) LogErrorBuilder(forAS bool) interface{} {
582 f := func(msg string, err string) {
583 r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name))
584 }
585 if !forAS {
586 return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) {
587 msg, err := r.readString(m, "LogError msg", offset, byteCount)
588 if err != nil {
589 return
590 }
591 errMsg, err := r.readString(m, "LogError err", errPtr, errSize)
592 if err != nil {
593 return
594 }
595 f(msg, errMsg)
596 }
597 } else {
598 return func(_ context.Context, m api.Module, offset, errPtr uint32) {
599 msg, err := r.readASString(m, "LogError msg", offset)
600 if err != nil {
601 return
602 }
603 errMsg, err := r.readASString(m, "LogError err", errPtr)
604 if err != nil {
605 return
606 }
607 f(msg, errMsg)
608 }
609 }
610}
611
612func (r *runtime) MergeBuilder(forAS bool) interface{} {
613 f := func(from string, to string) {
614 r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
615 cmd, err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher)
616 if err != nil {
617 r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
618 }
619 if r.mergeHook != nil {
620 r.mergeHook(cmd, r.command.pusher, to)
621 }
622 }
623 if !forAS {
624 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) {
625 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
626 if err != nil {
627 return
628 }
629 to, err := r.readString(m, "Merge to", toPtr, toSize)
630 if err != nil {
631 return
632 }
633 f(from, to)
634 }
635 } else {
636 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) {
637 from, err := r.readASString(m, "Merge from", fromPtr)
638 if err != nil {
639 return
640 }
641 to, err := r.readASString(m, "Merge to", toPtr)
642 if err != nil {
643 return
644 }
645 f(from, to)
646 }
647 }
648}
649
650func (r *runtime) GetCommitsBuilder(forAS bool) interface{} {
651 f := func(m api.Module, from string, to string) uint64 {
652 commits := []commitForDiffCommit{}
653 if err := r.repoWriter.WalkCommit(plumbing.NewHash(to), plumbing.NewHash(from), func(c *object.Commit) error {
654 r.logger.Debug("found commit", logger.NewLoggerPair("hash", c.Hash.String()), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
655 commits = append(commits, commitToCommitForDiff(c, nil))
656 return nil
657 }); err != nil {
658 r.logger.Error("can't WalkCommit", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
659 }
660 r.logger.Debug("found commits", logger.NewLoggerPair("nb", len(commits)), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
661 j, err := Marshall("//TODO", commits)
662 if err != nil {
663 r.logger.Error("can't Marshall", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
664 }
665 _, ptrSize, err := r.sendData(m, j) //TODO free ptr (first arg)
666 if err != nil {
667 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
668 return 0
669 }
670 return ptrSize
671 }
672 if !forAS {
673 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) uint64 {
674 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
675 if err != nil {
676 return 0
677 }
678 to, err := r.readString(m, "Merge to", toPtr, toSize)
679 if err != nil {
680 return 0
681 }
682 return f(m, from, to)
683 }
684 } else {
685 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) uint64 {
686 from, err := r.readASString(m, "Merge from", fromPtr)
687 if err != nil {
688 return 0
689 }
690 to, err := r.readASString(m, "Merge to", toPtr)
691 if err != nil {
692 return 0
693 }
694 return f(m, from, to)
695 }
696 }
697}
698
699func (r *runtime) Close() error {
700 return r.wazRun.Close(r.ctx)
701}
702
703func (r *runtime) sendData(module api.Module, message string) (ptr uint32, ptrSize uint64, err error) {
704 malloc := module.ExportedFunction("malloc")
705
706 s := uint64(len(message))
707
708 results, err := malloc.Call(r.ctx, s)
709 if err != nil {
710 return 0, 0, oops.Wrapf(err, "can't malloc memory")
711 }
712 ptrA := results[0]
713
714 // The pointer is a linear memory offset, which is where we write the name.
715 if !module.Memory().WriteString(uint32(ptrA), message) {
716 return 0, 0, oops.Errorf("can't write memory")
717 }
718
719 return uint32(ptrA), (ptrA << uint64(32)) | s, nil
720}
721
722func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) {
723 txt, ok := m.Memory().Read(ptr, size)
724 if !ok {
725 err := errors.New("Memory.Read fail")
726 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
727 return "", err
728 }
729 return string(txt), nil
730}
731
732func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) {
733 // Length is four bytes before pointer.
734 byteCount, ok := m.Memory().ReadUint32Le(offset - 4)
735 if !ok || byteCount%2 != 0 {
736 err := errors.New("Memory.ReadUint32Le fail")
737 r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset))
738 return "", err
739 }
740 buf, ok := m.Memory().Read(offset, byteCount)
741 if !ok {
742 err := errors.New("Memory.Read fail")
743 r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount))
744 return "", err
745 }
746 u16s := make([]uint16, len(buf)/2)
747
748 lb := len(buf)
749 for i := 0; i < lb; i += 2 {
750 u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8)
751 }
752 return string(utf16.Decode(u16s)), nil
753}