rugged 0.18.0.gh.de28323 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (283) hide show
  1. data/README.md +9 -4
  2. data/Rakefile +1 -1
  3. data/ext/rugged/extconf.rb +10 -0
  4. data/ext/rugged/rugged.c +153 -86
  5. data/ext/rugged/rugged.h +44 -33
  6. data/ext/rugged/rugged_blob.c +288 -60
  7. data/ext/rugged/rugged_branch.c +82 -57
  8. data/ext/rugged/rugged_commit.c +83 -86
  9. data/ext/rugged/rugged_config.c +68 -68
  10. data/ext/rugged/rugged_diff.c +509 -0
  11. data/ext/rugged/rugged_diff_delta.c +94 -0
  12. data/ext/rugged/rugged_diff_hunk.c +100 -0
  13. data/ext/rugged/rugged_diff_line.c +79 -0
  14. data/ext/rugged/rugged_diff_patch.c +169 -0
  15. data/ext/rugged/rugged_index.c +539 -8
  16. data/ext/rugged/rugged_note.c +74 -80
  17. data/ext/rugged/rugged_object.c +63 -8
  18. data/ext/rugged/rugged_reference.c +231 -145
  19. data/ext/rugged/rugged_remote.c +509 -53
  20. data/ext/rugged/rugged_repo.c +572 -236
  21. data/ext/rugged/rugged_revwalk.c +59 -36
  22. data/ext/rugged/rugged_settings.c +7 -9
  23. data/ext/rugged/rugged_signature.c +7 -11
  24. data/ext/rugged/rugged_tag.c +93 -39
  25. data/ext/rugged/rugged_tree.c +321 -58
  26. data/lib/rugged.rb +1 -0
  27. data/lib/rugged/commit.rb +16 -1
  28. data/lib/rugged/console.rb +9 -0
  29. data/lib/rugged/diff.rb +19 -0
  30. data/lib/rugged/diff/delta.rb +54 -0
  31. data/lib/rugged/diff/hunk.rb +23 -0
  32. data/lib/rugged/diff/line.rb +29 -0
  33. data/lib/rugged/diff/patch.rb +28 -0
  34. data/lib/rugged/repository.rb +36 -39
  35. data/lib/rugged/version.rb +1 -1
  36. data/test/blob_test.rb +308 -1
  37. data/test/branch_test.rb +7 -0
  38. data/test/commit_test.rb +7 -10
  39. data/test/coverage/cover.rb +9 -1
  40. data/test/diff_test.rb +777 -0
  41. data/test/fixtures/archive.tar.gz +0 -0
  42. data/test/fixtures/attr/attr0 +1 -0
  43. data/test/fixtures/attr/attr1 +29 -0
  44. data/test/fixtures/attr/attr2 +21 -0
  45. data/test/fixtures/attr/attr3 +4 -0
  46. data/test/fixtures/attr/binfile +1 -0
  47. data/test/fixtures/attr/dir/file +0 -0
  48. data/test/fixtures/attr/file +1 -0
  49. data/test/fixtures/attr/gitattributes +29 -0
  50. data/test/fixtures/attr/gitignore +2 -0
  51. data/test/fixtures/attr/ign +1 -0
  52. data/test/fixtures/attr/macro_bad +1 -0
  53. data/test/fixtures/attr/macro_test +1 -0
  54. data/test/fixtures/attr/root_test1 +1 -0
  55. data/test/fixtures/attr/root_test2 +6 -0
  56. data/test/fixtures/attr/root_test3 +19 -0
  57. data/test/fixtures/attr/root_test4.txt +14 -0
  58. data/test/fixtures/attr/sub/abc +37 -0
  59. data/test/fixtures/attr/sub/dir/file +0 -0
  60. data/test/fixtures/attr/sub/file +1 -0
  61. data/test/fixtures/attr/sub/ign/file +1 -0
  62. data/test/fixtures/attr/sub/ign/sub/file +1 -0
  63. data/test/fixtures/attr/sub/sub/dir +0 -0
  64. data/test/fixtures/attr/sub/sub/file +1 -0
  65. data/test/fixtures/attr/sub/sub/subsub.txt +1 -0
  66. data/test/fixtures/attr/sub/subdir_test1 +2 -0
  67. data/test/fixtures/attr/sub/subdir_test2.txt +1 -0
  68. data/test/fixtures/diff/another.txt +38 -0
  69. data/test/fixtures/diff/readme.txt +36 -0
  70. data/test/fixtures/mergedrepo/conflicts-one.txt +5 -0
  71. data/test/fixtures/mergedrepo/conflicts-two.txt +5 -0
  72. data/test/fixtures/mergedrepo/one.txt +10 -0
  73. data/test/fixtures/mergedrepo/two.txt +12 -0
  74. data/test/fixtures/status/current_file +1 -0
  75. data/test/fixtures/status/ignored_file +1 -0
  76. data/test/fixtures/status/modified_file +2 -0
  77. data/test/fixtures/status/new_file +1 -0
  78. data/test/fixtures/status/staged_changes +2 -0
  79. data/test/fixtures/status/staged_changes_modified_file +3 -0
  80. data/test/fixtures/status/staged_delete_modified_file +1 -0
  81. data/test/fixtures/status/staged_new_file +1 -0
  82. data/test/fixtures/status/staged_new_file_modified_file +2 -0
  83. data/test/fixtures/status/subdir.txt +2 -0
  84. data/test/fixtures/status/subdir/current_file +1 -0
  85. data/test/fixtures/status/subdir/modified_file +2 -0
  86. data/test/fixtures/status/subdir/new_file +1 -0
  87. data/test/fixtures/status//350/277/231 +1 -0
  88. data/test/fixtures/testrepo.git/config +5 -0
  89. data/test/fixtures/testrepo.git/objects/77/71329dfa3002caf8c61a0ceb62a31d09023f37 +0 -0
  90. data/test/fixtures/text_file.md +464 -0
  91. data/test/fixtures/unsymlinked.git/HEAD +1 -0
  92. data/test/fixtures/unsymlinked.git/config +6 -0
  93. data/test/fixtures/unsymlinked.git/description +1 -0
  94. data/test/fixtures/unsymlinked.git/info/exclude +2 -0
  95. data/test/fixtures/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf +0 -0
  96. data/test/fixtures/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b +0 -0
  97. data/test/fixtures/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c +0 -0
  98. data/test/fixtures/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773 +0 -0
  99. data/test/fixtures/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c +0 -0
  100. data/test/fixtures/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27 +0 -0
  101. data/test/fixtures/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d +0 -0
  102. data/test/fixtures/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3 +0 -0
  103. data/test/fixtures/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230 +0 -0
  104. data/test/fixtures/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6 +2 -0
  105. data/test/fixtures/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7 +0 -0
  106. data/test/fixtures/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a +0 -0
  107. data/test/fixtures/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9 +0 -0
  108. data/test/fixtures/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006 +0 -0
  109. data/test/fixtures/unsymlinked.git/refs/heads/exe-file +1 -0
  110. data/test/fixtures/unsymlinked.git/refs/heads/master +1 -0
  111. data/test/fixtures/unsymlinked.git/refs/heads/reg-file +1 -0
  112. data/test/index_test.rb +120 -0
  113. data/test/reference_test.rb +38 -3
  114. data/test/remote_test.rb +224 -3
  115. data/test/repo_reset_test.rb +2 -0
  116. data/test/repo_test.rb +147 -10
  117. data/test/test_helper.rb +5 -2
  118. data/vendor/libgit2/include/git2/attr.h +3 -3
  119. data/vendor/libgit2/include/git2/blob.h +11 -17
  120. data/vendor/libgit2/include/git2/branch.h +3 -2
  121. data/vendor/libgit2/include/git2/checkout.h +7 -0
  122. data/vendor/libgit2/include/git2/clone.h +3 -0
  123. data/vendor/libgit2/include/git2/commit.h +61 -66
  124. data/vendor/libgit2/include/git2/common.h +73 -42
  125. data/vendor/libgit2/include/git2/config.h +57 -71
  126. data/vendor/libgit2/include/git2/cred_helpers.h +2 -2
  127. data/vendor/libgit2/include/git2/diff.h +179 -30
  128. data/vendor/libgit2/include/git2/errors.h +3 -3
  129. data/vendor/libgit2/include/git2/index.h +225 -146
  130. data/vendor/libgit2/include/git2/indexer.h +2 -22
  131. data/vendor/libgit2/include/git2/inttypes.h +9 -9
  132. data/vendor/libgit2/include/git2/merge.h +123 -5
  133. data/vendor/libgit2/include/git2/odb.h +59 -38
  134. data/vendor/libgit2/include/git2/odb_backend.h +45 -104
  135. data/vendor/libgit2/include/git2/oid.h +30 -19
  136. data/vendor/libgit2/include/git2/pack.h +21 -3
  137. data/vendor/libgit2/include/git2/refdb.h +0 -35
  138. data/vendor/libgit2/include/git2/refs.h +93 -31
  139. data/vendor/libgit2/include/git2/refspec.h +17 -0
  140. data/vendor/libgit2/include/git2/remote.h +60 -20
  141. data/vendor/libgit2/include/git2/repository.h +48 -70
  142. data/vendor/libgit2/include/git2/reset.h +3 -3
  143. data/vendor/libgit2/include/git2/revparse.h +22 -0
  144. data/vendor/libgit2/include/git2/stash.h +1 -1
  145. data/vendor/libgit2/include/git2/status.h +131 -56
  146. data/vendor/libgit2/include/git2/strarray.h +2 -2
  147. data/vendor/libgit2/include/git2/submodule.h +16 -16
  148. data/vendor/libgit2/include/git2/sys/commit.h +46 -0
  149. data/vendor/libgit2/include/git2/sys/config.h +71 -0
  150. data/vendor/libgit2/include/git2/sys/index.h +179 -0
  151. data/vendor/libgit2/include/git2/sys/odb_backend.h +86 -0
  152. data/vendor/libgit2/include/git2/sys/refdb_backend.h +158 -0
  153. data/vendor/libgit2/include/git2/sys/refs.h +38 -0
  154. data/vendor/libgit2/include/git2/sys/repository.h +106 -0
  155. data/vendor/libgit2/include/git2/tag.h +44 -18
  156. data/vendor/libgit2/include/git2/trace.h +1 -2
  157. data/vendor/libgit2/include/git2/transport.h +74 -0
  158. data/vendor/libgit2/include/git2/tree.h +12 -22
  159. data/vendor/libgit2/include/git2/types.h +33 -0
  160. data/vendor/libgit2/include/git2/version.h +2 -2
  161. data/vendor/libgit2/src/array.h +66 -0
  162. data/vendor/libgit2/src/attr.c +26 -13
  163. data/vendor/libgit2/src/attr_file.c +3 -2
  164. data/vendor/libgit2/src/attr_file.h +3 -3
  165. data/vendor/libgit2/src/attrcache.h +4 -4
  166. data/vendor/libgit2/src/blob.c +13 -9
  167. data/vendor/libgit2/src/blob.h +2 -2
  168. data/vendor/libgit2/src/branch.c +67 -49
  169. data/vendor/libgit2/src/cache.c +224 -54
  170. data/vendor/libgit2/src/cache.h +33 -20
  171. data/vendor/libgit2/src/checkout.c +145 -85
  172. data/vendor/libgit2/src/clone.c +62 -50
  173. data/vendor/libgit2/src/commit.c +74 -40
  174. data/vendor/libgit2/src/commit.h +2 -3
  175. data/vendor/libgit2/src/commit_list.c +14 -8
  176. data/vendor/libgit2/src/config.c +119 -36
  177. data/vendor/libgit2/src/config.h +3 -0
  178. data/vendor/libgit2/src/config_cache.c +24 -7
  179. data/vendor/libgit2/src/config_file.c +9 -6
  180. data/vendor/libgit2/src/crlf.c +4 -2
  181. data/vendor/libgit2/src/date.c +3 -3
  182. data/vendor/libgit2/src/delta.c +1 -1
  183. data/vendor/libgit2/src/diff.c +681 -303
  184. data/vendor/libgit2/src/diff.h +34 -2
  185. data/vendor/libgit2/src/diff_driver.c +405 -0
  186. data/vendor/libgit2/src/diff_driver.h +49 -0
  187. data/vendor/libgit2/src/diff_file.c +447 -0
  188. data/vendor/libgit2/src/diff_file.h +58 -0
  189. data/vendor/libgit2/src/diff_patch.c +995 -0
  190. data/vendor/libgit2/src/diff_patch.h +46 -0
  191. data/vendor/libgit2/src/diff_print.c +430 -0
  192. data/vendor/libgit2/src/diff_tform.c +464 -203
  193. data/vendor/libgit2/src/diff_xdiff.c +166 -0
  194. data/vendor/libgit2/src/diff_xdiff.h +28 -0
  195. data/vendor/libgit2/src/fetch.c +11 -4
  196. data/vendor/libgit2/src/fileops.c +85 -61
  197. data/vendor/libgit2/src/fileops.h +4 -0
  198. data/vendor/libgit2/src/global.c +10 -2
  199. data/vendor/libgit2/src/global.h +0 -8
  200. data/vendor/libgit2/src/hash/hash_generic.h +3 -3
  201. data/vendor/libgit2/src/hash/hash_win32.h +4 -4
  202. data/vendor/libgit2/src/hashsig.c +0 -1
  203. data/vendor/libgit2/src/ignore.c +68 -28
  204. data/vendor/libgit2/src/ignore.h +10 -1
  205. data/vendor/libgit2/src/index.c +666 -84
  206. data/vendor/libgit2/src/index.h +6 -0
  207. data/vendor/libgit2/src/indexer.c +10 -28
  208. data/vendor/libgit2/src/iterator.c +427 -283
  209. data/vendor/libgit2/src/iterator.h +58 -4
  210. data/vendor/libgit2/src/merge.c +1892 -32
  211. data/vendor/libgit2/src/merge.h +132 -5
  212. data/vendor/libgit2/src/merge_file.c +174 -0
  213. data/vendor/libgit2/src/merge_file.h +71 -0
  214. data/vendor/libgit2/src/mwindow.c +1 -1
  215. data/vendor/libgit2/src/notes.c +45 -48
  216. data/vendor/libgit2/src/object.c +89 -127
  217. data/vendor/libgit2/src/object.h +0 -1
  218. data/vendor/libgit2/src/object_api.c +129 -0
  219. data/vendor/libgit2/src/odb.c +156 -59
  220. data/vendor/libgit2/src/odb.h +5 -2
  221. data/vendor/libgit2/src/odb_loose.c +31 -17
  222. data/vendor/libgit2/src/odb_pack.c +39 -43
  223. data/vendor/libgit2/src/oid.c +62 -27
  224. data/vendor/libgit2/src/oid.h +33 -0
  225. data/vendor/libgit2/src/oidmap.h +4 -6
  226. data/vendor/libgit2/src/pack-objects.c +54 -22
  227. data/vendor/libgit2/src/pack.c +98 -56
  228. data/vendor/libgit2/src/pack.h +3 -1
  229. data/vendor/libgit2/src/pathspec.c +26 -1
  230. data/vendor/libgit2/src/pathspec.h +14 -0
  231. data/vendor/libgit2/src/pool.c +5 -0
  232. data/vendor/libgit2/src/posix.c +2 -2
  233. data/vendor/libgit2/src/posix.h +3 -0
  234. data/vendor/libgit2/src/push.c +13 -10
  235. data/vendor/libgit2/src/refdb.c +82 -62
  236. data/vendor/libgit2/src/refdb.h +16 -16
  237. data/vendor/libgit2/src/refdb_fs.c +386 -133
  238. data/vendor/libgit2/src/reflog.c +3 -1
  239. data/vendor/libgit2/src/refs.c +247 -221
  240. data/vendor/libgit2/src/refs.h +2 -1
  241. data/vendor/libgit2/src/refspec.c +18 -1
  242. data/vendor/libgit2/src/refspec.h +3 -1
  243. data/vendor/libgit2/src/remote.c +434 -253
  244. data/vendor/libgit2/src/remote.h +5 -3
  245. data/vendor/libgit2/src/repository.c +197 -111
  246. data/vendor/libgit2/src/repository.h +26 -5
  247. data/vendor/libgit2/src/reset.c +1 -1
  248. data/vendor/libgit2/src/revparse.c +84 -79
  249. data/vendor/libgit2/src/revwalk.c +1 -1
  250. data/vendor/libgit2/src/signature.c +22 -10
  251. data/vendor/libgit2/src/stash.c +5 -2
  252. data/vendor/libgit2/src/status.c +311 -107
  253. data/vendor/libgit2/src/status.h +23 -0
  254. data/vendor/libgit2/src/submodule.c +21 -13
  255. data/vendor/libgit2/src/tag.c +42 -31
  256. data/vendor/libgit2/src/tag.h +2 -3
  257. data/vendor/libgit2/src/thread-utils.h +105 -3
  258. data/vendor/libgit2/src/trace.c +1 -2
  259. data/vendor/libgit2/src/trace.h +3 -3
  260. data/vendor/libgit2/src/transport.c +18 -6
  261. data/vendor/libgit2/src/transports/cred.c +103 -1
  262. data/vendor/libgit2/src/transports/local.c +19 -9
  263. data/vendor/libgit2/src/transports/smart_protocol.c +32 -12
  264. data/vendor/libgit2/src/transports/ssh.c +519 -0
  265. data/vendor/libgit2/src/transports/winhttp.c +3 -1
  266. data/vendor/libgit2/src/tree.c +26 -28
  267. data/vendor/libgit2/src/tree.h +3 -3
  268. data/vendor/libgit2/src/unix/posix.h +2 -0
  269. data/vendor/libgit2/src/util.c +43 -6
  270. data/vendor/libgit2/src/util.h +40 -12
  271. data/vendor/libgit2/src/vector.c +3 -5
  272. data/vendor/libgit2/src/vector.h +9 -0
  273. data/vendor/libgit2/src/win32/dir.c +1 -1
  274. data/vendor/libgit2/src/win32/error.c +2 -0
  275. data/vendor/libgit2/src/win32/findfile.c +3 -6
  276. data/vendor/libgit2/src/win32/posix_w32.c +85 -59
  277. data/vendor/libgit2/src/win32/pthread.c +16 -8
  278. data/vendor/libgit2/src/win32/pthread.h +7 -4
  279. metadata +407 -306
  280. data/test/coverage/HEAD.json +0 -1
  281. data/vendor/libgit2/include/git2/refdb_backend.h +0 -109
  282. data/vendor/libgit2/src/diff_output.c +0 -1819
  283. data/vendor/libgit2/src/diff_output.h +0 -93
