You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo_attribute.go 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package git
  4. import (
  5. "bytes"
  6. "context"
  7. "fmt"
  8. "io"
  9. "os"
  10. "code.gitea.io/gitea/modules/log"
  11. "github.com/hashicorp/go-version"
  12. )
  13. // CheckAttributeOpts represents the possible options to CheckAttribute
  14. type CheckAttributeOpts struct {
  15. CachedOnly bool
  16. AllAttributes bool
  17. Attributes []string
  18. Filenames []string
  19. IndexFile string
  20. WorkTree string
  21. }
  22. // CheckAttribute return the Blame object of file
  23. func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) {
  24. env := []string{}
  25. if len(opts.IndexFile) > 0 {
  26. env = append(env, "GIT_INDEX_FILE="+opts.IndexFile)
  27. }
  28. if len(opts.WorkTree) > 0 {
  29. env = append(env, "GIT_WORK_TREE="+opts.WorkTree)
  30. }
  31. if len(env) > 0 {
  32. env = append(os.Environ(), env...)
  33. }
  34. stdOut := new(bytes.Buffer)
  35. stdErr := new(bytes.Buffer)
  36. cmd := NewCommand(repo.Ctx, "check-attr", "-z")
  37. if opts.AllAttributes {
  38. cmd.AddArguments("-a")
  39. } else {
  40. for _, attribute := range opts.Attributes {
  41. if attribute != "" {
  42. cmd.AddDynamicArguments(attribute)
  43. }
  44. }
  45. }
  46. if opts.CachedOnly {
  47. cmd.AddArguments("--cached")
  48. }
  49. cmd.AddDashesAndList(opts.Filenames...)
  50. if err := cmd.Run(&RunOpts{
  51. Env: env,
  52. Dir: repo.Path,
  53. Stdout: stdOut,
  54. Stderr: stdErr,
  55. }); err != nil {
  56. return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
  57. }
  58. // FIXME: This is incorrect on versions < 1.8.5
  59. fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
  60. if len(fields)%3 != 1 {
  61. return nil, fmt.Errorf("wrong number of fields in return from check-attr")
  62. }
  63. name2attribute2info := make(map[string]map[string]string)
  64. for i := 0; i < (len(fields) / 3); i++ {
  65. filename := string(fields[3*i])
  66. attribute := string(fields[3*i+1])
  67. info := string(fields[3*i+2])
  68. attribute2info := name2attribute2info[filename]
  69. if attribute2info == nil {
  70. attribute2info = make(map[string]string)
  71. }
  72. attribute2info[attribute] = info
  73. name2attribute2info[filename] = attribute2info
  74. }
  75. return name2attribute2info, nil
  76. }
  77. // CheckAttributeReader provides a reader for check-attribute content that can be long running
  78. type CheckAttributeReader struct {
  79. // params
  80. Attributes []string
  81. Repo *Repository
  82. IndexFile string
  83. WorkTree string
  84. stdinReader io.ReadCloser
  85. stdinWriter *os.File
  86. stdOut attributeWriter
  87. cmd *Command
  88. env []string
  89. ctx context.Context
  90. cancel context.CancelFunc
  91. }
  92. // Init initializes the CheckAttributeReader
  93. func (c *CheckAttributeReader) Init(ctx context.Context) error {
  94. if len(c.Attributes) == 0 {
  95. lw := new(nulSeparatedAttributeWriter)
  96. lw.attributes = make(chan attributeTriple)
  97. lw.closed = make(chan struct{})
  98. c.stdOut = lw
  99. c.stdOut.Close()
  100. return fmt.Errorf("no provided Attributes to check")
  101. }
  102. c.ctx, c.cancel = context.WithCancel(ctx)
  103. c.cmd = NewCommand(c.ctx, "check-attr", "--stdin", "-z")
  104. if len(c.IndexFile) > 0 {
  105. c.cmd.AddArguments("--cached")
  106. c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile)
  107. }
  108. if len(c.WorkTree) > 0 {
  109. c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree)
  110. }
  111. if gitVersion.Equal(version.Must(version.NewVersion("2.43.1"))) {
  112. // https://github.com/go-gitea/gitea/issues/29141 gitea hanging with git 2.43.1 #29141
  113. // https://lore.kernel.org/git/CABn0oJvg3M_kBW-u=j3QhKnO=6QOzk-YFTgonYw_UvFS1NTX4g@mail.gmail.com/
  114. // git 2.43.1 has a bug: the GIT_FLUSH polarity is flipped
  115. c.env = append(c.env, "GIT_FLUSH=0")
  116. } else {
  117. c.env = append(c.env, "GIT_FLUSH=1")
  118. }
  119. c.cmd.AddDynamicArguments(c.Attributes...)
  120. var err error
  121. c.stdinReader, c.stdinWriter, err = os.Pipe()
  122. if err != nil {
  123. c.cancel()
  124. return err
  125. }
  126. lw := new(nulSeparatedAttributeWriter)
  127. lw.attributes = make(chan attributeTriple, 5)
  128. lw.closed = make(chan struct{})
  129. c.stdOut = lw
  130. return nil
  131. }
  132. // Run run cmd
  133. func (c *CheckAttributeReader) Run() error {
  134. defer func() {
  135. _ = c.stdinReader.Close()
  136. _ = c.stdOut.Close()
  137. }()
  138. stdErr := new(bytes.Buffer)
  139. err := c.cmd.Run(&RunOpts{
  140. Env: c.env,
  141. Dir: c.Repo.Path,
  142. Stdin: c.stdinReader,
  143. Stdout: c.stdOut,
  144. Stderr: stdErr,
  145. })
  146. if err != nil && // If there is an error we need to return but:
  147. c.ctx.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
  148. err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
  149. return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
  150. }
  151. return nil
  152. }
  153. // CheckPath check attr for given path
  154. func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
  155. defer func() {
  156. if err != nil && err != c.ctx.Err() {
  157. log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
  158. }
  159. }()
  160. select {
  161. case <-c.ctx.Done():
  162. return nil, c.ctx.Err()
  163. default:
  164. }
  165. if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
  166. defer c.Close()
  167. return nil, err
  168. }
  169. rs = make(map[string]string)
  170. for range c.Attributes {
  171. select {
  172. case attr, ok := <-c.stdOut.ReadAttribute():
  173. if !ok {
  174. return nil, c.ctx.Err()
  175. }
  176. rs[attr.Attribute] = attr.Value
  177. case <-c.ctx.Done():
  178. return nil, c.ctx.Err()
  179. }
  180. }
  181. return rs, nil
  182. }
  183. // Close close pip after use
  184. func (c *CheckAttributeReader) Close() error {
  185. c.cancel()
  186. err := c.stdinWriter.Close()
  187. return err
  188. }
  189. type attributeWriter interface {
  190. io.WriteCloser
  191. ReadAttribute() <-chan attributeTriple
  192. }
  193. type attributeTriple struct {
  194. Filename string
  195. Attribute string
  196. Value string
  197. }
  198. type nulSeparatedAttributeWriter struct {
  199. tmp []byte
  200. attributes chan attributeTriple
  201. closed chan struct{}
  202. working attributeTriple
  203. pos int
  204. }
  205. func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
  206. l, read := len(p), 0
  207. nulIdx := bytes.IndexByte(p, '\x00')
  208. for nulIdx >= 0 {
  209. wr.tmp = append(wr.tmp, p[:nulIdx]...)
  210. switch wr.pos {
  211. case 0:
  212. wr.working = attributeTriple{
  213. Filename: string(wr.tmp),
  214. }
  215. case 1:
  216. wr.working.Attribute = string(wr.tmp)
  217. case 2:
  218. wr.working.Value = string(wr.tmp)
  219. }
  220. wr.tmp = wr.tmp[:0]
  221. wr.pos++
  222. if wr.pos > 2 {
  223. wr.attributes <- wr.working
  224. wr.pos = 0
  225. }
  226. read += nulIdx + 1
  227. if l > read {
  228. p = p[nulIdx+1:]
  229. nulIdx = bytes.IndexByte(p, '\x00')
  230. } else {
  231. return l, nil
  232. }
  233. }
  234. wr.tmp = append(wr.tmp, p...)
  235. return len(p), nil
  236. }
  237. func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
  238. return wr.attributes
  239. }
  240. func (wr *nulSeparatedAttributeWriter) Close() error {
  241. select {
  242. case <-wr.closed:
  243. return nil
  244. default:
  245. }
  246. close(wr.attributes)
  247. close(wr.closed)
  248. return nil
  249. }
  250. // Create a check attribute reader for the current repository and provided commit ID
  251. func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
  252. indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
  253. if err != nil {
  254. return nil, func() {}
  255. }
  256. checker := &CheckAttributeReader{
  257. Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
  258. Repo: repo,
  259. IndexFile: indexFilename,
  260. WorkTree: worktree,
  261. }
  262. ctx, cancel := context.WithCancel(repo.Ctx)
  263. if err := checker.Init(ctx); err != nil {
  264. log.Error("Unable to open checker for %s. Error: %v", commitID, err)
  265. } else {
  266. go func() {
  267. err := checker.Run()
  268. if err != nil && err != ctx.Err() {
  269. log.Error("Unable to open checker for %s. Error: %v", commitID, err)
  270. }
  271. cancel()
  272. }()
  273. }
  274. deferable := func() {
  275. _ = checker.Close()
  276. cancel()
  277. deleteTemporaryFile()
  278. }
  279. return checker, deferable
  280. }