gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package attribute
  4. import (
  5. "bytes"
  6. "context"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "time"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/git/gitcmd"
  13. "code.gitea.io/gitea/modules/log"
  14. )
  15. // BatchChecker provides a reader for check-attribute content that can be long running
  16. type BatchChecker struct {
  17. attributesNum int
  18. repo *git.Repository
  19. stdinWriter *os.File
  20. stdOut *nulSeparatedAttributeWriter
  21. ctx context.Context
  22. cancel context.CancelFunc
  23. cmd *gitcmd.Command
  24. }
  25. // NewBatchChecker creates a check attribute reader for the current repository and provided commit ID
  26. // If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo
  27. func NewBatchChecker(repo *git.Repository, treeish string, attributes []string) (checker *BatchChecker, returnedErr error) {
  28. ctx, cancel := context.WithCancel(repo.Ctx)
  29. defer func() {
  30. if returnedErr != nil {
  31. cancel()
  32. }
  33. }()
  34. cmd, envs, cleanup, err := checkAttrCommand(repo, treeish, nil, attributes)
  35. if err != nil {
  36. return nil, err
  37. }
  38. defer func() {
  39. if returnedErr != nil {
  40. cleanup()
  41. }
  42. }()
  43. cmd.AddArguments("--stdin")
  44. checker = &BatchChecker{
  45. attributesNum: len(attributes),
  46. repo: repo,
  47. ctx: ctx,
  48. cmd: cmd,
  49. cancel: func() {
  50. cancel()
  51. cleanup()
  52. },
  53. }
  54. stdinReader, stdinWriter, err := os.Pipe()
  55. if err != nil {
  56. return nil, err
  57. }
  58. checker.stdinWriter = stdinWriter
  59. lw := new(nulSeparatedAttributeWriter)
  60. lw.attributes = make(chan attributeTriple, len(attributes))
  61. lw.closed = make(chan struct{})
  62. checker.stdOut = lw
  63. go func() {
  64. defer func() {
  65. _ = stdinReader.Close()
  66. _ = lw.Close()
  67. }()
  68. stdErr := new(bytes.Buffer)
  69. err := cmd.Run(ctx, &gitcmd.RunOpts{
  70. Env: envs,
  71. Dir: repo.Path,
  72. Stdin: stdinReader,
  73. Stdout: lw,
  74. Stderr: stdErr,
  75. })
  76. if err != nil && !git.IsErrCanceledOrKilled(err) {
  77. log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)
  78. }
  79. checker.cancel()
  80. }()
  81. return checker, nil
  82. }
  83. // CheckPath check attr for given path
  84. func (c *BatchChecker) CheckPath(path string) (rs *Attributes, err error) {
  85. defer func() {
  86. if err != nil && err != c.ctx.Err() {
  87. log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.repo.Path), err)
  88. }
  89. }()
  90. select {
  91. case <-c.ctx.Done():
  92. return nil, c.ctx.Err()
  93. default:
  94. }
  95. if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
  96. defer c.Close()
  97. return nil, err
  98. }
  99. reportTimeout := func() error {
  100. stdOutClosed := false
  101. select {
  102. case <-c.stdOut.closed:
  103. stdOutClosed = true
  104. default:
  105. }
  106. debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.repo.Path))
  107. debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
  108. if c.cmd != nil {
  109. debugMsg += fmt.Sprintf(", process state: %q", c.cmd.ProcessState())
  110. }
  111. _ = c.Close()
  112. return fmt.Errorf("CheckPath timeout: %s", debugMsg)
  113. }
  114. rs = NewAttributes()
  115. for i := 0; i < c.attributesNum; i++ {
  116. select {
  117. case <-time.After(5 * time.Second):
  118. // there is no "hang" problem now. This code is just used to catch other potential problems.
  119. return nil, reportTimeout()
  120. case attr, ok := <-c.stdOut.ReadAttribute():
  121. if !ok {
  122. return nil, c.ctx.Err()
  123. }
  124. rs.m[attr.Attribute] = Attribute(attr.Value)
  125. case <-c.ctx.Done():
  126. return nil, c.ctx.Err()
  127. }
  128. }
  129. return rs, nil
  130. }
  131. func (c *BatchChecker) Close() error {
  132. c.cancel()
  133. err := c.stdinWriter.Close()
  134. return err
  135. }
  136. type attributeTriple struct {
  137. Filename string
  138. Attribute string
  139. Value string
  140. }
  141. type nulSeparatedAttributeWriter struct {
  142. tmp []byte
  143. attributes chan attributeTriple
  144. closed chan struct{}
  145. working attributeTriple
  146. pos int
  147. }
  148. func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
  149. l, read := len(p), 0
  150. nulIdx := bytes.IndexByte(p, '\x00')
  151. for nulIdx >= 0 {
  152. wr.tmp = append(wr.tmp, p[:nulIdx]...)
  153. switch wr.pos {
  154. case 0:
  155. wr.working = attributeTriple{
  156. Filename: string(wr.tmp),
  157. }
  158. case 1:
  159. wr.working.Attribute = string(wr.tmp)
  160. case 2:
  161. wr.working.Value = string(wr.tmp)
  162. }
  163. wr.tmp = wr.tmp[:0]
  164. wr.pos++
  165. if wr.pos > 2 {
  166. wr.attributes <- wr.working
  167. wr.pos = 0
  168. }
  169. read += nulIdx + 1
  170. if l > read {
  171. p = p[nulIdx+1:]
  172. nulIdx = bytes.IndexByte(p, '\x00')
  173. } else {
  174. return l, nil
  175. }
  176. }
  177. wr.tmp = append(wr.tmp, p...)
  178. return l, nil
  179. }
  180. func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
  181. return wr.attributes
  182. }
  183. func (wr *nulSeparatedAttributeWriter) Close() error {
  184. select {
  185. case <-wr.closed:
  186. return nil
  187. default:
  188. }
  189. close(wr.attributes)
  190. close(wr.closed)
  191. return nil
  192. }