@@ -28,6 +28,9 @@ extern int git_config_find_global_r(git_buf *global_config_path);
28
28
  extern int git_config_find_xdg_r(git_buf *system_config_path);
29
29
  extern int git_config_find_system_r(git_buf *system_config_path);
30
30
 
31
+
32
+ extern int git_config__global_location(git_buf *buf);
33
+
31
34
  extern int git_config_rename_section(
32
35
  git_repository *repo,
33
36
  const char *old_section_name, /* eg "branch.dummy" */
@@ -26,7 +26,7 @@ struct map_data {
26
26
  * files that have the text property set. Alternatives are lf, crlf
27
27
  * and native, which uses the platform's native line ending. The default
28
28
  * value is native. See gitattributes(5) for more information on
29
- * end-of-line conversion.
29
+ * end-of-line conversion.
30
30
  */
31
31
  static git_cvar_map _cvar_map_eol[] = {
32
32
  {GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET},
@@ -37,7 +37,7 @@ static git_cvar_map _cvar_map_eol[] = {
37
37
 
38
38
  /*
39
39
  * core.autocrlf
40
- * Setting this variable to "true" is almost the same as setting
40
+ * Setting this variable to "true" is almost the same as setting
41
41
  * the text attribute to "auto" on all files except that text files are
42
42
  * not guaranteed to be normalized: files that contain CRLF in the
43
43
  * repository will not be touched. Use this setting if you want to have
@@ -51,9 +51,22 @@ static git_cvar_map _cvar_map_autocrlf[] = {
51
51
  {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}
52
52
  };
53
53
 
54
+ /*
55
+ * Generic map for integer values
56
+ */
57
+ static git_cvar_map _cvar_map_int[] = {
58
+ {GIT_CVAR_INT32, NULL, 0},
59
+ };
60
+
54
61
  static struct map_data _cvar_maps[] = {
55
62
  {"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT},
56
- {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT}
63
+ {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT},
64
+ {"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT },
65
+ {"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT },
66
+ {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT },
67
+ {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT },
68
+ {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT },
69
+ {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT },
57
70
  };
58
71
 
59
72
  int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar)
@@ -69,12 +82,16 @@ int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar)
69
82
  if (error < 0)
70
83
  return error;
71
84
 
72
- error = git_config_get_mapped(out,
73
- config, data->cvar_name, data->maps, data->map_count);
85
+ if (data->maps)
86
+ error = git_config_get_mapped(
87
+ out, config, data->cvar_name, data->maps, data->map_count);
88
+ else
89
+ error = git_config_get_bool(out, config, data->cvar_name);
74
90
 
75
- if (error == GIT_ENOTFOUND)
91
+ if (error == GIT_ENOTFOUND) {
92
+ giterr_clear();
76
93
  *out = data->default_value;
77
-
94
+ }
78
95
  else if (error < 0)
79
96
  return error;
80
97
 
@@ -12,6 +12,7 @@
12
12
  #include "buffer.h"
13
13
  #include "buf_text.h"
14
14
  #include "git2/config.h"
15
+ #include "git2/sys/config.h"
15
16
  #include "git2/types.h"
16
17
  #include "strmap.h"
17
18
 
@@ -80,10 +81,10 @@ typedef struct {
80
81
  time_t file_mtime;
81
82
  size_t file_size;
82
83
 
83
- unsigned int level;
84
+ git_config_level_t level;
84
85
  } diskfile_backend;
85
86
 
86
- static int config_parse(diskfile_backend *cfg_file, unsigned int level);
87
+ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level);
87
88
  static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
88
89
  static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
89
90
  static char *escape_value(const char *ptr);
@@ -180,7 +181,7 @@ static void free_vars(git_strmap *values)
180
181
  git_strmap_free(values);
181
182
  }
182
183
 
183
- static int config_open(git_config_backend *cfg, unsigned int level)
184
+ static int config_open(git_config_backend *cfg, git_config_level_t level)
184
185
  {
185
186
  int res;
186
187
  diskfile_backend *b = (diskfile_backend *)cfg;
@@ -295,7 +296,7 @@ cleanup:
295
296
 
296
297
  static int config_set(git_config_backend *cfg, const char *name, const char *value)
297
298
  {
298
- cvar_t *var = NULL, *old_var;
299
+ cvar_t *var = NULL, *old_var = NULL;
299
300
  diskfile_backend *b = (diskfile_backend *)cfg;
300
301
  char *key, *esc_value = NULL;
301
302
  khiter_t pos;
@@ -481,8 +482,10 @@ static int config_set_multivar(
481
482
 
482
483
  pos = git_strmap_lookup_index(b->values, key);
483
484
  if (!git_strmap_valid_index(b->values, pos)) {
485
+ /* If we don't have it, behave like a normal set */
486
+ result = config_set(cfg, name, value);
484
487
  git__free(key);
485
- return GIT_ENOTFOUND;
488
+ return result;
486
489
  }
487
490
 
488
491
  var = git_strmap_value_at(b->values, pos);
@@ -962,7 +965,7 @@ static int strip_comments(char *line, int in_quotes)
962
965
  return quote_count;
963
966
  }
964
967
 
965
- static int config_parse(diskfile_backend *cfg_file, unsigned int level)
968
+ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
966
969
  {
967
970
  int c;
968
971
  char *current_section = NULL;
@@ -5,14 +5,16 @@
5
5
  * a Linking Exception. For full terms see the included COPYING file.
6
6
  */
7
7
 
8
+ #include "git2/attr.h"
9
+ #include "git2/blob.h"
10
+ #include "git2/index.h"
11
+
8
12
  #include "common.h"
9
13
  #include "fileops.h"
10
14
  #include "hash.h"
11
15
  #include "filter.h"
12
16
  #include "buf_text.h"
13
17
  #include "repository.h"
14
- #include "git2/attr.h"
15
- #include "git2/blob.h"
16
18
 
17
19
  struct crlf_attrs {
18
20
  int crlf_action;
@@ -823,8 +823,8 @@ static void pending_number(struct tm *tm, int *num)
823
823
  }
824
824
 
825
825
  static git_time_t approxidate_str(const char *date,
826
- const struct timeval *tv,
827
- int *error_ret)
826
+ const struct timeval *tv,
827
+ int *error_ret)
828
828
  {
829
829
  int number = 0;
830
830
  int touched = 0;
@@ -866,7 +866,7 @@ int git__date_parse(git_time_t *out, const char *date)
866
866
  int offset, error_ret=0;
867
867
 
868
868
  if (!parse_date_basic(date, &timestamp, &offset)) {
869
- *out = timestamp;
869
+ *out = timestamp;
870
870
  return 0;
871
871
  }
872
872
 
@@ -168,7 +168,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
168
168
  memset(hash, 0, hsize * sizeof(*hash));
169
169
 
170
170
  /* allocate an array to count hash entries */
171
- hash_count = calloc(hsize, sizeof(*hash_count));
171
+ hash_count = git__calloc(hsize, sizeof(*hash_count));
172
172
  if (!hash_count) {
173
173
  git__free(index);
174
174
  return NULL;
@@ -11,9 +11,13 @@
11
11
  #include "attr_file.h"
12
12
  #include "filter.h"
13
13
  #include "pathspec.h"
14
+ #include "index.h"
15
+ #include "odb.h"
14
16
 
15
17
  #define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
16
18
  #define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
19
+ #define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
20
+ (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
17
21
 
18
22
  static git_diff_delta *diff_delta__alloc(
19
23
  git_diff_list *diff,
@@ -130,6 +134,7 @@ static int diff_delta__from_two(
130
134
  {
131
135
  git_diff_delta *delta;
132
136
  int notify_res;
137
+ const char *canonical_path = old_entry->path;
133
138
 
134
139
  if (status == GIT_DELTA_UNMODIFIED &&
135
140
  DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
@@ -149,7 +154,7 @@ static int diff_delta__from_two(
149
154
  new_mode = temp_mode;
150
155
  }
151
156
 
152
- delta = diff_delta__alloc(diff, status, old_entry->path);
157
+ delta = diff_delta__alloc(diff, status, canonical_path);
153
158
  GITERR_CHECK_ALLOC(delta);
154
159
 
155
160
  git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
@@ -194,21 +199,21 @@ static git_diff_delta *diff_delta__last_for_item(
194
199
  switch (delta->status) {
195
200
  case GIT_DELTA_UNMODIFIED:
196
201
  case GIT_DELTA_DELETED:
197
- if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0)
202
+ if (git_oid__cmp(&delta->old_file.oid, &item->oid) == 0)
198
203
  return delta;
199
204
  break;
200
205
  case GIT_DELTA_ADDED:
201
- if (git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
206
+ if (git_oid__cmp(&delta->new_file.oid, &item->oid) == 0)
202
207
  return delta;
203
208
  break;
204
209
  case GIT_DELTA_UNTRACKED:
205
210
  if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
206
- git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
211
+ git_oid__cmp(&delta->new_file.oid, &item->oid) == 0)
207
212
  return delta;
208
213
  break;
209
214
  case GIT_DELTA_MODIFIED:
210
- if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0 ||
211
- git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
215
+ if (git_oid__cmp(&delta->old_file.oid, &item->oid) == 0 ||
216
+ git_oid__cmp(&delta->new_file.oid, &item->oid) == 0)
212
217
  return delta;
213
218
  break;
214
219
  default:
@@ -229,10 +234,30 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
229
234
  return git_pool_strndup(pool, prefix, len + 1);
230
235
  }
231
236
 
237
+ GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
238
+ {
239
+ const char *str = delta->old_file.path;
240
+
241
+ if (!str ||
242
+ delta->status == GIT_DELTA_ADDED ||
243
+ delta->status == GIT_DELTA_RENAMED ||
244
+ delta->status == GIT_DELTA_COPIED)
245
+ str = delta->new_file.path;
246
+
247
+ return str;
248
+ }
249
+
232
250
  int git_diff_delta__cmp(const void *a, const void *b)
233
251
  {
234
252
  const git_diff_delta *da = a, *db = b;
235
- int val = strcmp(da->old_file.path, db->old_file.path);
253
+ int val = strcmp(diff_delta__path(da), diff_delta__path(db));
254
+ return val ? val : ((int)da->status - (int)db->status);
255
+ }
256
+
257
+ int git_diff_delta__casecmp(const void *a, const void *b)
258
+ {
259
+ const git_diff_delta *da = a, *db = b;
260
+ int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
236
261
  return val ? val : ((int)da->status - (int)db->status);
237
262
  }
238
263
 
@@ -267,67 +292,166 @@ static int config_bool(git_config *cfg, const char *name, int defvalue)
267
292
  return val;
268
293
  }
269
294
 
270
- static git_diff_list *git_diff_list_alloc(
271
- git_repository *repo, const git_diff_options *opts)
295
+ static int config_int(git_config *cfg, const char *name, int defvalue)
272
296
  {
273
- git_config *cfg;
297
+ int val = defvalue;
298
+
299
+ if (git_config_get_int32(&val, cfg, name) < 0)
300
+ giterr_clear();
301
+
302
+ return val;
303
+ }
304
+
305
+ static const char *diff_mnemonic_prefix(
306
+ git_iterator_type_t type, bool left_side)
307
+ {
308
+ const char *pfx = "";
309
+
310
+ switch (type) {
311
+ case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break;
312
+ case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break;
313
+ case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break;
314
+ case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break;
315
+ case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break;
316
+ default: break;
317
+ }
318
+
319
+ /* note: without a deeper look at pathspecs, there is no easy way
320
+ * to get the (o)bject / (w)ork tree mnemonics working...
321
+ */
322
+
323
+ return pfx;
324
+ }
325
+
326
+ static git_diff_list *diff_list_alloc(
327
+ git_repository *repo,
328
+ git_iterator *old_iter,
329
+ git_iterator *new_iter)
330
+ {
331
+ git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
274
332
  git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
275
- if (diff == NULL)
333
+ if (!diff)
276
334
  return NULL;
277
335
 
336
+ assert(repo && old_iter && new_iter);
337
+
278
338
  GIT_REFCOUNT_INC(diff);
279
339
  diff->repo = repo;
340
+ diff->old_src = old_iter->type;
341
+ diff->new_src = new_iter->type;
342
+ memcpy(&diff->opts, &dflt, sizeof(diff->opts));
280
343
 
281
344
  if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
282
- git_pool_init(&diff->pool, 1, 0) < 0)
283
- goto fail;
345
+ git_pool_init(&diff->pool, 1, 0) < 0) {
346
+ git_diff_list_free(diff);
347
+ return NULL;
348
+ }
349
+
350
+ /* Use case-insensitive compare if either iterator has
351
+ * the ignore_case bit set */
352
+ if (!git_iterator_ignore_case(old_iter) &&
353
+ !git_iterator_ignore_case(new_iter)) {
354
+ diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;
355
+
356
+ diff->strcomp = git__strcmp;
357
+ diff->strncomp = git__strncmp;
358
+ diff->pfxcomp = git__prefixcmp;
359
+ diff->entrycomp = git_index_entry__cmp;
360
+ } else {
361
+ diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
362
+
363
+ diff->strcomp = git__strcasecmp;
364
+ diff->strncomp = git__strncasecmp;
365
+ diff->pfxcomp = git__prefixcmp_icase;
366
+ diff->entrycomp = git_index_entry__cmp_icase;
367
+
368
+ git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
369
+ }
370
+
371
+ return diff;
372
+ }
373
+
374
+ static int diff_list_apply_options(
375
+ git_diff_list *diff,
376
+ const git_diff_options *opts)
377
+ {
378
+ git_config *cfg;
379
+ git_repository *repo = diff->repo;
380
+ git_pool *pool = &diff->pool;
381
+ int val;
382
+
383
+ if (opts) {
384
+ /* copy user options (except case sensitivity info from iterators) */
385
+ bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE);
386
+ memcpy(&diff->opts, opts, sizeof(diff->opts));
387
+ DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase);
388
+
389
+ /* initialize pathspec from options */
390
+ if (git_pathspec_init(&diff->pathspec, &opts->pathspec, pool) < 0)
391
+ return -1;
392
+ }
393
+
394
+ /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
395
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
396
+ diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
397
+
398
+ /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
399
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED_CONTENT))
400
+ diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
284
401
 
285
402
  /* load config values that affect diff behavior */
286
403
  if (git_repository_config__weakptr(&cfg, repo) < 0)
287
- goto fail;
288
- if (config_bool(cfg, "core.symlinks", 1))
404
+ return -1;
405
+
406
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS) && val)
289
407
  diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
290
- if (config_bool(cfg, "core.ignorestat", 0))
408
+
409
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORESTAT) && val)
291
410
  diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
292
- if (config_bool(cfg, "core.filemode", 1))
411
+
412
+ if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
413
+ !git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE) && val)
293
414
  diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
