gitea源码

git_diff_tree_test.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // Copyright 2025 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package gitdiff
  4. import (
  5. "strings"
  6. "testing"
  7. "code.gitea.io/gitea/modules/git"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestGitDiffTree(t *testing.T) {
  12. test := []struct {
  13. Name string
  14. RepoPath string
  15. BaseSha string
  16. HeadSha string
  17. useMergeBase bool
  18. Expected *DiffTree
  19. }{
  20. {
  21. Name: "happy path",
  22. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  23. BaseSha: "72866af952e98d02a73003501836074b286a78f6",
  24. HeadSha: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd",
  25. Expected: &DiffTree{
  26. Files: []*DiffTreeRecord{
  27. {
  28. Status: "modified",
  29. HeadPath: "LICENSE",
  30. BasePath: "LICENSE",
  31. HeadMode: git.EntryModeBlob,
  32. BaseMode: git.EntryModeBlob,
  33. HeadBlobID: "ee469963e76ae1bb7ee83d7510df2864e6c8c640",
  34. BaseBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336",
  35. },
  36. {
  37. Status: "modified",
  38. HeadPath: "README.md",
  39. BasePath: "README.md",
  40. HeadMode: git.EntryModeBlob,
  41. BaseMode: git.EntryModeBlob,
  42. HeadBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba",
  43. BaseBlobID: "074e590b8e64898b02beef03ece83f962c94f54c",
  44. },
  45. },
  46. },
  47. },
  48. {
  49. Name: "first commit (no parent)",
  50. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  51. HeadSha: "72866af952e98d02a73003501836074b286a78f6",
  52. Expected: &DiffTree{
  53. Files: []*DiffTreeRecord{
  54. {
  55. Status: "added",
  56. HeadPath: ".gitignore",
  57. BasePath: ".gitignore",
  58. HeadMode: git.EntryModeBlob,
  59. BaseMode: git.EntryModeNoEntry,
  60. HeadBlobID: "f1c181ec9c5c921245027c6b452ecfc1d3626364",
  61. BaseBlobID: "0000000000000000000000000000000000000000",
  62. },
  63. {
  64. Status: "added",
  65. HeadPath: "LICENSE",
  66. BasePath: "LICENSE",
  67. HeadMode: git.EntryModeBlob,
  68. BaseMode: git.EntryModeNoEntry,
  69. HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336",
  70. BaseBlobID: "0000000000000000000000000000000000000000",
  71. },
  72. {
  73. Status: "added",
  74. HeadPath: "README.md",
  75. BasePath: "README.md",
  76. HeadMode: git.EntryModeBlob,
  77. BaseMode: git.EntryModeNoEntry,
  78. HeadBlobID: "074e590b8e64898b02beef03ece83f962c94f54c",
  79. BaseBlobID: "0000000000000000000000000000000000000000",
  80. },
  81. },
  82. },
  83. },
  84. {
  85. Name: "first commit (no parent), merge base = true",
  86. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  87. HeadSha: "72866af952e98d02a73003501836074b286a78f6",
  88. useMergeBase: true,
  89. Expected: &DiffTree{
  90. Files: []*DiffTreeRecord{
  91. {
  92. Status: "added",
  93. HeadPath: ".gitignore",
  94. BasePath: ".gitignore",
  95. HeadMode: git.EntryModeBlob,
  96. BaseMode: git.EntryModeNoEntry,
  97. HeadBlobID: "f1c181ec9c5c921245027c6b452ecfc1d3626364",
  98. BaseBlobID: "0000000000000000000000000000000000000000",
  99. },
  100. {
  101. Status: "added",
  102. HeadPath: "LICENSE",
  103. BasePath: "LICENSE",
  104. HeadMode: git.EntryModeBlob,
  105. BaseMode: git.EntryModeNoEntry,
  106. HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336",
  107. BaseBlobID: "0000000000000000000000000000000000000000",
  108. },
  109. {
  110. Status: "added",
  111. HeadPath: "README.md",
  112. BasePath: "README.md",
  113. HeadMode: git.EntryModeBlob,
  114. BaseMode: git.EntryModeNoEntry,
  115. HeadBlobID: "074e590b8e64898b02beef03ece83f962c94f54c",
  116. BaseBlobID: "0000000000000000000000000000000000000000",
  117. },
  118. },
  119. },
  120. },
  121. {
  122. Name: "base and head same",
  123. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  124. BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f",
  125. HeadSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f",
  126. Expected: &DiffTree{
  127. Files: []*DiffTreeRecord{},
  128. },
  129. },
  130. {
  131. Name: "useMergeBase false",
  132. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  133. BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f",
  134. HeadSha: "111cac04bd7d20301964e27a93698aabb5781b80", // this commit can be found on the update-readme branch
  135. useMergeBase: false,
  136. Expected: &DiffTree{
  137. Files: []*DiffTreeRecord{
  138. {
  139. Status: "modified",
  140. HeadPath: "LICENSE",
  141. BasePath: "LICENSE",
  142. HeadMode: git.EntryModeBlob,
  143. BaseMode: git.EntryModeBlob,
  144. HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336",
  145. BaseBlobID: "ed5119b3c1f45547b6785bc03eac7f87570fa17f",
  146. },
  147. {
  148. Status: "modified",
  149. HeadPath: "README.md",
  150. BasePath: "README.md",
  151. HeadMode: git.EntryModeBlob,
  152. BaseMode: git.EntryModeBlob,
  153. HeadBlobID: "fb39771a8865c9a67f2ab9b616c854805664553c",
  154. BaseBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba",
  155. },
  156. },
  157. },
  158. },
  159. {
  160. Name: "useMergeBase true",
  161. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  162. BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f",
  163. HeadSha: "111cac04bd7d20301964e27a93698aabb5781b80", // this commit can be found on the update-readme branch
  164. useMergeBase: true,
  165. Expected: &DiffTree{
  166. Files: []*DiffTreeRecord{
  167. {
  168. Status: "modified",
  169. HeadPath: "README.md",
  170. BasePath: "README.md",
  171. HeadMode: git.EntryModeBlob,
  172. BaseMode: git.EntryModeBlob,
  173. HeadBlobID: "fb39771a8865c9a67f2ab9b616c854805664553c",
  174. BaseBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba",
  175. },
  176. },
  177. },
  178. },
  179. {
  180. Name: "no base set",
  181. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  182. HeadSha: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", // this commit can be found on the update-readme branch
  183. useMergeBase: false,
  184. Expected: &DiffTree{
  185. Files: []*DiffTreeRecord{
  186. {
  187. Status: "modified",
  188. HeadPath: "LICENSE",
  189. BasePath: "LICENSE",
  190. HeadMode: git.EntryModeBlob,
  191. BaseMode: git.EntryModeBlob,
  192. HeadBlobID: "ee469963e76ae1bb7ee83d7510df2864e6c8c640",
  193. BaseBlobID: "ed5119b3c1f45547b6785bc03eac7f87570fa17f",
  194. },
  195. },
  196. },
  197. },
  198. }
  199. for _, tt := range test {
  200. t.Run(tt.Name, func(t *testing.T) {
  201. gitRepo, err := git.OpenRepository(t.Context(), tt.RepoPath)
  202. assert.NoError(t, err)
  203. defer gitRepo.Close()
  204. diffPaths, err := GetDiffTree(t.Context(), gitRepo, tt.useMergeBase, tt.BaseSha, tt.HeadSha)
  205. require.NoError(t, err)
  206. assert.Equal(t, tt.Expected, diffPaths)
  207. })
  208. }
  209. }
  210. func TestParseGitDiffTree(t *testing.T) {
  211. test := []struct {
  212. Name string
  213. GitOutput string
  214. Expected []*DiffTreeRecord
  215. }{
  216. {
  217. Name: "file change",
  218. GitOutput: ":100644 100644 64e43d23bcd08db12563a0a4d84309cadb437e1a 5dbc7792b5bb228647cfcc8dfe65fc649119dedc M\tResources/views/curriculum/edit.blade.php",
  219. Expected: []*DiffTreeRecord{
  220. {
  221. Status: "modified",
  222. HeadPath: "Resources/views/curriculum/edit.blade.php",
  223. BasePath: "Resources/views/curriculum/edit.blade.php",
  224. HeadMode: git.EntryModeBlob,
  225. BaseMode: git.EntryModeBlob,
  226. HeadBlobID: "5dbc7792b5bb228647cfcc8dfe65fc649119dedc",
  227. BaseBlobID: "64e43d23bcd08db12563a0a4d84309cadb437e1a",
  228. },
  229. },
  230. },
  231. {
  232. Name: "file added",
  233. GitOutput: ":000000 100644 0000000000000000000000000000000000000000 0063162fb403db15ceb0517b34ab782e4e58b619 A\tResources/views/class/index.blade.php",
  234. Expected: []*DiffTreeRecord{
  235. {
  236. Status: "added",
  237. HeadPath: "Resources/views/class/index.blade.php",
  238. BasePath: "Resources/views/class/index.blade.php",
  239. HeadMode: git.EntryModeBlob,
  240. BaseMode: git.EntryModeNoEntry,
  241. HeadBlobID: "0063162fb403db15ceb0517b34ab782e4e58b619",
  242. BaseBlobID: "0000000000000000000000000000000000000000",
  243. },
  244. },
  245. },
  246. {
  247. Name: "file deleted",
  248. GitOutput: ":100644 000000 bac4286303c8c0017ea2f0a48c561ddcc0330a14 0000000000000000000000000000000000000000 D\tResources/views/classes/index.blade.php",
  249. Expected: []*DiffTreeRecord{
  250. {
  251. Status: "deleted",
  252. HeadPath: "Resources/views/classes/index.blade.php",
  253. BasePath: "Resources/views/classes/index.blade.php",
  254. HeadMode: git.EntryModeNoEntry,
  255. BaseMode: git.EntryModeBlob,
  256. HeadBlobID: "0000000000000000000000000000000000000000",
  257. BaseBlobID: "bac4286303c8c0017ea2f0a48c561ddcc0330a14",
  258. },
  259. },
  260. },
  261. {
  262. Name: "file renamed",
  263. GitOutput: ":100644 100644 c8a055cfb45cd39747292983ad1797ceab40f5b1 97248f79a90aaf81fe7fd74b33c1cb182dd41783 R087\tDatabase/Seeders/AdminDatabaseSeeder.php\tDatabase/Seeders/AcademicDatabaseSeeder.php",
  264. Expected: []*DiffTreeRecord{
  265. {
  266. Status: "renamed",
  267. Score: 87,
  268. HeadPath: "Database/Seeders/AcademicDatabaseSeeder.php",
  269. BasePath: "Database/Seeders/AdminDatabaseSeeder.php",
  270. HeadMode: git.EntryModeBlob,
  271. BaseMode: git.EntryModeBlob,
  272. HeadBlobID: "97248f79a90aaf81fe7fd74b33c1cb182dd41783",
  273. BaseBlobID: "c8a055cfb45cd39747292983ad1797ceab40f5b1",
  274. },
  275. },
  276. },
  277. {
  278. Name: "no changes",
  279. GitOutput: ``,
  280. Expected: []*DiffTreeRecord{},
  281. },
  282. {
  283. Name: "multiple changes",
  284. GitOutput: ":000000 100644 0000000000000000000000000000000000000000 db736b44533a840981f1f17b7029d0f612b69550 A\tHttp/Controllers/ClassController.php\n" +
  285. ":100644 000000 9a4d2344d4d0145db7c91b3f3e123c74367d4ef4 0000000000000000000000000000000000000000 D\tHttp/Controllers/ClassesController.php\n" +
  286. ":100644 100644 f060d6aede65d423f49e7dc248dfa0d8835ef920 b82c8e39a3602dedadb44669956d6eb5b6a7cc86 M\tHttp/Controllers/ProgramDirectorController.php\n",
  287. Expected: []*DiffTreeRecord{
  288. {
  289. Status: "added",
  290. HeadPath: "Http/Controllers/ClassController.php",
  291. BasePath: "Http/Controllers/ClassController.php",
  292. HeadMode: git.EntryModeBlob,
  293. BaseMode: git.EntryModeNoEntry,
  294. HeadBlobID: "db736b44533a840981f1f17b7029d0f612b69550",
  295. BaseBlobID: "0000000000000000000000000000000000000000",
  296. },
  297. {
  298. Status: "deleted",
  299. HeadPath: "Http/Controllers/ClassesController.php",
  300. BasePath: "Http/Controllers/ClassesController.php",
  301. HeadMode: git.EntryModeNoEntry,
  302. BaseMode: git.EntryModeBlob,
  303. HeadBlobID: "0000000000000000000000000000000000000000",
  304. BaseBlobID: "9a4d2344d4d0145db7c91b3f3e123c74367d4ef4",
  305. },
  306. {
  307. Status: "modified",
  308. HeadPath: "Http/Controllers/ProgramDirectorController.php",
  309. BasePath: "Http/Controllers/ProgramDirectorController.php",
  310. HeadMode: git.EntryModeBlob,
  311. BaseMode: git.EntryModeBlob,
  312. HeadBlobID: "b82c8e39a3602dedadb44669956d6eb5b6a7cc86",
  313. BaseBlobID: "f060d6aede65d423f49e7dc248dfa0d8835ef920",
  314. },
  315. },
  316. },
  317. {
  318. Name: "spaces in file path",
  319. GitOutput: ":000000 100644 0000000000000000000000000000000000000000 db736b44533a840981f1f17b7029d0f612b69550 A\tHttp /Controllers/Class Controller.php\n" +
  320. ":100644 000000 9a4d2344d4d0145db7c91b3f3e123c74367d4ef4 0000000000000000000000000000000000000000 D\tHttp/Cont rollers/Classes Controller.php\n" +
  321. ":100644 100644 f060d6aede65d423f49e7dc248dfa0d8835ef920 b82c8e39a3602dedadb44669956d6eb5b6a7cc86 R010\tHttp/Controllers/Program Director Controller.php\tHttp/Cont rollers/ProgramDirectorController.php\n",
  322. Expected: []*DiffTreeRecord{
  323. {
  324. Status: "added",
  325. HeadPath: "Http /Controllers/Class Controller.php",
  326. BasePath: "Http /Controllers/Class Controller.php",
  327. HeadMode: git.EntryModeBlob,
  328. BaseMode: git.EntryModeNoEntry,
  329. HeadBlobID: "db736b44533a840981f1f17b7029d0f612b69550",
  330. BaseBlobID: "0000000000000000000000000000000000000000",
  331. },
  332. {
  333. Status: "deleted",
  334. HeadPath: "Http/Cont rollers/Classes Controller.php",
  335. BasePath: "Http/Cont rollers/Classes Controller.php",
  336. HeadMode: git.EntryModeNoEntry,
  337. BaseMode: git.EntryModeBlob,
  338. HeadBlobID: "0000000000000000000000000000000000000000",
  339. BaseBlobID: "9a4d2344d4d0145db7c91b3f3e123c74367d4ef4",
  340. },
  341. {
  342. Status: "renamed",
  343. Score: 10,
  344. HeadPath: "Http/Cont rollers/ProgramDirectorController.php",
  345. BasePath: "Http/Controllers/Program Director Controller.php",
  346. HeadMode: git.EntryModeBlob,
  347. BaseMode: git.EntryModeBlob,
  348. HeadBlobID: "b82c8e39a3602dedadb44669956d6eb5b6a7cc86",
  349. BaseBlobID: "f060d6aede65d423f49e7dc248dfa0d8835ef920",
  350. },
  351. },
  352. },
  353. {
  354. Name: "file type changed",
  355. GitOutput: ":100644 120000 344e0ca8aa791cc4164fb0ea645f334fd40d00f0 a7c2973de00bfdc6ca51d315f401b5199fe01dc3 T\twebpack.mix.js",
  356. Expected: []*DiffTreeRecord{
  357. {
  358. Status: "typechanged",
  359. HeadPath: "webpack.mix.js",
  360. BasePath: "webpack.mix.js",
  361. HeadMode: git.EntryModeSymlink,
  362. BaseMode: git.EntryModeBlob,
  363. HeadBlobID: "a7c2973de00bfdc6ca51d315f401b5199fe01dc3",
  364. BaseBlobID: "344e0ca8aa791cc4164fb0ea645f334fd40d00f0",
  365. },
  366. },
  367. },
  368. }
  369. for _, tt := range test {
  370. t.Run(tt.Name, func(t *testing.T) {
  371. entries, err := parseGitDiffTree(strings.NewReader(tt.GitOutput))
  372. assert.NoError(t, err)
  373. assert.Equal(t, tt.Expected, entries)
  374. })
  375. }
  376. }
  377. func TestGitDiffTreeErrors(t *testing.T) {
  378. test := []struct {
  379. Name string
  380. RepoPath string
  381. BaseSha string
  382. HeadSha string
  383. }{
  384. {
  385. Name: "head doesn't exist",
  386. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  387. BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
  388. HeadSha: "asdfasdfasdf",
  389. },
  390. {
  391. Name: "base doesn't exist",
  392. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  393. BaseSha: "asdfasdfasdf",
  394. HeadSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
  395. },
  396. {
  397. Name: "head not set",
  398. RepoPath: "../../modules/git/tests/repos/repo5_pulls",
  399. BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
  400. },
  401. }
  402. for _, tt := range test {
  403. t.Run(tt.Name, func(t *testing.T) {
  404. gitRepo, err := git.OpenRepository(t.Context(), tt.RepoPath)
  405. assert.NoError(t, err)
  406. defer gitRepo.Close()
  407. diffPaths, err := GetDiffTree(t.Context(), gitRepo, true, tt.BaseSha, tt.HeadSha)
  408. assert.Error(t, err)
  409. assert.Nil(t, diffPaths)
  410. })
  411. }
  412. }