gitea源码

embedded.go 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cmd
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "code.gitea.io/gitea/modules/assetfs"
  12. "code.gitea.io/gitea/modules/glob"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/options"
  15. "code.gitea.io/gitea/modules/public"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/templates"
  18. "code.gitea.io/gitea/modules/util"
  19. "github.com/urfave/cli/v3"
  20. )
  21. // CmdEmbedded represents the available extract sub-command.
  22. var (
  23. CmdEmbedded = &cli.Command{
  24. Name: "embedded",
  25. Usage: "Extract embedded resources",
  26. Description: "A command for extracting embedded resources, like templates and images",
  27. Commands: []*cli.Command{
  28. subcmdList,
  29. subcmdView,
  30. subcmdExtract,
  31. },
  32. }
  33. subcmdList = &cli.Command{
  34. Name: "list",
  35. Usage: "List files matching the given pattern",
  36. Action: runList,
  37. Flags: []cli.Flag{
  38. &cli.BoolFlag{
  39. Name: "include-vendored",
  40. Aliases: []string{"vendor"},
  41. Usage: "Include files under public/vendor as well",
  42. },
  43. },
  44. }
  45. subcmdView = &cli.Command{
  46. Name: "view",
  47. Usage: "View a file matching the given pattern",
  48. Action: runView,
  49. Flags: []cli.Flag{
  50. &cli.BoolFlag{
  51. Name: "include-vendored",
  52. Aliases: []string{"vendor"},
  53. Usage: "Include files under public/vendor as well",
  54. },
  55. },
  56. }
  57. subcmdExtract = &cli.Command{
  58. Name: "extract",
  59. Usage: "Extract resources",
  60. Action: runExtract,
  61. Flags: []cli.Flag{
  62. &cli.BoolFlag{
  63. Name: "include-vendored",
  64. Aliases: []string{"vendor"},
  65. Usage: "Include files under public/vendor as well",
  66. },
  67. &cli.BoolFlag{
  68. Name: "overwrite",
  69. Usage: "Overwrite files if they already exist",
  70. },
  71. &cli.BoolFlag{
  72. Name: "rename",
  73. Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)",
  74. },
  75. &cli.BoolFlag{
  76. Name: "custom",
  77. Usage: "Extract to the 'custom' directory as per app.ini",
  78. },
  79. &cli.StringFlag{
  80. Name: "destination",
  81. Aliases: []string{"dest-dir"},
  82. Usage: "Extract to the specified directory",
  83. },
  84. },
  85. }
  86. matchedAssetFiles []assetFile
  87. )
  88. type assetFile struct {
  89. fs *assetfs.LayeredFS
  90. name string
  91. path string
  92. }
  93. func initEmbeddedExtractor(c *cli.Command) error {
  94. setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
  95. patterns, err := compileCollectPatterns(c.Args().Slice())
  96. if err != nil {
  97. return err
  98. }
  99. collectAssetFilesByPattern(c, patterns, "options", options.BuiltinAssets())
  100. collectAssetFilesByPattern(c, patterns, "public", public.BuiltinAssets())
  101. collectAssetFilesByPattern(c, patterns, "templates", templates.BuiltinAssets())
  102. return nil
  103. }
  104. func runList(_ context.Context, c *cli.Command) error {
  105. if err := runListDo(c); err != nil {
  106. _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
  107. return err
  108. }
  109. return nil
  110. }
  111. func runView(_ context.Context, c *cli.Command) error {
  112. if err := runViewDo(c); err != nil {
  113. _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
  114. return err
  115. }
  116. return nil
  117. }
  118. func runExtract(_ context.Context, c *cli.Command) error {
  119. if err := runExtractDo(c); err != nil {
  120. _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
  121. return err
  122. }
  123. return nil
  124. }
  125. func runListDo(c *cli.Command) error {
  126. if err := initEmbeddedExtractor(c); err != nil {
  127. return err
  128. }
  129. for _, a := range matchedAssetFiles {
  130. fmt.Println(a.path)
  131. }
  132. return nil
  133. }
  134. func runViewDo(c *cli.Command) error {
  135. if err := initEmbeddedExtractor(c); err != nil {
  136. return err
  137. }
  138. if len(matchedAssetFiles) == 0 {
  139. return errors.New("no files matched the given pattern")
  140. } else if len(matchedAssetFiles) > 1 {
  141. return errors.New("too many files matched the given pattern, try to be more specific")
  142. }
  143. data, err := matchedAssetFiles[0].fs.ReadFile(matchedAssetFiles[0].name)
  144. if err != nil {
  145. return fmt.Errorf("%s: %w", matchedAssetFiles[0].path, err)
  146. }
  147. if _, err = os.Stdout.Write(data); err != nil {
  148. return fmt.Errorf("%s: %w", matchedAssetFiles[0].path, err)
  149. }
  150. return nil
  151. }
  152. func runExtractDo(c *cli.Command) error {
  153. if err := initEmbeddedExtractor(c); err != nil {
  154. return err
  155. }
  156. if c.NArg() == 0 {
  157. return errors.New("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
  158. }
  159. destdir := "."
  160. if c.IsSet("destination") {
  161. destdir = c.String("destination")
  162. } else if c.Bool("custom") {
  163. destdir = setting.CustomPath
  164. fmt.Println("Using app.ini at", setting.CustomConf)
  165. }
  166. fi, err := os.Stat(destdir)
  167. if errors.Is(err, os.ErrNotExist) {
  168. // In case Windows users attempt to provide a forward-slash path
  169. wdestdir := filepath.FromSlash(destdir)
  170. if wfi, werr := os.Stat(wdestdir); werr == nil {
  171. destdir = wdestdir
  172. fi = wfi
  173. err = nil
  174. }
  175. }
  176. if err != nil {
  177. return fmt.Errorf("%s: %s", destdir, err)
  178. } else if !fi.IsDir() {
  179. return fmt.Errorf("destination %q is not a directory", destdir)
  180. }
  181. fmt.Printf("Extracting to %s:\n", destdir)
  182. overwrite := c.Bool("overwrite")
  183. rename := c.Bool("rename")
  184. for _, a := range matchedAssetFiles {
  185. if err := extractAsset(destdir, a, overwrite, rename); err != nil {
  186. // Non-fatal error
  187. _, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", a.path, err)
  188. }
  189. }
  190. return nil
  191. }
  192. func extractAsset(d string, a assetFile, overwrite, rename bool) error {
  193. dest := filepath.Join(d, filepath.FromSlash(a.path))
  194. dir := filepath.Dir(dest)
  195. data, err := a.fs.ReadFile(a.name)
  196. if err != nil {
  197. return fmt.Errorf("%s: %w", a.path, err)
  198. }
  199. if err := os.MkdirAll(dir, os.ModePerm); err != nil {
  200. return fmt.Errorf("%s: %w", dir, err)
  201. }
  202. perms := os.ModePerm & 0o666
  203. fi, err := os.Lstat(dest)
  204. if err != nil {
  205. if !errors.Is(err, os.ErrNotExist) {
  206. return fmt.Errorf("%s: %w", dest, err)
  207. }
  208. } else if !overwrite && !rename {
  209. fmt.Printf("%s already exists; skipped.\n", dest)
  210. return nil
  211. } else if !fi.Mode().IsRegular() {
  212. return fmt.Errorf("%s already exists, but it's not a regular file", dest)
  213. } else if rename {
  214. if err := util.Rename(dest, dest+".bak"); err != nil {
  215. return fmt.Errorf("error creating backup for %s: %w", dest, err)
  216. }
  217. // Attempt to respect file permissions mask (even if user:group will be set anew)
  218. perms = fi.Mode()
  219. }
  220. file, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms)
  221. if err != nil {
  222. return fmt.Errorf("%s: %w", dest, err)
  223. }
  224. defer file.Close()
  225. if _, err = file.Write(data); err != nil {
  226. return fmt.Errorf("%s: %w", dest, err)
  227. }
  228. fmt.Println(dest)
  229. return nil
  230. }
  231. func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) {
  232. fs := assetfs.Layered(layer)
  233. files, err := fs.ListAllFiles(".", true)
  234. if err != nil {
  235. log.Error("Error listing files in %q: %v", path, err)
  236. return
  237. }
  238. for _, name := range files {
  239. if path == "public" &&
  240. strings.HasPrefix(name, "vendor/") &&
  241. !c.Bool("include-vendored") {
  242. continue
  243. }
  244. matchName := path + "/" + name
  245. for _, g := range globs {
  246. if g.Match(matchName) {
  247. matchedAssetFiles = append(matchedAssetFiles, assetFile{fs: fs, name: name, path: path + "/" + name})
  248. break
  249. }
  250. }
  251. }
  252. }
  253. func compileCollectPatterns(args []string) (_ []glob.Glob, err error) {
  254. if len(args) == 0 {
  255. args = []string{"**"}
  256. }
  257. pat := make([]glob.Glob, len(args))
  258. for i := range args {
  259. if pat[i], err = glob.Compile(args[i], '/'); err != nil {
  260. return nil, fmt.Errorf("invalid glob patterh %q: %w", args[i], err)
  261. }
  262. }
  263. return pat, nil
  264. }