294
- if (config_bool(cfg, "core.trustctime", 1))
415
+
416
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_TRUSTCTIME) && val)
295
417
  diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
296
- /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
297
418
 
298
- /* TODO: there are certain config settings where even if we were
299
- * not given an options structure, we need the diff list to have one
300
- * so that we can store the altered default values.
301
- *
302
- * - diff.ignoreSubmodules
303
- * - diff.mnemonicprefix
304
- * - diff.noprefix
305
- */
419
+ /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
306
420
 
307
- if (opts == NULL) {
308
- /* Make sure we default to 3 lines */
309
- diff->opts.context_lines = 3;
310
- return diff;
311
- }
421
+ /* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */
422
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_NANOSECS;
312
423
 
313
- memcpy(&diff->opts, opts, sizeof(git_diff_options));
424
+ /* If not given explicit `opts`, check `diff.xyz` configs */
425
+ if (!opts) {
426
+ diff->opts.context_lines = config_int(cfg, "diff.context", 3);
314
427
 
315
- if(opts->flags & GIT_DIFF_IGNORE_FILEMODE)
316
- diff->diffcaps = diff->diffcaps & ~GIT_DIFFCAPS_TRUST_MODE_BITS;
428
+ if (config_bool(cfg, "diff.ignoreSubmodules", 0))
429
+ diff->opts.flags |= GIT_DIFF_IGNORE_SUBMODULES;
430
+ }
317
431
 
