@weibaohui/dsh-git-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/README.md +43 -0
  2. package/client/bundle.js +2356 -0
  3. package/client/index.js +2342 -0
  4. package/cordis.patch.yml +11 -0
  5. package/package.json +70 -0
  6. package/server/dist/api/v1.js +2029 -0
  7. package/server/dist/authx/password.js +43 -0
  8. package/server/dist/authx/um.js +157 -0
  9. package/server/dist/conf.js +323 -0
  10. package/server/dist/context.js +701 -0
  11. package/server/dist/db/actions.js +202 -0
  12. package/server/dist/db/db.js +838 -0
  13. package/server/dist/dshapi.js +1416 -0
  14. package/server/dist/editorconfig.js +104 -0
  15. package/server/dist/gitx/git.js +580 -0
  16. package/server/dist/gitx/http.js +232 -0
  17. package/server/dist/gitx/paths.js +28 -0
  18. package/server/dist/gitx/service.js +251 -0
  19. package/server/dist/gosprintf.js +167 -0
  20. package/server/dist/hook.js +53 -0
  21. package/server/dist/index.js +112 -0
  22. package/server/dist/lfsx.js +265 -0
  23. package/server/dist/mailer.js +127 -0
  24. package/server/dist/markup.js +317 -0
  25. package/server/dist/metrics.js +18 -0
  26. package/server/dist/mirror.js +203 -0
  27. package/server/dist/multipart.js +33 -0
  28. package/server/dist/repox.js +99 -0
  29. package/server/dist/router.js +185 -0
  30. package/server/dist/serv.js +43 -0
  31. package/server/dist/server.js +382 -0
  32. package/server/dist/sshx/dispatch.js +160 -0
  33. package/server/dist/sshx/lfs-transfer.js +289 -0
  34. package/server/dist/sshx/server.js +107 -0
  35. package/server/dist/toolx.js +108 -0
  36. package/server/dist/twofactor.js +160 -0
  37. package/server/dist/webapi.js +377 -0
  38. package/server/dist/webhook.js +128 -0
  39. package/server/package.json +34 -0
  40. package/server/public/img/avatar_default.png +0 -0
  41. package/server/tsconfig.json +19 -0
  42. package/server/vendored-conf/app.ini +538 -0
  43. package/server/vendored-conf/gitignore/Actionscript +11 -0
  44. package/server/vendored-conf/gitignore/Ada +5 -0
  45. package/server/vendored-conf/gitignore/Agda +1 -0
  46. package/server/vendored-conf/gitignore/Android +32 -0
  47. package/server/vendored-conf/gitignore/Anjuta +3 -0
  48. package/server/vendored-conf/gitignore/AppEngine +2 -0
  49. package/server/vendored-conf/gitignore/AppceleratorTitanium +3 -0
  50. package/server/vendored-conf/gitignore/ArchLinuxPackages +13 -0
  51. package/server/vendored-conf/gitignore/Archives +27 -0
  52. package/server/vendored-conf/gitignore/Autotools +14 -0
  53. package/server/vendored-conf/gitignore/BricxCC +4 -0
  54. package/server/vendored-conf/gitignore/C +32 -0
  55. package/server/vendored-conf/gitignore/C Sharp +108 -0
  56. package/server/vendored-conf/gitignore/C++ +28 -0
  57. package/server/vendored-conf/gitignore/CFWheels +12 -0
  58. package/server/vendored-conf/gitignore/CMake +6 -0
  59. package/server/vendored-conf/gitignore/CUDA +6 -0
  60. package/server/vendored-conf/gitignore/CakePHP +13 -0
  61. package/server/vendored-conf/gitignore/ChefCookbook +9 -0
  62. package/server/vendored-conf/gitignore/Cloud9 +3 -0
  63. package/server/vendored-conf/gitignore/CodeIgniter +6 -0
  64. package/server/vendored-conf/gitignore/CodeKit +3 -0
  65. package/server/vendored-conf/gitignore/CommonLisp +3 -0
  66. package/server/vendored-conf/gitignore/Composer +6 -0
  67. package/server/vendored-conf/gitignore/Concrete5 +3 -0
  68. package/server/vendored-conf/gitignore/Coq +3 -0
  69. package/server/vendored-conf/gitignore/CraftCMS +3 -0
  70. package/server/vendored-conf/gitignore/DM +5 -0
  71. package/server/vendored-conf/gitignore/Dart +15 -0
  72. package/server/vendored-conf/gitignore/DartEditor +2 -0
  73. package/server/vendored-conf/gitignore/Delphi +57 -0
  74. package/server/vendored-conf/gitignore/Dreamweaver +3 -0
  75. package/server/vendored-conf/gitignore/Drupal +36 -0
  76. package/server/vendored-conf/gitignore/EPiServer +4 -0
  77. package/server/vendored-conf/gitignore/Eagle +36 -0
  78. package/server/vendored-conf/gitignore/Eclipse +39 -0
  79. package/server/vendored-conf/gitignore/EiffelStudio +2 -0
  80. package/server/vendored-conf/gitignore/Elisp +5 -0
  81. package/server/vendored-conf/gitignore/Elixir +4 -0
  82. package/server/vendored-conf/gitignore/Emacs +32 -0
  83. package/server/vendored-conf/gitignore/Ensime +4 -0
  84. package/server/vendored-conf/gitignore/Erlang +10 -0
  85. package/server/vendored-conf/gitignore/Espresso +1 -0
  86. package/server/vendored-conf/gitignore/ExpressionEngine +19 -0
  87. package/server/vendored-conf/gitignore/ExtJs +4 -0
  88. package/server/vendored-conf/gitignore/Fancy +2 -0
  89. package/server/vendored-conf/gitignore/Finale +13 -0
  90. package/server/vendored-conf/gitignore/FlexBuilder +3 -0
  91. package/server/vendored-conf/gitignore/ForceDotCom +4 -0
  92. package/server/vendored-conf/gitignore/FuelPHP +2 -0
  93. package/server/vendored-conf/gitignore/GWT +28 -0
  94. package/server/vendored-conf/gitignore/Gcov +5 -0
  95. package/server/vendored-conf/gitignore/GitBook +16 -0
  96. package/server/vendored-conf/gitignore/Go +24 -0
  97. package/server/vendored-conf/gitignore/Gradle +8 -0
  98. package/server/vendored-conf/gitignore/Grails +33 -0
  99. package/server/vendored-conf/gitignore/Haskell +16 -0
  100. package/server/vendored-conf/gitignore/IGORPro +5 -0
  101. package/server/vendored-conf/gitignore/IPythonNotebook +2 -0
  102. package/server/vendored-conf/gitignore/Idris +2 -0
  103. package/server/vendored-conf/gitignore/JDeveloper +13 -0
  104. package/server/vendored-conf/gitignore/Java +12 -0
  105. package/server/vendored-conf/gitignore/Jboss +19 -0
  106. package/server/vendored-conf/gitignore/Jekyll +3 -0
  107. package/server/vendored-conf/gitignore/JetBrains +46 -0
  108. package/server/vendored-conf/gitignore/Joomla +525 -0
  109. package/server/vendored-conf/gitignore/KDevelop4 +2 -0
  110. package/server/vendored-conf/gitignore/Kate +3 -0
  111. package/server/vendored-conf/gitignore/KiCAD +13 -0
  112. package/server/vendored-conf/gitignore/Kohana +2 -0
  113. package/server/vendored-conf/gitignore/LabVIEW +16 -0
  114. package/server/vendored-conf/gitignore/Laravel +4 -0
  115. package/server/vendored-conf/gitignore/Lazarus +30 -0
  116. package/server/vendored-conf/gitignore/Leiningen +12 -0
  117. package/server/vendored-conf/gitignore/LemonStand +21 -0
  118. package/server/vendored-conf/gitignore/LibreOffice +2 -0
  119. package/server/vendored-conf/gitignore/Lilypond +6 -0
  120. package/server/vendored-conf/gitignore/Linux +7 -0
  121. package/server/vendored-conf/gitignore/Lithium +2 -0
  122. package/server/vendored-conf/gitignore/Lua +41 -0
  123. package/server/vendored-conf/gitignore/LyX +4 -0
  124. package/server/vendored-conf/gitignore/Magento +126 -0
  125. package/server/vendored-conf/gitignore/Matlab +16 -0
  126. package/server/vendored-conf/gitignore/Maven +9 -0
  127. package/server/vendored-conf/gitignore/Mercurial +6 -0
  128. package/server/vendored-conf/gitignore/Mercury +13 -0
  129. package/server/vendored-conf/gitignore/MetaProgrammingSystem +16 -0
  130. package/server/vendored-conf/gitignore/MicrosoftOffice +10 -0
  131. package/server/vendored-conf/gitignore/ModelSim +23 -0
  132. package/server/vendored-conf/gitignore/Momentics +8 -0
  133. package/server/vendored-conf/gitignore/MonoDevelop +8 -0
  134. package/server/vendored-conf/gitignore/Nanoc +10 -0
  135. package/server/vendored-conf/gitignore/NetBeans +8 -0
  136. package/server/vendored-conf/gitignore/Nim +1 -0
  137. package/server/vendored-conf/gitignore/Ninja +2 -0
  138. package/server/vendored-conf/gitignore/Node +28 -0
  139. package/server/vendored-conf/gitignore/NotepadPP +2 -0
  140. package/server/vendored-conf/gitignore/OCaml +20 -0
  141. package/server/vendored-conf/gitignore/Objective-C +43 -0
  142. package/server/vendored-conf/gitignore/Opa +13 -0
  143. package/server/vendored-conf/gitignore/OpenCart +11 -0
  144. package/server/vendored-conf/gitignore/OracleForms +8 -0
  145. package/server/vendored-conf/gitignore/Packer +5 -0
  146. package/server/vendored-conf/gitignore/Perl +20 -0
  147. package/server/vendored-conf/gitignore/Phalcon +2 -0
  148. package/server/vendored-conf/gitignore/PhpStorm +63 -0
  149. package/server/vendored-conf/gitignore/PlayFramework +17 -0
  150. package/server/vendored-conf/gitignore/Plone +18 -0
  151. package/server/vendored-conf/gitignore/Prestashop +25 -0
  152. package/server/vendored-conf/gitignore/Processing +7 -0
  153. package/server/vendored-conf/gitignore/Python +58 -0
  154. package/server/vendored-conf/gitignore/Qooxdoo +5 -0
  155. package/server/vendored-conf/gitignore/Qt +34 -0
  156. package/server/vendored-conf/gitignore/R +16 -0
  157. package/server/vendored-conf/gitignore/ROS +47 -0
  158. package/server/vendored-conf/gitignore/Rails +35 -0
  159. package/server/vendored-conf/gitignore/Redcar +1 -0
  160. package/server/vendored-conf/gitignore/Redis +3 -0
  161. package/server/vendored-conf/gitignore/RhodesRhomobile +9 -0
  162. package/server/vendored-conf/gitignore/Ruby +36 -0
  163. package/server/vendored-conf/gitignore/Rust +11 -0
  164. package/server/vendored-conf/gitignore/SBT +9 -0
  165. package/server/vendored-conf/gitignore/SCons +2 -0
  166. package/server/vendored-conf/gitignore/SVN +1 -0
  167. package/server/vendored-conf/gitignore/Sass +2 -0
  168. package/server/vendored-conf/gitignore/Scala +17 -0
  169. package/server/vendored-conf/gitignore/Scrivener +7 -0
  170. package/server/vendored-conf/gitignore/Sdcc +8 -0
  171. package/server/vendored-conf/gitignore/SeamGen +26 -0
  172. package/server/vendored-conf/gitignore/SketchUp +1 -0
  173. package/server/vendored-conf/gitignore/SlickEdit +11 -0
  174. package/server/vendored-conf/gitignore/Stella +12 -0
  175. package/server/vendored-conf/gitignore/SublimeText +14 -0
  176. package/server/vendored-conf/gitignore/SugarCRM +25 -0
  177. package/server/vendored-conf/gitignore/Swift +43 -0
  178. package/server/vendored-conf/gitignore/Symfony +37 -0
  179. package/server/vendored-conf/gitignore/SymphonyCMS +6 -0
  180. package/server/vendored-conf/gitignore/SynopsysVCS +36 -0
  181. package/server/vendored-conf/gitignore/Tags +14 -0
  182. package/server/vendored-conf/gitignore/TeX +136 -0
  183. package/server/vendored-conf/gitignore/TextMate +3 -0
  184. package/server/vendored-conf/gitignore/Textpattern +11 -0
  185. package/server/vendored-conf/gitignore/TortoiseGit +2 -0
  186. package/server/vendored-conf/gitignore/TurboGears2 +20 -0
  187. package/server/vendored-conf/gitignore/Typo3 +20 -0
  188. package/server/vendored-conf/gitignore/Umbraco +17 -0
  189. package/server/vendored-conf/gitignore/Unity +21 -0
  190. package/server/vendored-conf/gitignore/UnrealEngine +76 -0
  191. package/server/vendored-conf/gitignore/VVVV +6 -0
  192. package/server/vendored-conf/gitignore/Vagrant +1 -0
  193. package/server/vendored-conf/gitignore/Vim +6 -0
  194. package/server/vendored-conf/gitignore/VirtualEnv +10 -0
  195. package/server/vendored-conf/gitignore/VisualStudio +217 -0
  196. package/server/vendored-conf/gitignore/VisualStudioCode +2 -0
  197. package/server/vendored-conf/gitignore/Waf +3 -0
  198. package/server/vendored-conf/gitignore/WebMethods +14 -0
  199. package/server/vendored-conf/gitignore/WebStorm +63 -0
  200. package/server/vendored-conf/gitignore/Windows +18 -0
  201. package/server/vendored-conf/gitignore/WordPress +18 -0
  202. package/server/vendored-conf/gitignore/Xcode +23 -0
  203. package/server/vendored-conf/gitignore/XilinxISE +57 -0
  204. package/server/vendored-conf/gitignore/Xojo +11 -0
  205. package/server/vendored-conf/gitignore/Yeoman +6 -0
  206. package/server/vendored-conf/gitignore/Yii +6 -0
  207. package/server/vendored-conf/gitignore/ZendFramework +20 -0
  208. package/server/vendored-conf/gitignore/Zephir +26 -0
  209. package/server/vendored-conf/gitignore/macOS +25 -0
  210. package/server/vendored-conf/label/Default +7 -0
  211. package/server/vendored-conf/license/Abstyles License +10 -0
  212. package/server/vendored-conf/license/Academic Free License v1.1 +22 -0
  213. package/server/vendored-conf/license/Academic Free License v1.2 +27 -0
  214. package/server/vendored-conf/license/Academic Free License v2.0 +43 -0
  215. package/server/vendored-conf/license/Academic Free License v2.1 +44 -0
  216. package/server/vendored-conf/license/Academic Free License v3.0 +42 -0
  217. package/server/vendored-conf/license/Affero General Public License v1.0 +84 -0
  218. package/server/vendored-conf/license/Apache License 1.0 +19 -0
  219. package/server/vendored-conf/license/Apache License 1.1 +20 -0
  220. package/server/vendored-conf/license/Apache License 2.0 +72 -0
  221. package/server/vendored-conf/license/Artistic License 1.0 +48 -0
  222. package/server/vendored-conf/license/Artistic License 2.0 +84 -0
  223. package/server/vendored-conf/license/BSD 2-clause License +8 -0
  224. package/server/vendored-conf/license/BSD 3-clause License +10 -0
  225. package/server/vendored-conf/license/BSD 4-clause License +13 -0
  226. package/server/vendored-conf/license/Creative Commons CC0 1.0 Universal +39 -0
  227. package/server/vendored-conf/license/Eclipse Public License 1.0 +72 -0
  228. package/server/vendored-conf/license/Educational Community License v1.0 +22 -0
  229. package/server/vendored-conf/license/Educational Community License v2.0 +97 -0
  230. package/server/vendored-conf/license/GNU Affero General Public License v3.0 +241 -0
  231. package/server/vendored-conf/license/GNU Free Documentation License v1.1 +118 -0
  232. package/server/vendored-conf/license/GNU Free Documentation License v1.2 +129 -0
  233. package/server/vendored-conf/license/GNU Free Documentation License v1.3 +148 -0
  234. package/server/vendored-conf/license/GNU General Public License v1.0 +99 -0
  235. package/server/vendored-conf/license/GNU General Public License v2.0 +116 -0
  236. package/server/vendored-conf/license/GNU General Public License v3.0 +231 -0
  237. package/server/vendored-conf/license/GNU Lesser General Public License v2.1 +174 -0
  238. package/server/vendored-conf/license/GNU Lesser General Public License v3.0 +70 -0
  239. package/server/vendored-conf/license/GNU Library General Public License v2.0 +173 -0
  240. package/server/vendored-conf/license/ISC license +6 -0
  241. package/server/vendored-conf/license/MIT License +8 -0
  242. package/server/vendored-conf/license/Mozilla Public License 1.0 +122 -0
  243. package/server/vendored-conf/license/Mozilla Public License 1.1 +142 -0
  244. package/server/vendored-conf/license/Mozilla Public License 2.0 +143 -0
  245. package/server/vendored-conf/readme/Default +3 -0
  246. package/src/dsh-host.js +168 -0
  247. package/src/index.js +682 -0
  248. package/src/runner.js +364 -0
