GitContributors.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. const API = require("../../lib/API")
  2. const AccessControl = require("../../lib/AccessControl")
  3. const { BaseStdResponse } = require("../../BaseStdResponse")
  4. const db = require("../../plugin/DataBase/db")
  5. const simpleGit = require('simple-git')
  6. const crypto = require("crypto")
  7. class GitContributors extends API {
  8. constructor() {
  9. super()
  10. this.setMethod("GET")
  11. this.setPath("/Repos/Contributors")
  12. }
  13. getGravatarUrl(email) {
  14. if (!email) return null
  15. const hash = crypto.createHash("md5").update(email.trim().toLowerCase()).digest("hex")
  16. return `https://gravatar.loli.net/avatar/${hash}?s=512&r=pg`
  17. }
  18. async analyzeGitContributors(repoPath) {
  19. try {
  20. const git = simpleGit()
  21. await git.cwd(repoPath)
  22. // 获取提交日志,包括作者姓名和邮箱
  23. const logs = await git.raw([
  24. "log",
  25. "--format=%an <%ae>",
  26. "--shortstat"
  27. ])
  28. const lines = logs.split("\n").map(l => l.trim()).filter(l => l)
  29. let contributors = {}
  30. let currentAuthor = null
  31. for (const line of lines) {
  32. if (line.includes("<") && line.includes(">")) {
  33. const match = line.match(/(.*?) <(.*?)>/)
  34. if (!match) continue
  35. const name = match[1].trim()
  36. const email = match[2].trim()
  37. currentAuthor = name
  38. if (!contributors[currentAuthor]) {
  39. contributors[currentAuthor] = {
  40. name,
  41. email,
  42. avatar: this.getGravatarUrl(email),
  43. commits: 0,
  44. linesChanged: 0
  45. }
  46. }
  47. contributors[currentAuthor].commits += 1
  48. } else if (line.includes("file changed")) {
  49. const match = line.match(/(\d+) insertions?\(\+\)|(\d+) deletions?\(-\)/g)
  50. if (match) {
  51. const changes = match.map(m => parseInt(m.match(/\d+/)[0], 10)).reduce((a, b) => a + b, 0)
  52. contributors[currentAuthor].linesChanged += changes
  53. }
  54. }
  55. }
  56. const sortedContributors = Object.values(contributors)
  57. .sort((a, b) => b.linesChanged - a.linesChanged || b.commits - a.commits)
  58. return sortedContributors
  59. } catch (error) {
  60. console.error("获取仓库开发者失败:", error)
  61. throw new Error("获取仓库开发者失败")
  62. }
  63. }
  64. async onRequest(req, res) {
  65. let { uuid, session, id } = req.query
  66. if ([uuid, session, id].some(value => value === '' || value === null || value === undefined))
  67. return res.json({
  68. ...BaseStdResponse.MISSING_PARAMETER
  69. })
  70. // 检查 session
  71. if (!await AccessControl.checkSession(uuid, session))
  72. return res.status(401).json({
  73. ...BaseStdResponse.ACCESS_DENIED
  74. })
  75. let sql = 'SELECT state, path FROM repos WHERE create_user = ? AND id = ?'
  76. let r = await db.query(sql, [uuid, id])
  77. if (!r || r.length === 0)
  78. return res.json({
  79. ...BaseStdResponse.ERR,
  80. msg: '未找到仓库'
  81. })
  82. if (r[0].state !== 1 || !r[0].path)
  83. return res.json({
  84. ...BaseStdResponse.ERR,
  85. msg: '仓库未成功克隆!'
  86. })
  87. let contributors
  88. try {
  89. contributors = await this.analyzeGitContributors(r[0].path)
  90. } catch (error) {
  91. this.logger.error('获取仓库开发者失败!' + error.stack)
  92. return res.json({
  93. ...BaseStdResponse.ERR,
  94. msg: '获取仓库开发者失败!'
  95. })
  96. }
  97. res.json({
  98. ...BaseStdResponse.OK,
  99. data: contributors
  100. })
  101. }
  102. }
  103. module.exports.GitContributors = GitContributors