318
- /* pathspec init will do nothing for empty pathspec */
319
- if (git_pathspec_init(&diff->pathspec, &opts->pathspec, &diff->pool) < 0)
320
- goto fail;
432
+ /* if either prefix is not set, figure out appropriate value */
433
+ if (!diff->opts.old_prefix || !diff->opts.new_prefix) {
434
+ const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
435
+ const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
321
436
 
322
- /* TODO: handle config diff.mnemonicprefix, diff.noprefix */
437
+ if (config_bool(cfg, "diff.noprefix", 0)) {
438
+ use_old = use_new = "";
439
+ } else if (config_bool(cfg, "diff.mnemonicprefix", 0)) {
440
+ use_old = diff_mnemonic_prefix(diff->old_src, true);
441
+ use_new = diff_mnemonic_prefix(diff->new_src, false);
442
+ }
323
443
 
324
- diff->opts.old_prefix = diff_strdup_prefix(&diff->pool,
325
- opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT);
326
- diff->opts.new_prefix = diff_strdup_prefix(&diff->pool,
327
- opts->new_prefix ? opts->new_prefix : DIFF_NEW_PREFIX_DEFAULT);
444
+ if (!diff->opts.old_prefix)
445
+ diff->opts.old_prefix = use_old;
446
+ if (!diff->opts.new_prefix)
447
+ diff->opts.new_prefix = use_new;
448
+ }
328
449
 