@@ -0,0 +1,104 @@
1
+ // Minimal editorconfig-core compatible parser/matcher for the
2
+ // /api/v1/repos/:u/:r/editorconfig/:filename endpoint.
3
+ import * as git from './gitx/git.js';
4
+ export function parseEditorconfig(text) {
5
+ const out = { root: false, definitions: [] };
6
+ let current = null;
7
+ for (const rawLine of text.split(/\r?\n/)) {
8
+ const line = rawLine.replace(/;.*$|(^|\s)#.*$/, (m, p1) => (p1 ?? '')).trim();
9
+ if (!line)
10
+ continue;
11
+ const sec = /^\[(.+)\]$/.exec(line);
12
+ if (sec) {
13
+ current = { section: sec[1], props: {} };
14
+ out.definitions.push(current);
15
+ continue;
16
+ }
17
+ const kv = /^\s*([A-Za-z_][A-Za-z0-9_-]*)\s*[=:]\s*(.*?)\s*$/.exec(rawLine);
18
+ if (!kv)
19
+ continue;
20
+ const [, key, value] = kv;
21
+ if (!current) {
22
+ if (key.toLowerCase() === 'root' && value.toLowerCase() === 'true')
23
+ out.root = true;
24
+ continue;
25
+ }
26
+ current.props[key.toLowerCase()] = value.toLowerCase() === 'true' ? 'true' : value.toLowerCase() === 'false' ? 'false' : value;
27
+ }
28
+ return out;
29
+ }
30
+ /** Expand `*.{a,b}` style brace groups into multiple globs. */
31
+ function expandBraces(glob) {
32
+ const m = /\{([^{}]+)\}/.exec(glob);
33
+ if (!m)
34
+ return [glob];
35
+ const out = [];
36
+ for (const alt of m[1].split(',')) {
37
+ out.push(...expandBraces(glob.slice(0, m.index) + alt + glob.slice(m.index + m[0].length)));
38
+ }
39
+ return out;
40
+ }
41
+ function globToRegex(glob) {
42
+ let source = '';
43
+ let i = 0;
44
+ while (i < glob.length) {
45
+ const ch = glob[i];
46
+ if (ch === '*') {
47
+ if (glob[i + 1] === '*') {
48
+ source += '.*';
49
+ i += 2;
50
+ }
51
+ else {
52
+ source += '[^/]*';
53
+ i++;
54
+ }
55
+ continue;
56
+ }
57
+ if (ch === '?') {
58
+ source += '[^/]';
59
+ i++;
60
+ continue;
61
+ }
62
+ source += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
63
+ i++;
64
+ }
65
+ return new RegExp('^' + source + '$', 'i');
66
+ }
67
+ /** editorconfig-core GetDefinitionForFilename: last matching section wins per property. */
68
+ export function getDefinitionForFilename(config, filename) {
69
+ filename = filename.replace(/^\//, '');
70
+ const result = {};
71
+ let matchedSection = '';
72
+ for (const def of config.definitions) {
73
+ const globs = expandBraces(def.section);
74
+ for (const glob of globs) {
75
+ // a glob containing '/' matches against the full path; otherwise basename
76
+ const target = glob.includes('/') ? filename : filename.split('/').pop();
77
+ if (globToRegex(glob).test(target)) {
78
+ matchedSection = def.section;
79
+ Object.assign(result, def.props);
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ if (!matchedSection)
85
+ return null;
86
+ // indent_size defaults to tab_width when indent_style=tab (per spec)
87
+ if (result['indent_style'] === 'tab' && result['indent_size'] === undefined && result['tab_width'] !== undefined) {
88
+ result['indent_size'] = result['tab_width'];
89
+ }
90
+ return { Section: matchedSection, Properties: result };
91
+ }
92
+ /** Fetch and parse {repoDir}/.editorconfig at the given ref. */
93
+ export async function repoEditorconfig(repoDir, ref) {
94
+ const commit = await git.getCommit(repoDir, ref);
95
+ if (!commit)
96
+ return null;
97
+ const tree = await git.lsTree(repoDir, commit.id, '.editorconfig');
98
+ const entry = tree?.entries[0];
99
+ if (!entry || entry.type !== 'blob')
100
+ return null;
101
+ const content = (await git.blobBytes(repoDir, entry.sha)).toString('utf8');
102
+ return parseEditorconfig(content);
103
+ }
104
+ //# sourceMappingURL=editorconfig.js.map
@@ -0,0 +1,580 @@
1
+ // Git CLI wrapper mirroring gogs/git-module usage patterns from the contract
2
+ // (internal/gitx + github.com/gogs/git-module). All revs are passed after
3
+ // positional position with `-` prefixed values rejected.
4
+ import { spawn, execFile } from 'node:child_process';
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ export function safeRev(rev) {
8
+ if (!rev || rev.startsWith('-'))
9
+ throw new Error(`invalid revision: ${rev}`);
10
+ return rev;
11
+ }
12
+ export function runGit(dir, args, timeoutMs = 120000, extraEnv = {}) {
13
+ return new Promise((resolve, reject) => {
14
+ const child = spawn('git', args, {
15
+ cwd: dir || undefined,
16
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: '/bin/true', GCM_INTERACTIVE: 'Never', ...extraEnv },
17
+ });
18
+ const chunks = [];
19
+ let stderr = '';
20
+ let settled = false;
21
+ const timer = setTimeout(() => {
22
+ if (!settled) {
23
+ settled = true;
24
+ child.kill('SIGKILL');
25
+ reject(new Error(`git ${args[0]}: timeout after ${timeoutMs}ms`));
26
+ }
27
+ }, timeoutMs);
28
+ child.stdout.on('data', (d) => chunks.push(d));
29
+ child.stderr.on('data', (d) => (stderr += d.toString()));
30
+ child.on('error', (e) => {
31
+ if (settled)
32
+ return;
33
+ settled = true;
34
+ clearTimeout(timer);
35
+ reject(e);
36
+ });
37
+ child.on('close', (code) => {
38
+ if (settled)
39
+ return;
40
+ settled = true;
41
+ clearTimeout(timer);
42
+ resolve({ stdout: Buffer.concat(chunks), stderr, code: code ?? 0 });
43
+ });
44
+ });
45
+ }
46
+ export async function git(dir, ...args) {
47
+ const r = await runGit(dir, args);
48
+ if (r.code !== 0)
49
+ throw new Error(`git ${args.join(' ')}: ${r.stderr.trim() || 'exit ' + r.code}`);
50
+ return r.stdout;
51
+ }
52
+ /** Run git tolerating non-zero exit (returns null on failure). */
53
+ export async function gitOK(dir, ...args) {
54
+ const r = await runGit(dir, args);
55
+ return r.code === 0 ? r.stdout : null;
56
+ }
57
+ // ---------------------------------------------------------------- version
58
+ export async function gitVersion() {
59
+ const out = await git(process.cwd(), 'version');
60
+ return out.toString().trim().split(' ')[2] ?? '';
61
+ }
62
+ // views with Go-style exported field names for templates
63
+ const sigViews = new WeakMap();
64
+ function sigView(sig) {
65
+ let v = sigViews.get(sig);
66
+ if (!v) {
67
+ v = {};
68
+ defineAlias(v, 'Name', 'name', sig);
69
+ defineAlias(v, 'Email', 'email', sig);
70
+ defineAlias(v, 'When', 'when', sig);
71
+ sigViews.set(sig, v);
72
+ }
73
+ return v;
74
+ }
75
+ /** String wrapper exposing .String() like Go sha1 objects. */
76
+ function shaObject(sha) {
77
+ const obj = new String(sha);
78
+ obj.String = () => sha;
79
+ return obj;
80
+ }
81
+ function defineAlias(target, exported, key, src) {
82
+ Object.defineProperty(target, exported, {
83
+ get() {
84
+ return (src ?? target)[key];
85
+ },
86
+ enumerable: false,
87
+ configurable: true,
88
+ });
89
+ }
90
+ function defineGetter(target, exported, fn) {
91
+ Object.defineProperty(target, exported, {
92
+ get: fn,
93
+ enumerable: false,
94
+ configurable: true,
95
+ });
96
+ }
97
+ /** Full Go-style view of a Commit for templates: ID, Author/Committer views,
98
+ * Summary(), ParentsCount, plus User/CommitterUser slots filled by callers. */
99
+ export function commitView(cm) {
100
+ const v = {
101
+ ID: shaObject(cm.id),
102
+ id: cm.id,
103
+ Message: cm.message,
104
+ message: cm.message,
105
+ Parents: cm.parents,
106
+ ParentsCount: cm.parents.length,
107
+ Author: sigView(cm.author),
108
+ Committer: sigView(cm.committer),
109
+ author: cm.author,
110
+ committer: cm.committer,
111
+ Summary: () => cm.Summary(),
112
+ FullMessage: () => cm.message,
113
+ repoDir: cm.repoDir,
114
+ };
115
+ return v;
116
+ }
117
+ export function Summary() {
118
+ return '';
119
+ }
120
+ function parseSignature(line) {
121
+ // "Name <email> 1234567890 +0800"
122
+ const m = /^(.*) <([^>]*)> (\d+) ([+-]\d{4})$/.exec(line.trim());
123
+ if (!m) {
124
+ const m2 = /^(.*) <([^>]*)>$/.exec(line.trim());
125
+ return { name: m2?.[1] ?? line.trim(), email: m2?.[2] ?? '', when: new Date(0) };
126
+ }
127
+ return { name: m[1], email: m[2], when: new Date(Number(m[3]) * 1000) };
128
+ }
129
+ export function parseRawCommit(repoDir, sha, raw) {
130
+ const [head, ...rest] = raw.split('\n\n');
131
+ const message = rest.join('\n\n').replace(/\n$/, '');
132
+ let tree = '';
133
+ const parents = [];
134
+ let author = { name: '', email: '', when: new Date(0) };
135
+ let committer = { name: '', email: '', when: new Date(0) };
136
+ for (const line of head.split('\n')) {
137
+ if (line.startsWith('tree '))
138
+ tree = line.slice(5).trim();
139
+ else if (line.startsWith('parent '))
140
+ parents.push(line.slice(7).trim());
141
+ else if (line.startsWith('author '))
142
+ author = parseSignature(line.slice(7));
143
+ else if (line.startsWith('committer '))
144
+ committer = parseSignature(line.slice(10));
145
+ }
146
+ const commit = {
147
+ id: sha,
148
+ author,
149
+ committer,
150
+ message,
151
+ parents,
152
+ repoDir,
153
+ Summary() {
154
+ const idx = message.indexOf('\n');
155
+ return idx < 0 ? message : message.slice(0, idx);
156
+ },
157
+ FullMessage() {
158
+ return message;
159
+ },
160
+ };
161
+ // Go-style exported aliases for templates (.ID, .Author.Name, .Committer.When ...)
162
+ defineGetter(commit, 'ID', () => shaObject(sha));
163
+ defineAlias(commit, 'Message', 'message');
164
+ defineGetter(commit, 'Author', () => sigView(commit.author));
165
+ defineGetter(commit, 'Committer', () => sigView(commit.committer));
166
+ return commit;
167
+ }
168
+ export async function catFileCommit(repoDir, sha) {
169
+ const out = await git(repoDir, 'cat-file', 'commit', safeRev(sha));
170
+ return parseRawCommit(repoDir, sha, out.toString('utf8'));
171
+ }
172
+ export async function getCommit(repoDir, rev) {
173
+ const sha = await gitOK(repoDir, 'rev-parse', '--verify', '--end-of-options', safeRev(rev) + '^{commit}');
174
+ if (!sha)
175
+ return null;
176
+ return catFileCommit(repoDir, sha.toString().trim());
177
+ }
178
+ export async function refExists(repoDir, rev) {
179
+ const out = await gitOK(repoDir, 'rev-parse', '--verify', '--quiet', '--end-of-options', safeRev(rev));
180
+ return out !== null;
181
+ }
182
+ /** Resolve branch → tag → 7..40 hex sha, mirroring gogs RefCommits handling. */
183
+ /** gogs RevParse semantics: resolve a branch/tag/SHA to its commit ID. */
184
+ export async function resolveRef(repoDir, ref) {
185
+ for (const candidate of ['refs/heads/' + ref, 'refs/tags/' + ref]) {
186
+ if (await refExists(repoDir, candidate)) {
187
+ const out = await gitOK(repoDir, 'rev-parse', '--verify', '--end-of-options', candidate + '^{commit}');
188
+ return out ? out.toString().trim() : null;
189
+ }
190
+ }
191
+ if (/^[0-9a-f]{7,40}$/.test(ref) && (await refExists(repoDir, ref + '^{commit}')))
192
+ return ref;
193
+ return null;
194
+ }
195
+ export async function commitsCount(repoDir, rev) {
196
+ const out = await gitOK(repoDir, 'rev-list', '--count', '--end-of-options', safeRev(rev), '--');
197
+ return out ? Number(out.toString().trim()) : 0;
198
+ }
199
+ export async function commitsByPage(repoDir, rev, page, size, filePath) {
200
+ const args = ['log', '--pretty=format:%H', `--max-count=${size}`, `--skip=${(page - 1) * size}`, '--end-of-options', safeRev(rev)];
201
+ if (filePath)
202
+ args.push('--', filePath);
203
+ const out = await gitOK(repoDir, ...args);
204
+ if (!out)
205
+ return [];
206
+ const shas = out.toString().trim().split('\n').filter(Boolean);
207
+ const commits = [];
208
+ for (const sha of shas) {
209
+ try {
210
+ commits.push(await catFileCommit(repoDir, sha));
211
+ }
212
+ catch {
213
+ // ignore missing
214
+ }
215
+ }
216
+ return commits;
217
+ }
218
+ export async function commitsAfter(repoDir, oldSha, newSha) {
219
+ const out = await gitOK(repoDir, 'rev-list', '--end-of-options', `${oldSha}...${newSha}`, '--');
220
+ if (!out)
221
+ return [];
222
+ const shas = out.toString().trim().split('\n').filter(Boolean);
223
+ const commits = [];
224
+ for (const sha of shas) {
225
+ try {
226
+ commits.push(await catFileCommit(repoDir, sha));
227
+ }
228
+ catch {
229
+ // ignore
230
+ }
231
+ }
232
+ return commits;
233
+ }
234
+ /** Latest commit that touched the given path. */
235
+ export async function commitByPath(repoDir, rev, treePath) {
236
+ const out = await gitOK(repoDir, 'log', '--pretty=format:%H', '--max-count=1', '--end-of-options', safeRev(rev), '--', treePath);
237
+ if (!out)
238
+ return null;
239
+ const sha = out.toString().trim();
240
+ if (!sha)
241
+ return null;
242
+ return catFileCommit(repoDir, sha);
243
+ }
244
+ export async function lsTree(repoDir, treeRev, treePath) {
245
+ let rev = safeRev(treeRev);
246
+ if (treePath) {
247
+ // walk down the path
248
+ const parts = treePath.split('/').filter(Boolean);
249
+ for (let i = 0; i < parts.length; i++) {
250
+ const sub = await lsTreeOnce(repoDir, rev, '');
251
+ const found = sub?.entries.find((e) => e.name === parts[i]);
252
+ if (!found)
253
+ return null;
254
+ rev = found.sha;
255
+ if (found.type !== 'tree') {
256
+ // blob / submodule: single-entry view, don't ls-tree a non-tree
257
+ return {
258
+ sha: rev,
259
+ entries: [{
260
+ mode: found.mode,
261
+ type: found.type,
262
+ sha: found.sha,
263
+ name: found.name,
264
+ size: found.type === 'blob' ? await entrySize(repoDir, found.sha) : 0,
265
+ }],
266
+ };
267
+ }
268
+ }
269
+ }
270
+ const entries = await lsTreeOnce(repoDir, rev, '');
271
+ if (!entries)
272
+ return null;
273
+ return { entries: entries.entries, sha: rev };
274
+ }
275
+ async function lsTreeOnce(repoDir, rev, subPath) {
276
+ const args = ['ls-tree', '-z', '--end-of-options', safeRev(rev)];
277
+ if (subPath)
278
+ args.push('--', subPath);
279
+ const out = await gitOK(repoDir, ...args);
280
+ if (!out)
281
+ return null;
282
+ const entries = [];
283
+ const records = out.toString('utf8').split('\0').filter(Boolean);
284
+ for (const rec of records) {
285
+ // "<mode> <type> <sha>\t<name>"
286
+ const tab = rec.indexOf('\t');
287
+ if (tab < 0)
288
+ continue;
289
+ const meta = rec.slice(0, tab).split(/\s+/);
290
+ const name = rec.slice(tab + 1);
291
+ entries.push({
292
+ mode: meta[0],
293
+ type: meta[1],
294
+ sha: meta[2],
295
+ name,
296
+ size: 0,
297
+ });
298
+ }
299
+ return { entries, sha: rev };
300
+ }
301
+ export async function entrySize(repoDir, sha) {
302
+ const out = await gitOK(repoDir, 'cat-file', '-s', sha);
303
+ return out ? Number(out.toString().trim()) : 0;
304
+ }
305
+ export async function blobBytes(repoDir, sha, maxBytes = 0) {
306
+ if (maxBytes > 0) {
307
+ // read at most maxBytes by streaming through cat-file with size check
308
+ const size = await entrySize(repoDir, sha);
309
+ if (size > maxBytes) {
310
+ const err = new Error('file too large');
311
+ err.tooLarge = true;
312
+ err.size = size;
313
+ throw err;
314
+ }
315
+ }
316
+ return git(repoDir, 'cat-file', 'blob', sha);
317
+ }
318
+ export async function getBranches(repoDir) {
319
+ const out = await gitOK(repoDir, 'show-ref', '--heads');
320
+ if (!out)
321
+ return [];
322
+ const branches = [];
323
+ for (const line of out.toString().trim().split('\n').filter(Boolean)) {
324
+ const [sha, ref] = line.split(/\s+/);
325
+ const name = ref.replace('refs/heads/', '');
326
+ try {
327
+ const commit = await catFileCommit(repoDir, sha);
328
+ branches.push({ name, commit });
329
+ }
330
+ catch {
331
+ // ignore
332
+ }
333
+ }
334
+ return branches;
335
+ }
336
+ export async function getDefaultBranch(repoDir) {
337
+ const out = await gitOK(repoDir, 'symbolic-ref', '--short', 'HEAD');
338
+ if (out)
339
+ return out.toString().trim();
340
+ return 'master';
341
+ }
342
+ export async function getTags(repoDir) {
343
+ const out = await gitOK(repoDir, 'show-ref', '--tags');
344
+ if (!out)
345
+ return [];
346
+ const tags = [];
347
+ for (const line of out.toString().trim().split('\n').filter(Boolean)) {
348
+ const [sha, ref] = line.split(/\s+/);
349
+ const name = ref.replace('refs/tags/', '');
350
+ try {
351
+ // dereference annotated tags
352
+ const deref = (await gitOK(repoDir, 'rev-parse', '--verify', '--end-of-options', sha + '^{commit}'))?.toString().trim() ?? sha;
353
+ const commit = await catFileCommit(repoDir, deref);
354
+ tags.push({ name, commit });
355
+ }
356
+ catch {
357
+ // ignore
358
+ }
359
+ }
360
+ return tags;
361
+ }
362
+ export async function repoDiff(repoDir, rev, base, maxFiles = 1000, maxLines = 5000) {
363
+ let args;
364
+ if (base) {
365
+ args = ['diff', '--full-index', '-M', '--end-of-options', safeRev(base), safeRev(rev)];
366
+ }
367
+ else {
368
+ const commit = await getCommit(repoDir, rev);
369
+ if (!commit)
370
+ throw new Error('commit not found: ' + rev);
371
+ if (commit.parents.length > 0) {
372
+ args = ['diff', '--full-index', '-M', '--end-of-options', commit.parents[0], commit.id];
373
+ }
374
+ else {
375
+ args = ['show', '--full-index', '--end-of-options', commit.id];
376
+ }
377
+ }
378
+ const out = await git(repoDir, ...args);
379
+ return parseDiff(out.toString('utf8'), maxFiles, maxLines);
380
+ }
381
+ export async function diffNameOnly(repoDir, base, head) {
382
+ const out = await gitOK(repoDir, 'diff', '--name-only', '--end-of-options', safeRev(base), safeRev(head));
383
+ if (!out)
384
+ return [];
385
+ return out.toString().trim().split('\n').filter(Boolean);
386
+ }
387
+ export async function mergeBase(repoDir, base, head) {
388
+ const out = await gitOK(repoDir, 'merge-base', '--end-of-options', safeRev(base), safeRev(head));
389
+ return out ? out.toString().trim() : null;
390
+ }
391
+ export function parseDiff(text, maxFiles = 1000, maxLines = 5000) {
392
+ const diff = { files: [], totalAdditions: 0, totalDeletions: 0, numFiles: 0, isIncomplete: false, Files: [], NumFiles: 0, TotalAdditions: 0, TotalDeletions: 0, IsIncomplete: false };
393
+ if (!text)
394
+ return diff;
395
+ const lines = text.split('\n');
396
+ let curFile = null;
397
+ let curSection = null;
398
+ let leftIdx = 0;
399
+ let rightIdx = 0;
400
+ let lineCount = 0;
401
+ for (let i = 0; i < lines.length; i++) {
402
+ const line = lines[i];
403
+ if (line.startsWith('diff --git ')) {
404
+ if (diff.files.length >= maxFiles) {
405
+ diff.isIncomplete = true;
406
+ break;
407
+ }
408
+ // parse "diff --git a/x b/x"
409
+ const rest = line.slice('diff --git '.length);
410
+ const paths = parseDiffGitPaths(rest);
411
+ curFile = {
412
+ name: paths.b,
413
+ oldName: paths.a,
414
+ index: diff.files.length + 1,
415
+ type: 'change',
416
+ isBinary: false,
417
+ sections: [],
418
+ additions: 0,
419
+ deletions: 0,
420
+ isCreated: false,
421
+ isDeleted: false,
422
+ mode: '',
423
+ };
424
+ diff.files.push(curFile);
425
+ curSection = null;
426
+ continue;
427
+ }
428
+ if (!curFile)
429
+ continue;
430
+ if (line.startsWith('new file mode')) {
431
+ curFile.type = 'add';
432
+ curFile.isCreated = true;
433
+ curFile.mode = line.slice('new file mode '.length).trim();
434
+ }
435
+ else if (line.startsWith('deleted file mode')) {
436
+ curFile.type = 'delete';
437
+ curFile.isDeleted = true;
438
+ curFile.mode = line.slice('deleted file mode '.length).trim();
439
+ }
440
+ else if (line.startsWith('old mode') || line.startsWith('new mode')) {
441
+ curFile.mode = line.split(' ').pop() ?? '';
442
+ }
443
+ else if (line.startsWith('rename from ')) {
444
+ curFile.type = 'rename';
445
+ curFile.oldName = line.slice('rename from '.length);
446
+ }
447
+ else if (line.startsWith('rename to ')) {
448
+ curFile.name = line.slice('rename to '.length);
449
+ }
450
+ else if (line.startsWith('Binary')) {
451
+ curFile.isBinary = true;
452
+ }
453
+ else if (line.startsWith('@@')) {
454
+ if (lineCount >= maxLines) {
455
+ diff.isIncomplete = true;
456
+ continue;
457
+ }
458
+ const m = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
459
+ curSection = {
460
+ fileName: curFile.name,
461
+ leftHunk: m?.[1] ?? '0',
462
+ leftRange: m?.[2] ? Number(m[2]) : 1,
463
+ rightHunk: m?.[3] ?? '0',
464
+ rightRange: m?.[4] ? Number(m[4]) : 1,
465
+ lines: [],
466
+ };
467
+ curFile.sections.push(curSection);
468
+ leftIdx = Number(curSection.leftHunk);
469
+ rightIdx = Number(curSection.rightHunk);
470
+ }
471
+ else if (curSection) {
472
+ if (line.startsWith('+')) {
473
+ curSection.lines.push({ leftIdx: 0, rightIdx: rightIdx++, type: 'add', content: line.slice(1) });
474
+ curFile.additions++;
475
+ diff.totalAdditions++;
476
+ lineCount++;
477
+ }
478
+ else if (line.startsWith('-')) {
479
+ curSection.lines.push({ leftIdx: leftIdx++, rightIdx: 0, type: 'del', content: line.slice(1) });
480
+ curFile.deletions++;
481
+ diff.totalDeletions++;
482
+ lineCount++;
483
+ }
484
+ else if (line.startsWith(' ') || line === '') {
485
+ curSection.lines.push({ leftIdx: leftIdx++, rightIdx: rightIdx++, type: 'same', content: line.slice(1) });
486
+ lineCount++;
487
+ }
488
+ // "" ignored
489
+ }
490
+ }
491
+ diff.numFiles = diff.files.length;
492
+ diff.Files = diff.files;
493
+ diff.NumFiles = diff.numFiles;
494
+ diff.TotalAdditions = diff.totalAdditions;
495
+ diff.TotalDeletions = diff.totalDeletions;
496
+ diff.IsIncomplete = diff.isIncomplete;
497
+ return diff;
498
+ }
499
+ function parseDiffGitPaths(rest) {
500
+ // handles quoted paths and plain a/... b/...
501
+ const m = /^"((?:[^"\\]|\\.)*)" "((?:[^"\\]|\\.)*)"$/.exec(rest);
502
+ if (m) {
503
+ return { a: unescapeDiffPath(m[1].slice(2)), b: unescapeDiffPath(m[2].slice(2)) };
504
+ }
505
+ const parts = rest.split(' ');
506
+ return { a: (parts[0] ?? '').replace(/^a\//, ''), b: (parts[1] ?? '').replace(/^b\//, '') };
507
+ }
508
+ function unescapeDiffPath(p) {
509
+ try {
510
+ return JSON.parse('"' + p + '"');
511
+ }
512
+ catch {
513
+ return p;
514
+ }
515
+ }
516
+ // ---------------------------------------------------------------- archive & misc
517
+ export async function archive(repoDir, sha, format, dst, prefix) {
518
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
519
+ await git(repoDir, 'archive', `--prefix=${prefix}`, `--format=${format === 'zip' ? 'zip' : 'tar.gz'}`, '-o', dst, '--end-of-options', sha);
520
+ }
521
+ export async function updateServerInfo(repoDir) {
522
+ await gitOK(repoDir, 'update-server-info');
523
+ }
524
+ export async function countObjects(repoDir) {
525
+ const out = await gitOK(repoDir, 'count-objects', '-v');
526
+ if (!out)
527
+ return { size: 0 };
528
+ const m = /size: (\d+)/.exec(out.toString());
529
+ const sizeDisk = /size-pack: (\d+)/.exec(out.toString());
530
+ const kb = Number(m?.[1] ?? 0) + Number(sizeDisk?.[1] ?? 0);
531
+ return { size: kb * 1024 };
532
+ }
533
+ export async function initBare(repoDir, defaultBranch) {
534
+ fs.mkdirSync(repoDir, { recursive: true });
535
+ await git(repoDir, 'init', '--bare', '--end-of-options');
536
+ await gitOK(repoDir, 'symbolic-ref', 'HEAD', `refs/heads/${defaultBranch}`);
537
+ }
538
+ /** pkt-line for smart HTTP advertisement */
539
+ export function pktLine(data) {
540
+ const len = Buffer.byteLength(data) + 4;
541
+ return len.toString(16).padStart(4, '0') + data;
542
+ }
543
+ /** Execute `git <service> --stateless-rpc [--advertise-refs] <dir>` piping stdin→stdout. */
544
+ export function statelessRPC(dir, service, advertiseRefs, input, extraEnv = {}) {
545
+ return new Promise((resolve, reject) => {
546
+ const args = [service, '--stateless-rpc'];
547
+ if (advertiseRefs)
548
+ args.push('--advertise-refs');
549
+ args.push('.');
550
+ const child = spawn('git', args, {
551
+ cwd: dir,
552
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0', ...extraEnv },
553
+ });
554
+ const chunks = [];
555
+ child.stdout.on('data', (d) => chunks.push(d));
556
+ child.stderr.on('data', () => { });
557
+ child.on('error', reject);
558
+ child.on('close', (code) => {
559
+ if (code === 0)
560
+ resolve(Buffer.concat(chunks));
561
+ else
562
+ reject(new Error(`${service} exited with ${code}`));
563
+ });
564
+ child.stdin.end(input);
565
+ });
566
+ }
567
+ /** execFile helper for one-shot commands outside repo context */
568
+ export function execCmd(cmd, args, timeoutMs = 600000) {
569
+ return new Promise((resolve, reject) => {
570
+ execFile(cmd, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 * 64 }, (err, stdout, stderr) => {
571
+ if (err) {
572
+ reject(new Error(`${cmd} ${args.join(' ')}: ${stderr || err.message}`));
573
+ }
574
+ else {
575
+ resolve(stdout);
576
+ }
577
+ });
578
+ });
579
+ }
580
+ //# sourceMappingURL=git.js.map