450
+ /* strdup prefix from pool so we're not dependent on external data */
451
+ diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix);
452
+ diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix);
329
453
  if (!diff->opts.old_prefix || !diff->opts.new_prefix)
330
- goto fail;
454
+ return -1;
331
455
 
332
456
  if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
333
457
  const char *swap = diff->opts.old_prefix;
@@ -335,15 +459,7 @@ static git_diff_list *git_diff_list_alloc(
335
459
  diff->opts.new_prefix = swap;
336
460
  }
337
461
 
338
- /* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
339
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
340
- diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
341
-
342
- return diff;
343
-
344
- fail:
345
- git_diff_list_free(diff);
346
- return NULL;
462
+ return 0;
347
463
  }
348
464
 
349
465
  static void diff_list_free(git_diff_list *diff)
@@ -359,6 +475,8 @@ static void diff_list_free(git_diff_list *diff)
359
475
 
360
476
  git_pathspec_free(&diff->pathspec);
361
477
  git_pool_clear(&diff->pool);
478
+
479
+ git__memzero(diff, sizeof(*diff));
362
480
  git__free(diff);
363
481
  }
364
482
 
@@ -445,24 +563,77 @@ cleanup:
445
563
  return result;
446
564
  }
447
565
 
566
+ static bool diff_time_eq(
567
+ const git_index_time *a, const git_index_time *b, bool use_nanos)
568
+ {
569
+ return a->seconds == b->seconds &&
570
+ (!use_nanos || a->nanoseconds == b->nanoseconds);
571
+ }
572
+
573
+ typedef struct {
574
+ git_repository *repo;
575
+ git_iterator *old_iter;
576
+ git_iterator *new_iter;
577
+ const git_index_entry *oitem;
578
+ const git_index_entry *nitem;
579
+ git_buf ignore_prefix;
580
+ } diff_in_progress;
581
+
448
582
  #define MODE_BITS_MASK 0000777
449
583
 
584
+ static int maybe_modified_submodule(
585
+ git_delta_t *status,
586
+ git_oid *found_oid,
587
+ git_diff_list *diff,
588
+ diff_in_progress *info)
589
+ {
590
+ int error = 0;
591
+ git_submodule *sub;
592
+ unsigned int sm_status = 0;
593
+ const git_oid *sm_oid;
594
+
595
+ *status = GIT_DELTA_UNMODIFIED;
596
+
597
+ if (!DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) &&
598
+ !(error = git_submodule_lookup(
599
+ &sub, diff->repo, info->nitem->path)) &&
600
+ git_submodule_ignore(sub) != GIT_SUBMODULE_IGNORE_ALL &&
601
+ !(error = git_submodule_status(&sm_status, sub)))
602
+ {
603
+ /* check IS_WD_UNMODIFIED because this case is only used
604
+ * when the new side of the diff is the working directory
605
+ */
606
+ if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
607
+ *status = GIT_DELTA_MODIFIED;
608
+
609
+ /* grab OID while we are here */
610
+ if (git_oid_iszero(&info->nitem->oid) &&
611
+ (sm_oid = git_submodule_wd_id(sub)) != NULL)
612
+ git_oid_cpy(found_oid, sm_oid);
613
+ }
614
+
615
+ /* GIT_EEXISTS means a dir with .git in it was found - ignore it */
616
+ if (error == GIT_EEXISTS) {
617
+ giterr_clear();
618
+ error = 0;
619
+ }
620
+
621
+ return error;
622
+ }
623
+
450
624
  static int maybe_modified(
451
- git_iterator *old_iter,
452
- const git_index_entry *oitem,
453
- git_iterator *new_iter,
454
- const git_index_entry *nitem,
455
- git_diff_list *diff)
625
+ git_diff_list *diff,
626
+ diff_in_progress *info)
456
627
  {
457
- git_oid noid, *use_noid = NULL;
628
+ git_oid noid;
458
629
  git_delta_t status = GIT_DELTA_MODIFIED;
630
+ const git_index_entry *oitem = info->oitem;
631
+ const git_index_entry *nitem = info->nitem;
459
632
  unsigned int omode = oitem->mode;
460
633
  unsigned int nmode = nitem->mode;
461
- bool new_is_workdir = (new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
634
+ bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
462
635
  const char *matched_pathspec;
463
636
 
464
- GIT_UNUSED(old_iter);
465
-
466
637
  if (!git_pathspec_match_path(
467
638
  &diff->pathspec, oitem->path,
468
639
  DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
@@ -470,6 +641,8 @@ static int maybe_modified(
470
641
  &matched_pathspec))
471
642
  return 0;
472
643
 
644
+ memset(&noid, 0, sizeof(noid));
645
+
473
646
  /* on platforms with no symlinks, preserve mode of existing symlinks */
474
647
  if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
475
648
  !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
@@ -502,63 +675,40 @@ static int maybe_modified(
502
675
  }
503
676
  }
504
677
 
505
- /* if oids and modes match, then file is unmodified */
506
- else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode)
678
+ /* if oids and modes match (and are valid), then file is unmodified */
679
+ else if (git_oid_equal(&oitem->oid, &nitem->oid) &&
680
+ omode == nmode &&
681
+ !git_oid_iszero(&oitem->oid))
507
682
  status = GIT_DELTA_UNMODIFIED;
508
683
 
509
684
  /* if we have an unknown OID and a workdir iterator, then check some
510
685
  * circumstances that can accelerate things or need special handling
511
686
  */
512
687
  else if (git_oid_iszero(&nitem->oid) && new_is_workdir) {
513
- /* TODO: add check against index file st_mtime to avoid racy-git */
688
+ bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
689
+ bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0);
514
690
 
515
- /* if the stat data looks exactly alike, then assume the same */
516
- if (omode == nmode &&
517
- oitem->file_size == nitem->file_size &&
518
- (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
519
- (oitem->ctime.seconds == nitem->ctime.seconds)) &&
520
- oitem->mtime.seconds == nitem->mtime.seconds &&
521
- (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) ||
522
- (oitem->dev == nitem->dev)) &&
523
- oitem->ino == nitem->ino &&
524
- oitem->uid == nitem->uid &&
525
- oitem->gid == nitem->gid)
526
- status = GIT_DELTA_UNMODIFIED;
691
+ status = GIT_DELTA_UNMODIFIED;
527
692
 
528
- else if (S_ISGITLINK(nmode)) {
529
- int err;
530
- git_submodule *sub;
531
-
532
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
533
- status = GIT_DELTA_UNMODIFIED;
534
- else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) {
535
- if (err == GIT_EEXISTS)
536
- status = GIT_DELTA_UNMODIFIED;
537
- else
538
- return err;
539
- } else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
540
- status = GIT_DELTA_UNMODIFIED;
541
- else {
542
- unsigned int sm_status = 0;
543
- if (git_submodule_status(&sm_status, sub) < 0)
544
- return -1;
545
-
546
- /* check IS_WD_UNMODIFIED because this case is only used
547
- * when the new side of the diff is the working directory
548
- */
549
- status = GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)
550
- ? GIT_DELTA_UNMODIFIED : GIT_DELTA_MODIFIED;
551
-
552
- /* grab OID while we are here */
553
- if (git_oid_iszero(&nitem->oid)) {
554
- const git_oid *sm_oid = git_submodule_wd_id(sub);
555
- if (sm_oid != NULL) {
556
- git_oid_cpy(&noid, sm_oid);
557
- use_noid = &noid;
558
- }
559
- }
560
- }
693
+ /* TODO: add check against index file st_mtime to avoid racy-git */
694
+
695
+ if (S_ISGITLINK(nmode)) {
696
+ if (maybe_modified_submodule(&status, &noid, diff, info) < 0)
697
+ return -1;
561
698
  }
699
+
700
+ /* if the stat data looks different, then mark modified - this just
701
+ * means that the OID will be recalculated below to confirm change
702
+ */
703
+ else if (omode != nmode ||
704
+ oitem->file_size != nitem->file_size ||
705
+ !diff_time_eq(&oitem->mtime, &nitem->mtime, use_nanos) ||
706
+ (use_ctime &&
707
+ !diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) ||
708
+ oitem->ino != nitem->ino ||
709
+ oitem->uid != nitem->uid ||
710
+ oitem->gid != nitem->gid)
711
+ status = GIT_DELTA_MODIFIED;
562
712
  }
563
713
 
564
714
  /* if mode is GITLINK and submodules are ignored, then skip */
@@ -570,11 +720,10 @@ static int maybe_modified(
570
720
  * haven't calculated the OID of the new item, then calculate it now
571
721
  */
572
722
  if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) {
573
- if (!use_noid) {
723
+ if (git_oid_iszero(&noid)) {
574
724
  if (git_diff__oid_for_file(diff->repo,
575
725
  nitem->path, nitem->mode, nitem->file_size, &noid) < 0)
576
726
  return -1;
577
- use_noid = &noid;
578
727
  }
579
728
 
580
729
  /* if oid matches, then mark unmodified (except submodules, where
@@ -582,12 +731,13 @@ static int maybe_modified(
582
731
  * matches between the index and the workdir HEAD)
583
732
  */
584
733
  if (omode == nmode && !S_ISGITLINK(omode) &&
585
- git_oid_equal(&oitem->oid, use_noid))
734
+ git_oid_equal(&oitem->oid, &noid))
586
735
  status = GIT_DELTA_UNMODIFIED;
587
736
  }
588
737
 
589
738
  return diff_delta__from_two(
590
- diff, status, oitem, omode, nitem, nmode, use_noid, matched_pathspec);
739
+ diff, status, oitem, omode, nitem, nmode,
740
+ git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
591
741
  }
592
742
 
593
743
  static bool entry_is_prefixed(
@@ -607,237 +757,337 @@ static bool entry_is_prefixed(
607
757
  item->path[pathlen] == '/');
608
758
  }
609
759
 
610
- static int diff_list_init_from_iterators(
611
- git_diff_list *diff,
612
- git_iterator *old_iter,
613
- git_iterator *new_iter)
760
+ static int diff_scan_inside_untracked_dir(
761
+ git_diff_list *diff, diff_in_progress *info, git_delta_t *delta_type)
614
762
  {
615
- diff->old_src = old_iter->type;
616
- diff->new_src = new_iter->type;
763
+ int error = 0;
764
+ git_buf base = GIT_BUF_INIT;
765
+ bool is_ignored;
617
766
 
618
- /* Use case-insensitive compare if either iterator has
619
- * the ignore_case bit set */
620
- if (!git_iterator_ignore_case(old_iter) &&
621
- !git_iterator_ignore_case(new_iter))
622
- {
623
- diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;
767
+ *delta_type = GIT_DELTA_IGNORED;
768
+ git_buf_sets(&base, info->nitem->path);
624
769
 
625
- diff->strcomp = git__strcmp;
626
- diff->strncomp = git__strncmp;
627
- diff->pfxcomp = git__prefixcmp;
628
- diff->entrycomp = git_index_entry__cmp;
629
- } else {
630
- diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
770
+ /* advance into untracked directory */
771
+ if ((error = git_iterator_advance_into(&info->nitem, info->new_iter)) < 0) {
631
772
 
632
- diff->strcomp = git__strcasecmp;
633
- diff->strncomp = git__strncasecmp;
634
- diff->pfxcomp = git__prefixcmp_icase;
635
- diff->entrycomp = git_index_entry__cmp_icase;
773
+ /* skip ahead if empty */
774
+ if (error == GIT_ENOTFOUND) {
775
+ giterr_clear();
776
+ error = git_iterator_advance(&info->nitem, info->new_iter);
777
+ }
778
+
779
+ goto done;
636
780
  }
637
781
 
638
- return 0;
782
+ /* look for actual untracked file */
783
+ while (info->nitem != NULL &&
784
+ !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
785
+ is_ignored = git_iterator_current_is_ignored(info->new_iter);
786
+
787
+ /* need to recurse into non-ignored directories */
788
+ if (!is_ignored && S_ISDIR(info->nitem->mode)) {
789
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
790
+
791
+ if (!error)
792
+ continue;
793
+ else if (error == GIT_ENOTFOUND) {
794
+ error = 0;
795
+ is_ignored = true; /* treat empty as ignored */
796
+ } else
797
+ break; /* real error, must stop */
798
+ }
799
+
800
+ /* found a non-ignored item - treat parent dir as untracked */
801
+ if (!is_ignored) {
802
+ *delta_type = GIT_DELTA_UNTRACKED;
803
+ break;
804
+ }
805
+
806
+ if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
807
+ break;
808
+ }
809
+
810
+ /* finish off scan */
811
+ while (info->nitem != NULL &&
812
+ !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
813
+ if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
814
+ break;
815
+ }
816
+
817
+ done:
818
+ git_buf_free(&base);
819
+
820
+ if (error == GIT_ITEROVER)
821
+ error = 0;
822
+
823
+ return error;
639
824
  }
640
825
 
641
- int git_diff__from_iterators(
642
- git_diff_list **diff_ptr,
643
- git_repository *repo,
644
- git_iterator *old_iter,
645
- git_iterator *new_iter,
646
- const git_diff_options *opts)
826
+ static int handle_unmatched_new_item(
827
+ git_diff_list *diff, diff_in_progress *info)
647
828
  {
648
829
  int error = 0;
649
- const git_index_entry *oitem, *nitem;
650
- git_buf ignore_prefix = GIT_BUF_INIT;
651
- git_diff_list *diff = git_diff_list_alloc(repo, opts);
652
-
653
- *diff_ptr = NULL;
830
+ const git_index_entry *nitem = info->nitem;
831
+ git_delta_t delta_type = GIT_DELTA_UNTRACKED;
832
+ bool contains_oitem;
654
833
 
655
- if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
656
- goto fail;
834
+ /* check if this is a prefix of the other side */
835
+ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
657
836
 
658
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) {
659
- if (git_iterator_set_ignore_case(old_iter, true) < 0 ||
660
- git_iterator_set_ignore_case(new_iter, true) < 0)
661
- goto fail;
837
+ /* check if this is contained in an ignored parent directory */
838
+ if (git_buf_len(&info->ignore_prefix)) {
839
+ if (diff->pfxcomp(nitem->path, git_buf_cstr(&info->ignore_prefix)) == 0)
840
+ delta_type = GIT_DELTA_IGNORED;
841
+ else
842
+ git_buf_clear(&info->ignore_prefix);
662
843
  }
663
844
 
664
- if (git_iterator_current(&oitem, old_iter) < 0 ||
665
- git_iterator_current(&nitem, new_iter) < 0)
666
- goto fail;
845
+ if (S_ISDIR(nitem->mode)) {
846
+ bool recurse_into_dir = contains_oitem;
667
847
 
668
- /* run iterators building diffs */
669
- while (oitem || nitem) {
670
- int cmp = oitem ? (nitem ? diff->entrycomp(oitem, nitem) : -1) : 1;
848
+ /* if not already inside an ignored dir, check if this is ignored */
849
+ if (delta_type != GIT_DELTA_IGNORED &&
850
+ git_iterator_current_is_ignored(info->new_iter)) {
851
+ delta_type = GIT_DELTA_IGNORED;
852
+ git_buf_sets(&info->ignore_prefix, nitem->path);
853
+ }
671
854
 
672
- /* create DELETED records for old items not matched in new */
673
- if (cmp < 0) {
674
- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
675
- goto fail;
855
+ /* check if user requests recursion into this type of dir */
856
+ recurse_into_dir = contains_oitem ||
857
+ (delta_type == GIT_DELTA_UNTRACKED &&
858
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
859
+ (delta_type == GIT_DELTA_IGNORED &&
860
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
861
+
862
+ /* do not advance into directories that contain a .git file */
863
+ if (recurse_into_dir) {
864
+ git_buf *full = NULL;
865
+ if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
866
+ return -1;
867
+ if (full && git_path_contains_dir(full, DOT_GIT))
868
+ recurse_into_dir = false;
869
+ }
676
870
 
677
- /* if we are generating TYPECHANGE records then check for that
678
- * instead of just generating a DELETE record
679
- */
680
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
681
- entry_is_prefixed(diff, nitem, oitem))
682
- {
683
- /* this entry has become a tree! convert to TYPECHANGE */
684
- git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
685
- if (last) {
686
- last->status = GIT_DELTA_TYPECHANGE;
687
- last->new_file.mode = GIT_FILEMODE_TREE;
688
- }
871
+ /* still have to look into untracked directories to match core git -
872
+ * with no untracked files, directory is treated as ignored
873
+ */
874
+ if (!recurse_into_dir &&
875
+ delta_type == GIT_DELTA_UNTRACKED &&
876
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_FAST_UNTRACKED_DIRS))
877
+ {
878
+ git_diff_delta *last;
879
+
880
+ /* attempt to insert record for this directory */
881
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
882
+ return error;
883
+
884
+ /* if delta wasn't created (because of rules), just skip ahead */
885
+ last = diff_delta__last_for_item(diff, nitem);
886
+ if (!last)
887
+ return git_iterator_advance(&info->nitem, info->new_iter);
888
+
889
+ /* iterate into dir looking for an actual untracked file */
890
+ if (diff_scan_inside_untracked_dir(diff, info, &delta_type) < 0)
891
+ return -1;
892
+
893
+ /* it iteration changed delta type, the update the record */
894
+ if (delta_type == GIT_DELTA_IGNORED) {
895
+ last->status = GIT_DELTA_IGNORED;
689
896
 
690
- /* If new_iter is a workdir iterator, then this situation
691
- * will certainly be followed by a series of untracked items.
692
- * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
693
- */
694
- if (S_ISDIR(nitem->mode) &&
695
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
696
- {
697
- if (git_iterator_advance(&nitem, new_iter) < 0)
698
- goto fail;
897
+ /* remove the record if we don't want ignored records */
898
+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
899
+ git_vector_pop(&diff->deltas);
900
+ git__free(last);
699
901
  }
700
902
  }
701
903
 
702
- if (git_iterator_advance(&oitem, old_iter) < 0)
703
- goto fail;
904
+ return 0;
704
905
  }
705
906
 
706
- /* create ADDED, TRACKED, or IGNORED records for new items not
707
- * matched in old (and/or descend into directories as needed)
907
+ /* try to advance into directory if necessary */
908
+ if (recurse_into_dir) {
909
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
910
+
911
+ /* if real error or no error, proceed with iteration */
912
+ if (error != GIT_ENOTFOUND)
913
+ return error;
914
+ giterr_clear();
915
+
916
+ /* if directory is empty, can't advance into it, so either skip
917
+ * it or ignore it
918
+ */
919
+ if (contains_oitem)
920
+ return git_iterator_advance(&info->nitem, info->new_iter);
921
+ delta_type = GIT_DELTA_IGNORED;
922
+ }
923
+ }
924
+
925
+ /* In core git, the next two checks are effectively reversed --
926
+ * i.e. when an file contained in an ignored directory is explicitly
927
+ * ignored, it shows up as an ignored file in the diff list, even though
928
+ * other untracked files in the same directory are skipped completely.
929
+ *
930
+ * To me, this seems odd. If the directory is ignored and the file is
931
+ * untracked, we should skip it consistently, regardless of whether it
932
+ * happens to match a pattern in the ignore file.
933
+ *
934
+ * To match the core git behavior, reverse the following two if checks
935
+ * so that individual file ignores are checked before container
936
+ * directory exclusions are used to skip the file.
937
+ */
938
+ else if (delta_type == GIT_DELTA_IGNORED &&
939
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
940
+ /* item contained in ignored directory, so skip over it */
941
+ return git_iterator_advance(&info->nitem, info->new_iter);
942
+
943
+ else if (git_iterator_current_is_ignored(info->new_iter))
944
+ delta_type = GIT_DELTA_IGNORED;
945
+
946
+ else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
947
+ delta_type = GIT_DELTA_ADDED;
948
+
949
+ /* Actually create the record for this item if necessary */
950
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
951
+ return error;
952
+
953
+ /* If user requested TYPECHANGE records, then check for that instead of
954
+ * just generating an ADDED/UNTRACKED record
955
+ */
956
+ if (delta_type != GIT_DELTA_IGNORED &&
957
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
958
+ contains_oitem)
959
+ {
960
+ /* this entry was prefixed with a tree - make TYPECHANGE */
961
+ git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
962
+ if (last) {
963
+ last->status = GIT_DELTA_TYPECHANGE;
964
+ last->old_file.mode = GIT_FILEMODE_TREE;
965
+ }
966
+ }
967
+
968
+ return git_iterator_advance(&info->nitem, info->new_iter);
969
+ }
970
+
971
+ static int handle_unmatched_old_item(
972
+ git_diff_list *diff, diff_in_progress *info)
973
+ {
974
+ int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem);
975
+ if (error < 0)
976
+ return error;
977
+
978
+ /* if we are generating TYPECHANGE records then check for that
979
+ * instead of just generating a DELETE record
980
+ */
981
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
982
+ entry_is_prefixed(diff, info->nitem, info->oitem))
983
+ {
984
+ /* this entry has become a tree! convert to TYPECHANGE */
985
+ git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
986
+ if (last) {
987
+ last->status = GIT_DELTA_TYPECHANGE;
988
+ last->new_file.mode = GIT_FILEMODE_TREE;
989
+ }
990
+
991
+ /* If new_iter is a workdir iterator, then this situation
992
+ * will certainly be followed by a series of untracked items.
993
+ * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
708
994
  */
709
- else if (cmp > 0) {
710
- git_delta_t delta_type = GIT_DELTA_UNTRACKED;
711
- bool contains_oitem = entry_is_prefixed(diff, oitem, nitem);
712
-
713
- /* check if contained in ignored parent directory */
714
- if (git_buf_len(&ignore_prefix) &&
715
- diff->pfxcomp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
716
- delta_type = GIT_DELTA_IGNORED;
717
-
718
- if (S_ISDIR(nitem->mode)) {
719
- /* recurse into directory only if there are tracked items in
720
- * it or if the user requested the contents of untracked
721
- * directories and it is not under an ignored directory.
722
- */
723
- bool recurse_into_dir =
724
- (delta_type == GIT_DELTA_UNTRACKED &&
725
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
726
- (delta_type == GIT_DELTA_IGNORED &&
727
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
728
-
729
- /* do not advance into directories that contain a .git file */
730
- if (!contains_oitem && recurse_into_dir) {
731
- git_buf *full = NULL;
732
- if (git_iterator_current_workdir_path(&full, new_iter) < 0)
733
- goto fail;
734
- if (git_path_contains_dir(full, DOT_GIT))
735
- recurse_into_dir = false;
736
- }
995
+ if (S_ISDIR(info->nitem->mode) &&
996
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
997
+ return git_iterator_advance(&info->nitem, info->new_iter);
998
+ }
737
999
 
738
- /* if directory is ignored, remember ignore_prefix */
739
- if ((contains_oitem || recurse_into_dir) &&
740
- delta_type == GIT_DELTA_UNTRACKED &&
741
- git_iterator_current_is_ignored(new_iter))
742
- {
743
- git_buf_sets(&ignore_prefix, nitem->path);
744
- delta_type = GIT_DELTA_IGNORED;
745
-
746
- /* skip recursion if we've just learned this is ignored */
747
- if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
748
- recurse_into_dir = false;
749
- }
1000
+ return git_iterator_advance(&info->oitem, info->old_iter);
1001
+ }
750
1002
 
751
- if (contains_oitem || recurse_into_dir) {
752
- /* advance into directory */
753
- error = git_iterator_advance_into(&nitem, new_iter);
1003
+ static int handle_matched_item(
1004
+ git_diff_list *diff, diff_in_progress *info)
1005
+ {
1006
+ int error = 0;
754
1007
 
755
- /* if directory is empty, can't advance into it, so skip */
756
- if (error == GIT_ENOTFOUND) {
757
- giterr_clear();
758
- error = git_iterator_advance(&nitem, new_iter);
1008
+ if ((error = maybe_modified(diff, info)) < 0)
1009
+ return error;
759
1010
 
760
- git_buf_clear(&ignore_prefix);
761
- }
1011
+ if (!(error = git_iterator_advance(&info->oitem, info->old_iter)) ||
1012
+ error == GIT_ITEROVER)
1013
+ error = git_iterator_advance(&info->nitem, info->new_iter);
762
1014
 
763
- if (error < 0)
764
- goto fail;
765
- continue;
766
- }
767
- }
1015
+ return error;
1016
+ }
768
1017
 
769
- /* In core git, the next two "else if" clauses are effectively
770
- * reversed -- i.e. when an untracked file contained in an
771
- * ignored directory is individually ignored, it shows up as an
772
- * ignored file in the diff list, even though other untracked
773
- * files in the same directory are skipped completely.
774
- *
775
- * To me, this is odd. If the directory is ignored and the file
776
- * is untracked, we should skip it consistently, regardless of
777
- * whether it happens to match a pattern in the ignore file.
778
- *
779
- * To match the core git behavior, just reverse the following
780
- * two "else if" cases so that individual file ignores are
781
- * checked before container directory exclusions are used to
782
- * skip the file.
783
- */
784
- else if (delta_type == GIT_DELTA_IGNORED &&
785
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) {
786
- if (git_iterator_advance(&nitem, new_iter) < 0)
787
- goto fail;
788
- continue; /* ignored parent directory, so skip completely */
789
- }
1018
+ int git_diff__from_iterators(
1019
+ git_diff_list **diff_ptr,
1020
+ git_repository *repo,
1021
+ git_iterator *old_iter,
1022
+ git_iterator *new_iter,
1023
+ const git_diff_options *opts)
1024
+ {
1025
+ int error = 0;
1026
+ diff_in_progress info;
1027
+ git_diff_list *diff;
790
1028
 
791
- else if (git_iterator_current_is_ignored(new_iter))
792
- delta_type = GIT_DELTA_IGNORED;
1029
+ *diff_ptr = NULL;
793
1030
 
794
- else if (new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
795
- delta_type = GIT_DELTA_ADDED;
1031
+ diff = diff_list_alloc(repo, old_iter, new_iter);
1032
+ GITERR_CHECK_ALLOC(diff);
796
1033
 
797
- if (diff_delta__from_one(diff, delta_type, nitem) < 0)
798
- goto fail;
1034
+ info.repo = repo;
1035
+ info.old_iter = old_iter;
1036
+ info.new_iter = new_iter;
1037
+ git_buf_init(&info.ignore_prefix, 0);
799
1038
 
800
- /* if we are generating TYPECHANGE records then check for that
801
- * instead of just generating an ADDED/UNTRACKED record
802
- */
803
- if (delta_type != GIT_DELTA_IGNORED &&
804
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
805
- contains_oitem)
806
- {
807
- /* this entry was prefixed with a tree - make TYPECHANGE */
808
- git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
809
- if (last) {
810
- last->status = GIT_DELTA_TYPECHANGE;
811
- last->old_file.mode = GIT_FILEMODE_TREE;
812
- }
813
- }
1039
+ /* make iterators have matching icase behavior */
1040
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) {
1041
+ if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 ||
1042
+ (error = git_iterator_set_ignore_case(new_iter, true)) < 0)
1043
+ goto cleanup;
1044
+ }
814
1045
 
815
- if (git_iterator_advance(&nitem, new_iter) < 0)
816
- goto fail;
817
- }
1046
+ /* finish initialization */
1047
+ if ((error = diff_list_apply_options(diff, opts)) < 0)
1048
+ goto cleanup;
1049
+
1050
+ if ((error = git_iterator_current(&info.oitem, old_iter)) < 0 &&
1051
+ error != GIT_ITEROVER)
1052
+ goto cleanup;
1053
+ if ((error = git_iterator_current(&info.nitem, new_iter)) < 0 &&
1054
+ error != GIT_ITEROVER)
1055
+ goto cleanup;
1056
+ error = 0;
1057
+
1058
+ /* run iterators building diffs */
1059
+ while (!error && (info.oitem || info.nitem)) {
1060
+ int cmp = info.oitem ?
1061
+ (info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
1062
+
1063
+ /* create DELETED records for old items not matched in new */
1064
+ if (cmp < 0)
1065
+ error = handle_unmatched_old_item(diff, &info);
1066
+
1067
+ /* create ADDED, TRACKED, or IGNORED records for new items not
1068
+ * matched in old (and/or descend into directories as needed)
1069
+ */
1070
+ else if (cmp > 0)
1071
+ error = handle_unmatched_new_item(diff, &info);
818
1072
 
819
1073
  /* otherwise item paths match, so create MODIFIED record
820
1074
  * (or ADDED and DELETED pair if type changed)
821
1075
  */
822
- else {
823
- assert(oitem && nitem && cmp == 0);
1076
+ else
1077
+ error = handle_matched_item(diff, &info);
824
1078
 
825
- if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
826
- git_iterator_advance(&oitem, old_iter) < 0 ||
827
- git_iterator_advance(&nitem, new_iter) < 0)
828
- goto fail;
829
- }
1079
+ /* because we are iterating over two lists, ignore ITEROVER */
1080
+ if (error == GIT_ITEROVER)
1081
+ error = 0;
830
1082
  }
831
1083
 
832
- *diff_ptr = diff;
833
-
834
- fail:
835
- if (!*diff_ptr) {
1084
+ cleanup:
1085
+ if (!error)
1086
+ *diff_ptr = diff;
1087
+ else
836
1088
  git_diff_list_free(diff);
837
- error = -1;
838
- }
839
1089
 
840
- git_buf_free(&ignore_prefix);
1090
+ git_buf_free(&info.ignore_prefix);
841
1091
 
842
1092
  return error;
843
1093
  }
@@ -859,12 +1109,20 @@ int git_diff_tree_to_tree(
859
1109
  const git_diff_options *opts)
860
1110
  {
861
1111
  int error = 0;
1112
+ git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
862
1113
 
863
1114
  assert(diff && repo);
864
1115
 
1116
+ /* for tree to tree diff, be case sensitive even if the index is
1117
+ * currently case insensitive, unless the user explicitly asked
1118
+ * for case insensitivity
1119
+ */
1120
+ if (opts && (opts->flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)
1121
+ iflag = GIT_ITERATOR_IGNORE_CASE;
1122
+
865
1123
  DIFF_FROM_ITERATORS(
866
- git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
867
- git_iterator_for_tree(&b, new_tree, 0, pfx, pfx)
1124
+ git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx),
1125
+ git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx)
868
1126
  );
869
1127
 
870
1128
  return error;
@@ -878,17 +1136,40 @@ int git_diff_tree_to_index(
878
1136
  const git_diff_options *opts)
879
1137
  {
880
1138
  int error = 0;
1139
+ bool reset_index_ignore_case = false;
881
1140
 
882
1141
  assert(diff && repo);
883
1142
 
884
1143
  if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
885
1144
  return error;
886
1145
 
1146
+ if (index->ignore_case) {
1147
+ git_index__set_ignore_case(index, false);
1148
+ reset_index_ignore_case = true;
1149
+ }
1150
+
887
1151
  DIFF_FROM_ITERATORS(
888
1152
  git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
889
1153
  git_iterator_for_index(&b, index, 0, pfx, pfx)
890
1154
  );
891
1155
 
1156
+ if (reset_index_ignore_case) {
1157
+ git_index__set_ignore_case(index, true);
1158
+
1159
+ if (!error) {
1160
+ git_diff_list *d = *diff;
1161
+
1162
+ d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
1163
+ d->strcomp = git__strcasecmp;
1164
+ d->strncomp = git__strncasecmp;
1165
+ d->pfxcomp = git__prefixcmp_icase;
1166
+ d->entrycomp = git_index_entry__cmp_icase;
1167
+
1168
+ git_vector_set_cmp(&d->deltas, git_diff_delta__casecmp);
1169
+ git_vector_sort(&d->deltas);
1170
+ }
1171
+ }
1172
+
892
1173
  return error;
893
1174
  }
894
1175
 
@@ -933,3 +1214,100 @@ int git_diff_tree_to_workdir(
933
1214
 
934
1215
  return error;
935
1216
  }
1217
+
1218
+ size_t git_diff_num_deltas(git_diff_list *diff)
1219
+ {
1220
+ assert(diff);
1221
+ return (size_t)diff->deltas.length;
1222
+ }
1223
+
1224
+ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
1225
+ {
1226
+ size_t i, count = 0;
1227
+ git_diff_delta *delta;
1228
+
1229
+ assert(diff);
1230
+
1231
+ git_vector_foreach(&diff->deltas, i, delta) {
1232
+ count += (delta->status == type);
1233
+ }
1234
+
1235
+ return count;
1236
+ }
1237
+
1238
+ int git_diff__paired_foreach(
1239
+ git_diff_list *head2idx,
1240
+ git_diff_list *idx2wd,
1241
+ int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
1242
+ void *payload)
1243
+ {
1244
+ int cmp;
1245
+ git_diff_delta *h2i, *i2w;
1246
+ size_t i, j, i_max, j_max;
1247
+ int (*strcomp)(const char *, const char *) = git__strcmp;
1248
+ bool icase_mismatch;
1249
+
1250
+ i_max = head2idx ? head2idx->deltas.length : 0;
1251
+ j_max = idx2wd ? idx2wd->deltas.length : 0;
1252
+
1253
+ /* At some point, tree-to-index diffs will probably never ignore case,
1254
+ * even if that isn't true now. Index-to-workdir diffs may or may not
1255
+ * ignore case, but the index filename for the idx2wd diff should
1256
+ * still be using the canonical case-preserving name.
1257
+ *
1258
+ * Therefore the main thing we need to do here is make sure the diffs
1259
+ * are traversed in a compatible order. To do this, we temporarily
1260
+ * resort a mismatched diff to get the order correct.
1261
+ */
1262
+ icase_mismatch =
1263
+ (head2idx != NULL && idx2wd != NULL &&
1264
+ ((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE));
1265
+
1266
+ /* force case-sensitive delta sort */
1267
+ if (icase_mismatch) {
1268
+ if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
1269
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
1270
+ git_vector_sort(&head2idx->deltas);
1271
+ } else {
1272
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp);
1273
+ git_vector_sort(&idx2wd->deltas);
1274
+ }
1275
+ }
1276
+ else if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)
1277
+ strcomp = git__strcasecmp;
1278
+
1279
+ for (i = 0, j = 0; i < i_max || j < j_max; ) {
1280
+ h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
1281
+ i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
1282
+
1283
+ cmp = !i2w ? -1 : !h2i ? 1 :
1284
+ strcomp(h2i->new_file.path, i2w->old_file.path);
1285
+
1286
+ if (cmp < 0) {
1287
+ if (cb(h2i, NULL, payload))
1288
+ return GIT_EUSER;
1289
+ i++;
1290
+ } else if (cmp > 0) {
1291
+ if (cb(NULL, i2w, payload))
1292
+ return GIT_EUSER;
1293
+ j++;
1294
+ } else {
1295
+ if (cb(h2i, i2w, payload))
1296
+ return GIT_EUSER;
1297
+ i++; j++;
1298
+ }
1299
+ }
1300
+
1301
+ /* restore case-insensitive delta sort */
1302
+ if (icase_mismatch) {
1303
+ if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
1304
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
1305
+ git_vector_sort(&head2idx->deltas);
1306
+ } else {
1307
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp);
1308
+ git_vector_sort(&idx2wd->deltas);
1309
+ }
1310
+ }
1311
+
1312
+ return 0;
1313
+ }