commonmarker 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of commonmarker might be problematic. Click here for more details.

Files changed (501) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -2
  3. data/README.md +67 -42
  4. data/Rakefile +22 -2
  5. data/commonmarker.gemspec +13 -9
  6. data/ext/commonmarker/cmark/api_test/main.c +35 -0
  7. data/ext/commonmarker/cmark/build/CMakeFiles/CMakeError.log +12 -12
  8. data/ext/commonmarker/cmark/build/CMakeFiles/CMakeOutput.log +141 -141
  9. data/ext/commonmarker/cmark/build/api_test/CMakeFiles/api_test.dir/main.c.o +0 -0
  10. data/ext/commonmarker/cmark/build/api_test/api_test +0 -0
  11. data/ext/commonmarker/cmark/build/src/CMakeFiles/cmark.dir/houdini_html_u.c.o +0 -0
  12. data/ext/commonmarker/cmark/build/src/CMakeFiles/cmark.dir/iterator.c.o +0 -0
  13. data/ext/commonmarker/cmark/build/src/CMakeFiles/libcmark.dir/houdini_html_u.c.o +0 -0
  14. data/ext/commonmarker/cmark/build/src/CMakeFiles/libcmark.dir/iterator.c.o +0 -0
  15. data/ext/commonmarker/cmark/build/src/CMakeFiles/libcmark_static.dir/houdini_html_u.c.o +0 -0
  16. data/ext/commonmarker/cmark/build/src/CMakeFiles/libcmark_static.dir/iterator.c.o +0 -0
  17. data/ext/commonmarker/cmark/build/src/cmark +0 -0
  18. data/ext/commonmarker/cmark/build/src/libcmark.0.19.0.dylib +0 -0
  19. data/ext/commonmarker/cmark/build/src/libcmark.a +0 -0
  20. data/ext/commonmarker/cmark/build/src/libcmark.dylib +0 -0
  21. data/ext/commonmarker/cmark/src/houdini_html_u.c +26 -13
  22. data/ext/commonmarker/cmark/src/iterator.c +2 -2
  23. data/ext/commonmarker/cmark/test/__pycache__/cmark.cpython-34.pyc +0 -0
  24. data/ext/commonmarker/cmark/test/__pycache__/normalize.cpython-34.pyc +0 -0
  25. data/ext/commonmarker/cmark/test/cmark.pyc +0 -0
  26. data/ext/commonmarker/cmark/test/normalize.pyc +0 -0
  27. data/ext/commonmarker/commonmarker.c +276 -3
  28. data/ext/commonmarker/extconf.rb +3 -1
  29. data/lib/commonmarker.rb +70 -360
  30. data/lib/commonmarker/config.rb +1 -1
  31. data/lib/commonmarker/renderer.rb +91 -0
  32. data/lib/commonmarker/renderer/html_renderer.rb +149 -0
  33. data/lib/commonmarker/version.rb +1 -1
  34. data/test/benchinput.md +148414 -0
  35. data/test/benchmark.rb +13 -9
  36. data/test/progit/Gemfile +5 -0
  37. data/test/progit/README.md +9 -0
  38. data/test/progit/README.original.md +70 -0
  39. data/test/progit/Rakefile +285 -0
  40. data/test/progit/ar/01-introduction/01-chapter1.markdown +264 -0
  41. data/test/progit/ar/02-git-basics/01-chapter2.markdown +1124 -0
  42. data/test/progit/ar/NOTES +18 -0
  43. data/test/progit/ar/README +14 -0
  44. data/test/progit/az/01-introduction/01-chapter1.markdown +257 -0
  45. data/test/progit/az/02-git-basics/01-chapter2.markdown +1127 -0
  46. data/test/progit/az/03-git-branching/01-chapter3.markdown +598 -0
  47. data/test/progit/az/04-git-server/01-chapter4.markdown +861 -0
  48. data/test/progit/az/05-distributed-git/01-chapter5.markdown +897 -0
  49. data/test/progit/az/06-git-tools/01-chapter6.markdown +1126 -0
  50. data/test/progit/az/07-customizing-git/01-chapter7.markdown +937 -0
  51. data/test/progit/az/08-git-and-other-scms/01-chapter8.markdown +690 -0
  52. data/test/progit/az/09-git-internals/01-chapter9.markdown +977 -0
  53. data/test/progit/be/01-introduction/01-chapter1.markdown +257 -0
  54. data/test/progit/be/02-git-basics/01-chapter2.markdown +1126 -0
  55. data/test/progit/ca/01-introduction/01-chapter1.markdown +257 -0
  56. data/test/progit/ca/README.txt +1 -0
  57. data/test/progit/couchapp/Makefile +41 -0
  58. data/test/progit/couchapp/Readme.md +17 -0
  59. data/test/progit/couchapp/_id +1 -0
  60. data/test/progit/couchapp/shows/chapter.js +14 -0
  61. data/test/progit/couchapp/templates/foot.html +7 -0
  62. data/test/progit/couchapp/templates/head.html +51 -0
  63. data/test/progit/couchapp/vendor/markdown/showdown.js +420 -0
  64. data/test/progit/couchapp/vendor/mustache.js/mustache.js +302 -0
  65. data/test/progit/cs/01-introduction/01-chapter1.markdown +259 -0
  66. data/test/progit/cs/02-git-basics/01-chapter2.markdown +1225 -0
  67. data/test/progit/cs/03-git-branching/01-chapter3.markdown +606 -0
  68. data/test/progit/cs/04-git-server/01-chapter4.markdown +871 -0
  69. data/test/progit/cs/05-distributed-git/01-chapter5.markdown +914 -0
  70. data/test/progit/cs/06-git-tools/01-chapter6.markdown +1167 -0
  71. data/test/progit/cs/07-customizing-git/01-chapter7.markdown +940 -0
  72. data/test/progit/cs/08-git-and-other-scms/01-chapter8.markdown +700 -0
  73. data/test/progit/cs/09-git-internals/01-chapter9.markdown +1014 -0
  74. data/test/progit/de/01-introduction/01-chapter1.markdown +445 -0
  75. data/test/progit/de/02-git-basics/01-chapter2.markdown +1589 -0
  76. data/test/progit/de/03-git-branching/01-chapter3.markdown +964 -0
  77. data/test/progit/de/04-git-server/01-chapter4.markdown +1337 -0
  78. data/test/progit/de/05-distributed-git/01-chapter5.markdown +1329 -0
  79. data/test/progit/de/06-git-tools/01-chapter6.markdown +1502 -0
  80. data/test/progit/de/07-customizing-git/01-chapter7.markdown +1361 -0
  81. data/test/progit/de/08-git-and-other-scms/01-chapter8.markdown +919 -0
  82. data/test/progit/de/09-git-internals/01-chapter9.markdown +1361 -0
  83. data/test/progit/de/README.md +626 -0
  84. data/test/progit/ebooks/cover.png +0 -0
  85. data/test/progit/en/01-introduction/01-chapter1.markdown +263 -0
  86. data/test/progit/en/02-git-basics/01-chapter2.markdown +1228 -0
  87. data/test/progit/en/03-git-branching/01-chapter3.markdown +606 -0
  88. data/test/progit/en/04-git-server/01-chapter4.markdown +871 -0
  89. data/test/progit/en/05-distributed-git/01-chapter5.markdown +914 -0
  90. data/test/progit/en/06-git-tools/01-chapter6.markdown +1150 -0
  91. data/test/progit/en/07-customizing-git/01-chapter7.markdown +940 -0
  92. data/test/progit/en/08-git-and-other-scms/01-chapter8.markdown +700 -0
  93. data/test/progit/en/09-git-internals/01-chapter9.markdown +983 -0
  94. data/test/progit/eo/01-introduction/01-chapter1.markdown +257 -0
  95. data/test/progit/eo/02-git-basics/01-chapter2.markdown +1171 -0
  96. data/test/progit/epub/ProGit.css +28 -0
  97. data/test/progit/epub/title.png +0 -0
  98. data/test/progit/es-ni/01-introduction/01-chapter1.markdown +257 -0
  99. data/test/progit/es-ni/02-git-basics/01-chapter2.markdown +1127 -0
  100. data/test/progit/es/01-introduction/01-chapter1.markdown +262 -0
  101. data/test/progit/es/02-git-basics/01-chapter2.markdown +1165 -0
  102. data/test/progit/es/03-git-branching/01-chapter3.markdown +598 -0
  103. data/test/progit/es/04-git-server/01-chapter4.markdown +707 -0
  104. data/test/progit/es/05-distributed-git/01-chapter5.markdown +890 -0
  105. data/test/progit/es/06-git-tools/01-chapter6.markdown +1113 -0
  106. data/test/progit/es/07-customizing-git/01-chapter7.markdown +875 -0
  107. data/test/progit/es/08-git-and-other-scms/01-chapter8.markdown +686 -0
  108. data/test/progit/es/09-git-internals/01-chapter9.markdown +976 -0
  109. data/test/progit/es/NOTES +29 -0
  110. data/test/progit/es/README +3 -0
  111. data/test/progit/es/glosario-Benzirpi.txt +27 -0
  112. data/test/progit/es/omegat-Benzirpi.tmx +29075 -0
  113. data/test/progit/fa/01-introduction/01-chapter1.markdown +262 -0
  114. data/test/progit/fa/03-git-branching/01-chapter3.markdown +608 -0
  115. data/test/progit/fa/04-git-server/01-chapter4.markdown +872 -0
  116. data/test/progit/fa/NOTES.en-fa.md +143 -0
  117. data/test/progit/fa/README.md +7 -0
  118. data/test/progit/fi/01-introduction/01-chapter1.markdown +259 -0
  119. data/test/progit/fi/02-git-basics/01-chapter2.markdown +1171 -0
  120. data/test/progit/fi/NOTES +5 -0
  121. data/test/progit/figures-dia/fig0101.dia +617 -0
  122. data/test/progit/figures-dia/fig0102.dia +921 -0
  123. data/test/progit/figures-dia/fig0103.dia +1468 -0
  124. data/test/progit/figures-dia/fig0104.dia +1432 -0
  125. data/test/progit/figures-dia/fig0105.dia +1924 -0
  126. data/test/progit/figures-dia/fig0106.dia +562 -0
  127. data/test/progit/figures-dia/fig0201.dia +774 -0
  128. data/test/progit/figures-dia/fig0301.dia +2006 -0
  129. data/test/progit/figures-dia/fig0302.dia +2148 -0
  130. data/test/progit/figures-dia/fig0303.dia +719 -0
  131. data/test/progit/figures-dia/fig0304.dia +525 -0
  132. data/test/progit/figures-dia/fig0305.dia +622 -0
  133. data/test/progit/figures-dia/fig0306.dia +622 -0
  134. data/test/progit/figures-dia/fig0307.dia +719 -0
  135. data/test/progit/figures-dia/fig0308.dia +734 -0
  136. data/test/progit/figures-dia/fig0309.dia +831 -0
  137. data/test/progit/figures-dia/fig0310.dia +412 -0
  138. data/test/progit/figures-dia/fig0311.dia +493 -0
  139. data/test/progit/figures-dia/fig0312.dia +596 -0
  140. data/test/progit/figures-dia/fig0313.dia +774 -0
  141. data/test/progit/figures-dia/fig0314.dia +846 -0
  142. data/test/progit/figures-dia/fig0315.dia +787 -0
  143. data/test/progit/figures-dia/fig0316.dia +1078 -0
  144. data/test/progit/figures-dia/fig0317.dia +881 -0
  145. data/test/progit/figures-dia/fig0318.dia +968 -0
  146. data/test/progit/figures-dia/fig0319.dia +957 -0
  147. data/test/progit/figures-dia/fig0320.dia +1637 -0
  148. data/test/progit/figures-dia/fig0321.dia +1494 -0
  149. data/test/progit/figures-dia/fig0322.dia +1142 -0
  150. data/test/progit/figures-dia/fig0323.dia +1377 -0
  151. data/test/progit/figures-dia/fig0324.dia +1603 -0
  152. data/test/progit/figures-dia/fig0325.dia +2003 -0
  153. data/test/progit/figures-dia/fig0326.dia +2013 -0
  154. data/test/progit/figures-dia/fig0327.dia +687 -0
  155. data/test/progit/figures-dia/fig0328.dia +814 -0
  156. data/test/progit/figures-dia/fig0329.dia +793 -0
  157. data/test/progit/figures-dia/fig0330.dia +693 -0
  158. data/test/progit/figures-dia/fig0331.dia +1159 -0
  159. data/test/progit/figures-dia/fig0332.dia +1362 -0
  160. data/test/progit/figures-dia/fig0333.dia +1165 -0
  161. data/test/progit/figures-dia/fig0334.dia +1450 -0
  162. data/test/progit/figures-dia/fig0335.dia +994 -0
  163. data/test/progit/figures-dia/fig0336.dia +786 -0
  164. data/test/progit/figures-dia/fig0337.dia +1546 -0
  165. data/test/progit/figures-dia/fig0338.dia +1755 -0
  166. data/test/progit/figures-dia/fig0339.dia +1882 -0
  167. data/test/progit/figures-dia/fig0501.dia +456 -0
  168. data/test/progit/figures-dia/fig0502.dia +956 -0
  169. data/test/progit/figures-dia/fig0503.dia +915 -0
  170. data/test/progit/figures-dia/fig0504.dia +620 -0
  171. data/test/progit/figures-dia/fig0505.dia +744 -0
  172. data/test/progit/figures-dia/fig0506.dia +747 -0
  173. data/test/progit/figures-dia/fig0507.dia +895 -0
  174. data/test/progit/figures-dia/fig0508.dia +1122 -0
  175. data/test/progit/figures-dia/fig0509.dia +1243 -0
  176. data/test/progit/figures-dia/fig0510.dia +1240 -0
  177. data/test/progit/figures-dia/fig0511.dia +1201 -0
  178. data/test/progit/figures-dia/fig0512.dia +801 -0
  179. data/test/progit/figures-dia/fig0513.dia +1387 -0
  180. data/test/progit/figures-dia/fig0514.dia +1568 -0
  181. data/test/progit/figures-dia/fig0515.dia +1721 -0
  182. data/test/progit/figures-dia/fig0516.dia +997 -0
  183. data/test/progit/figures-dia/fig0517.dia +994 -0
  184. data/test/progit/figures-dia/fig0518.dia +1145 -0
  185. data/test/progit/figures-dia/fig0519.dia +992 -0
  186. data/test/progit/figures-dia/fig0520.dia +1240 -0
  187. data/test/progit/figures-dia/fig0521.dia +801 -0
  188. data/test/progit/figures-dia/fig0522.dia +922 -0
  189. data/test/progit/figures-dia/fig0523.dia +922 -0
  190. data/test/progit/figures-dia/fig0524.dia +1828 -0
  191. data/test/progit/figures-dia/fig0525.dia +2685 -0
  192. data/test/progit/figures-dia/fig0526.dia +717 -0
  193. data/test/progit/figures-dia/fig0527.dia +856 -0
  194. data/test/progit/figures-dia/fig0601.dia +790 -0
  195. data/test/progit/figures-dia/fig0702.dia +795 -0
  196. data/test/progit/figures-dia/fig0703.dia +795 -0
  197. data/test/progit/figures-dia/fig0901.dia +669 -0
  198. data/test/progit/figures-dia/fig0902.dia +834 -0
  199. data/test/progit/figures-dia/fig0903.dia +1483 -0
  200. data/test/progit/figures-dia/fig0904.dia +1728 -0
  201. data/test/progit/figures-dia/makeimages +25 -0
  202. data/test/progit/figures-source/progit.graffle +123108 -0
  203. data/test/progit/figures/18333fig0101-tn.png +0 -0
  204. data/test/progit/figures/18333fig0102-tn.png +0 -0
  205. data/test/progit/figures/18333fig0103-tn.png +0 -0
  206. data/test/progit/figures/18333fig0104-tn.png +0 -0
  207. data/test/progit/figures/18333fig0105-tn.png +0 -0
  208. data/test/progit/figures/18333fig0106-tn.png +0 -0
  209. data/test/progit/figures/18333fig0107-tn.png +0 -0
  210. data/test/progit/figures/18333fig0201-tn.png +0 -0
  211. data/test/progit/figures/18333fig0202-tn.png +0 -0
  212. data/test/progit/figures/18333fig0301-tn.png +0 -0
  213. data/test/progit/figures/18333fig0302-tn.png +0 -0
  214. data/test/progit/figures/18333fig0303-tn.png +0 -0
  215. data/test/progit/figures/18333fig0304-tn.png +0 -0
  216. data/test/progit/figures/18333fig0305-tn.png +0 -0
  217. data/test/progit/figures/18333fig0306-tn.png +0 -0
  218. data/test/progit/figures/18333fig0307-tn.png +0 -0
  219. data/test/progit/figures/18333fig0308-tn.png +0 -0
  220. data/test/progit/figures/18333fig0309-tn.png +0 -0
  221. data/test/progit/figures/18333fig0310-tn.png +0 -0
  222. data/test/progit/figures/18333fig0311-tn.png +0 -0
  223. data/test/progit/figures/18333fig0312-tn.png +0 -0
  224. data/test/progit/figures/18333fig0313-tn.png +0 -0
  225. data/test/progit/figures/18333fig0314-tn.png +0 -0
  226. data/test/progit/figures/18333fig0315-tn.png +0 -0
  227. data/test/progit/figures/18333fig0316-tn.png +0 -0
  228. data/test/progit/figures/18333fig0317-tn.png +0 -0
  229. data/test/progit/figures/18333fig0318-tn.png +0 -0
  230. data/test/progit/figures/18333fig0319-tn.png +0 -0
  231. data/test/progit/figures/18333fig0320-tn.png +0 -0
  232. data/test/progit/figures/18333fig0321-tn.png +0 -0
  233. data/test/progit/figures/18333fig0322-tn.png +0 -0
  234. data/test/progit/figures/18333fig0323-tn.png +0 -0
  235. data/test/progit/figures/18333fig0324-tn.png +0 -0
  236. data/test/progit/figures/18333fig0325-tn.png +0 -0
  237. data/test/progit/figures/18333fig0326-tn.png +0 -0
  238. data/test/progit/figures/18333fig0327-tn.png +0 -0
  239. data/test/progit/figures/18333fig0328-tn.png +0 -0
  240. data/test/progit/figures/18333fig0329-tn.png +0 -0
  241. data/test/progit/figures/18333fig0330-tn.png +0 -0
  242. data/test/progit/figures/18333fig0331-tn.png +0 -0
  243. data/test/progit/figures/18333fig0332-tn.png +0 -0
  244. data/test/progit/figures/18333fig0333-tn.png +0 -0
  245. data/test/progit/figures/18333fig0334-tn.png +0 -0
  246. data/test/progit/figures/18333fig0335-tn.png +0 -0
  247. data/test/progit/figures/18333fig0336-tn.png +0 -0
  248. data/test/progit/figures/18333fig0337-tn.png +0 -0
  249. data/test/progit/figures/18333fig0338-tn.png +0 -0
  250. data/test/progit/figures/18333fig0339-tn.png +0 -0
  251. data/test/progit/figures/18333fig0401-tn.png +0 -0
  252. data/test/progit/figures/18333fig0402-tn.png +0 -0
  253. data/test/progit/figures/18333fig0403-tn.png +0 -0
  254. data/test/progit/figures/18333fig0404-tn.png +0 -0
  255. data/test/progit/figures/18333fig0405-tn.png +0 -0
  256. data/test/progit/figures/18333fig0406-tn.png +0 -0
  257. data/test/progit/figures/18333fig0407-tn.png +0 -0
  258. data/test/progit/figures/18333fig0408-tn.png +0 -0
  259. data/test/progit/figures/18333fig0409-tn.png +0 -0
  260. data/test/progit/figures/18333fig0410-tn.png +0 -0
  261. data/test/progit/figures/18333fig0411-tn.png +0 -0
  262. data/test/progit/figures/18333fig0412-tn.png +0 -0
  263. data/test/progit/figures/18333fig0413-tn.png +0 -0
  264. data/test/progit/figures/18333fig0414-tn.png +0 -0
  265. data/test/progit/figures/18333fig0415-tn.png +0 -0
  266. data/test/progit/figures/18333fig0501-tn.png +0 -0
  267. data/test/progit/figures/18333fig0502-tn.png +0 -0
  268. data/test/progit/figures/18333fig0503-tn.png +0 -0
  269. data/test/progit/figures/18333fig0504-tn.png +0 -0
  270. data/test/progit/figures/18333fig0505-tn.png +0 -0
  271. data/test/progit/figures/18333fig0506-tn.png +0 -0
  272. data/test/progit/figures/18333fig0507-tn.png +0 -0
  273. data/test/progit/figures/18333fig0508-tn.png +0 -0
  274. data/test/progit/figures/18333fig0509-tn.png +0 -0
  275. data/test/progit/figures/18333fig0510-tn.png +0 -0
  276. data/test/progit/figures/18333fig0511-tn.png +0 -0
  277. data/test/progit/figures/18333fig0512-tn.png +0 -0
  278. data/test/progit/figures/18333fig0513-tn.png +0 -0
  279. data/test/progit/figures/18333fig0514-tn.png +0 -0
  280. data/test/progit/figures/18333fig0515-tn.png +0 -0
  281. data/test/progit/figures/18333fig0516-tn.png +0 -0
  282. data/test/progit/figures/18333fig0517-tn.png +0 -0
  283. data/test/progit/figures/18333fig0518-tn.png +0 -0
  284. data/test/progit/figures/18333fig0519-tn.png +0 -0
  285. data/test/progit/figures/18333fig0520-tn.png +0 -0
  286. data/test/progit/figures/18333fig0521-tn.png +0 -0
  287. data/test/progit/figures/18333fig0522-tn.png +0 -0
  288. data/test/progit/figures/18333fig0523-tn.png +0 -0
  289. data/test/progit/figures/18333fig0524-tn.png +0 -0
  290. data/test/progit/figures/18333fig0525-tn.png +0 -0
  291. data/test/progit/figures/18333fig0526-tn.png +0 -0
  292. data/test/progit/figures/18333fig0527-tn.png +0 -0
  293. data/test/progit/figures/18333fig0601-tn.png +0 -0
  294. data/test/progit/figures/18333fig0701-tn.png +0 -0
  295. data/test/progit/figures/18333fig0702-tn.png +0 -0
  296. data/test/progit/figures/18333fig0703-tn.png +0 -0
  297. data/test/progit/figures/18333fig0901-tn.png +0 -0
  298. data/test/progit/figures/18333fig0902-tn.png +0 -0
  299. data/test/progit/figures/18333fig0903-tn.png +0 -0
  300. data/test/progit/figures/18333fig0904-tn.png +0 -0
  301. data/test/progit/fr/01-introduction/01-chapter1.markdown +371 -0
  302. data/test/progit/fr/02-git-basics/01-chapter2.markdown +1378 -0
  303. data/test/progit/fr/03-git-branching/01-chapter3.markdown +781 -0
  304. data/test/progit/fr/04-git-server/01-chapter4.markdown +1141 -0
  305. data/test/progit/fr/05-distributed-git/01-chapter5.markdown +1163 -0
  306. data/test/progit/fr/06-git-tools/01-chapter6.markdown +1356 -0
  307. data/test/progit/fr/07-customizing-git/01-chapter7.markdown +1200 -0
  308. data/test/progit/fr/08-git-and-other-scms/01-chapter8.markdown +832 -0
  309. data/test/progit/fr/09-git-internals/01-chapter9.markdown +1228 -0
  310. data/test/progit/fr/NOTES.fr-fr.markdown +1 -0
  311. data/test/progit/fr/NOTES.fr-fr.md +127 -0
  312. data/test/progit/fr/README.md +43 -0
  313. data/test/progit/fr/glossaire-git.adoc +108 -0
  314. data/test/progit/hi/01-introduction/01-chapter1.markdown +7 -0
  315. data/test/progit/hu/01-introduction/01-chapter1.markdown +257 -0
  316. data/test/progit/id/01-introduction/01-chapter1.markdown +257 -0
  317. data/test/progit/id/02-git-basics/01-chapter2.markdown +1127 -0
  318. data/test/progit/id/03-git-branching/01-chapter3.markdown +598 -0
  319. data/test/progit/it/01-introduction/01-chapter1.markdown +263 -0
  320. data/test/progit/it/02-git-basics/01-chapter2.markdown +1227 -0
  321. data/test/progit/it/03-git-branching/01-chapter3.markdown +598 -0
  322. data/test/progit/it/04-git-server/01-chapter4.markdown +864 -0
  323. data/test/progit/it/05-distributed-git/01-chapter5.markdown +897 -0
  324. data/test/progit/it/06-git-tools/01-chapter6.markdown +1144 -0
  325. data/test/progit/it/07-customizing-git/01-chapter7.markdown +606 -0
  326. data/test/progit/it/08-git-and-other-scms/01-chapter8.markdown +707 -0
  327. data/test/progit/it/09-git-internals/01-chapter9.markdown +1000 -0
  328. data/test/progit/ja/01-introduction/01-chapter1.markdown +260 -0
  329. data/test/progit/ja/02-git-basics/01-chapter2.markdown +1221 -0
  330. data/test/progit/ja/03-git-branching/01-chapter3.markdown +604 -0
  331. data/test/progit/ja/04-git-server/01-chapter4.markdown +863 -0
  332. data/test/progit/ja/05-distributed-git/01-chapter5.markdown +908 -0
  333. data/test/progit/ja/06-git-tools/01-chapter6.markdown +1133 -0
  334. data/test/progit/ja/07-customizing-git/01-chapter7.markdown +936 -0
  335. data/test/progit/ja/08-git-and-other-scms/01-chapter8.markdown +690 -0
  336. data/test/progit/ja/09-git-internals/01-chapter9.markdown +984 -0
  337. data/test/progit/ja/README.md +58 -0
  338. data/test/progit/ja/translation glossaries.txt +33 -0
  339. data/test/progit/ko/01-introduction/01-chapter1.markdown +258 -0
  340. data/test/progit/ko/02-git-basics/01-chapter2.markdown +1181 -0
  341. data/test/progit/ko/03-git-branching/01-chapter3.markdown +612 -0
  342. data/test/progit/ko/04-git-server/01-chapter4.markdown +867 -0
  343. data/test/progit/ko/05-distributed-git/01-chapter5.markdown +913 -0
  344. data/test/progit/ko/06-git-tools/01-chapter6.markdown +1142 -0
  345. data/test/progit/ko/07-customizing-git/01-chapter7.markdown +935 -0
  346. data/test/progit/ko/08-git-and-other-scms/01-chapter8.markdown +688 -0
  347. data/test/progit/ko/09-git-internals/01-chapter9.markdown +976 -0
  348. data/test/progit/ko/README.md +75 -0
  349. data/test/progit/ko/translation_guide.txt +65 -0
  350. data/test/progit/latex/README +27 -0
  351. data/test/progit/latex/config.yml +144 -0
  352. data/test/progit/latex/makepdf +207 -0
  353. data/test/progit/latex/template.tex +155 -0
  354. data/test/progit/makeebooks +125 -0
  355. data/test/progit/makepdfs +47 -0
  356. data/test/progit/mk/01-introduction/01-chapter1.markdown +258 -0
  357. data/test/progit/mk/02-git-basics/01-chapter2.markdown +1125 -0
  358. data/test/progit/mk/03-git-branching/01-chapter3.markdown +598 -0
  359. data/test/progit/mk/05-distributed-git/01-chapter5.markdown +897 -0
  360. data/test/progit/nl/01-introduction/01-chapter1.markdown +296 -0
  361. data/test/progit/nl/02-git-basics/01-chapter2.markdown +1253 -0
  362. data/test/progit/nl/03-git-branching/01-chapter3.markdown +642 -0
  363. data/test/progit/nl/04-git-server/01-chapter4.markdown +902 -0
  364. data/test/progit/nl/05-distributed-git/01-chapter5.markdown +953 -0
  365. data/test/progit/nl/06-git-tools/01-chapter6.markdown +1177 -0
  366. data/test/progit/nl/07-customizing-git/01-chapter7.markdown +974 -0
  367. data/test/progit/nl/08-git-and-other-scms/01-chapter8.markdown +725 -0
  368. data/test/progit/nl/09-git-internals/01-chapter9.markdown +1013 -0
  369. data/test/progit/no-nb/01-introduction/01-chapter1.markdown +261 -0
  370. data/test/progit/no-nb/02-git-basics/01-chapter2.markdown +1225 -0
  371. data/test/progit/no-nb/03-git-branching/01-chapter3.markdown +606 -0
  372. data/test/progit/no-nb/04-git-server/01-chapter4.markdown +867 -0
  373. data/test/progit/no-nb/05-distributed-git/01-chapter5.markdown +914 -0
  374. data/test/progit/no-nb/06-git-tools/01-chapter6.markdown +1144 -0
  375. data/test/progit/no-nb/07-customizing-git/01-chapter7.markdown +936 -0
  376. data/test/progit/no-nb/08-git-and-other-scms/01-chapter8.markdown +689 -0
  377. data/test/progit/no-nb/09-git-internals/01-chapter9.markdown +977 -0
  378. data/test/progit/no-nb/README +2 -0
  379. data/test/progit/pl/01-introduction/01-chapter1.markdown +257 -0
  380. data/test/progit/pl/02-git-basics/02-chapter2.markdown +1128 -0
  381. data/test/progit/pl/03-git-branching/01-chapter3.markdown +598 -0
  382. data/test/progit/pl/04-git-server/01-chapter4.markdown +897 -0
  383. data/test/progit/pl/05-distributed-git/01-chapter5.markdown +1278 -0
  384. data/test/progit/pl/06-git-tools/01-chapter6.markdown +1550 -0
  385. data/test/progit/pl/07-customizing-git/01-chapter7.markdown +1058 -0
  386. data/test/progit/pl/08-git-and-other-scms/01-chapter8.markdown +948 -0
  387. data/test/progit/pl/09-git-internals/01-chapter9.markdown +1382 -0
  388. data/test/progit/pl/translation-guidelines.txt +70 -0
  389. data/test/progit/pt-br/01-introduction/01-chapter1.markdown +256 -0
  390. data/test/progit/pt-br/02-git-basics/01-chapter2.markdown +1127 -0
  391. data/test/progit/pt-br/03-git-branching/01-chapter3.markdown +596 -0
  392. data/test/progit/pt-br/04-git-server/01-chapter4.markdown +888 -0
  393. data/test/progit/pt-br/05-distributed-git/01-chapter5.markdown +896 -0
  394. data/test/progit/pt-br/06-git-tools/01-chapter6.markdown +1122 -0
  395. data/test/progit/pt-br/07-customizing-git/01-chapter7.markdown +932 -0
  396. data/test/progit/pt-br/08-git-and-other-scms/01-chapter8.markdown +691 -0
  397. data/test/progit/pt-br/09-git-internals/01-chapter9.markdown +978 -0
  398. data/test/progit/pt-br/figures-dia/fig0101.dia +617 -0
  399. data/test/progit/pt-br/figures-dia/fig0102.dia +921 -0
  400. data/test/progit/pt-br/figures-dia/fig0103.dia +1468 -0
  401. data/test/progit/pt-br/figures-dia/fig0104.dia +1432 -0
  402. data/test/progit/pt-br/figures-dia/fig0105.dia +1924 -0
  403. data/test/progit/pt-br/figures-dia/fig0106.dia +562 -0
  404. data/test/progit/pt-br/figures-dia/fig0201.dia +776 -0
  405. data/test/progit/pt-br/figures-dia/fig0301.dia +2006 -0
  406. data/test/progit/pt-br/figures-dia/fig0302.dia +2148 -0
  407. data/test/progit/pt-br/figures-dia/fig0316.dia +1079 -0
  408. data/test/progit/pt-br/figures-dia/fig0322.dia +1142 -0
  409. data/test/progit/pt-br/figures-dia/fig0323.dia +1407 -0
  410. data/test/progit/pt-br/figures-dia/fig0324.dia +1603 -0
  411. data/test/progit/pt-br/figures-dia/fig0325.dia +2003 -0
  412. data/test/progit/pt-br/figures-dia/fig0326.dia +2013 -0
  413. data/test/progit/pt-br/figures-dia/fig0336.dia +786 -0
  414. data/test/progit/pt-br/figures-dia/fig0337.dia +1546 -0
  415. data/test/progit/pt-br/figures-dia/fig0338.dia +1755 -0
  416. data/test/progit/pt-br/figures-dia/fig0339.dia +1882 -0
  417. data/test/progit/pt-br/figures-dia/fig0501.dia +456 -0
  418. data/test/progit/pt-br/figures-dia/fig0502.dia +965 -0
  419. data/test/progit/pt-br/figures-dia/fig0503.dia +914 -0
  420. data/test/progit/pt-br/figures-dia/fig0511.dia +1201 -0
  421. data/test/progit/pt-br/figures-dia/fig0515.dia +1721 -0
  422. data/test/progit/pt-br/figures-dia/fig0702.dia +795 -0
  423. data/test/progit/pt-br/figures-dia/fig0703.dia +795 -0
  424. data/test/progit/pt-br/figures-dia/fig0901.dia +669 -0
  425. data/test/progit/pt-br/figures-dia/fig0902.dia +834 -0
  426. data/test/progit/pt-br/figures-dia/fig0903.dia +1483 -0
  427. data/test/progit/pt-br/figures-dia/fig0904.dia +1728 -0
  428. data/test/progit/ro/01-introduction/01-chapter1.markdown +257 -0
  429. data/test/progit/ru/01-introduction/01-chapter1.markdown +259 -0
  430. data/test/progit/ru/02-git-basics/01-chapter2.markdown +1155 -0
  431. data/test/progit/ru/03-git-branching/01-chapter3.markdown +598 -0
  432. data/test/progit/ru/04-git-server/01-chapter4.markdown +854 -0
  433. data/test/progit/ru/05-distributed-git/01-chapter5.markdown +897 -0
  434. data/test/progit/ru/06-git-tools/01-chapter6.markdown +1126 -0
  435. data/test/progit/ru/07-customizing-git/01-chapter7.markdown +938 -0
  436. data/test/progit/ru/08-git-and-other-scms/01-chapter8.markdown +691 -0
  437. data/test/progit/ru/09-git-internals/01-chapter9.markdown +977 -0
  438. data/test/progit/ru/Glossary +38 -0
  439. data/test/progit/ru/README +12 -0
  440. data/test/progit/ru/figures-dia/fig0101.dia +647 -0
  441. data/test/progit/ru/figures-dia/fig0102.dia +1009 -0
  442. data/test/progit/ru/figures-dia/fig0103.dia +1468 -0
  443. data/test/progit/ru/figures-dia/fig0104.dia +1432 -0
  444. data/test/progit/ru/figures-dia/fig0105.dia +1924 -0
  445. data/test/progit/ru/figures-dia/fig0106.dia +561 -0
  446. data/test/progit/ru/figures-dia/fig0201.dia +774 -0
  447. data/test/progit/ru/figures-dia/fig0322.dia +1182 -0
  448. data/test/progit/ru/figures-dia/fig0323.dia +1457 -0
  449. data/test/progit/ru/figures-dia/fig0324.dia +1698 -0
  450. data/test/progit/ru/figures-dia/fig0325.dia +2101 -0
  451. data/test/progit/ru/figures-dia/fig0326.dia +2111 -0
  452. data/test/progit/ru/figures-dia/fig0336.dia +786 -0
  453. data/test/progit/ru/figures-dia/fig0337.dia +1546 -0
  454. data/test/progit/ru/figures-dia/fig0338.dia +1755 -0
  455. data/test/progit/ru/figures-dia/fig0339.dia +1882 -0
  456. data/test/progit/ru/figures-dia/fig0501.dia +477 -0
  457. data/test/progit/ru/figures-dia/fig0502.dia +1063 -0
  458. data/test/progit/ru/figures-dia/fig0503.dia +915 -0
  459. data/test/progit/ru/figures-dia/fig0511.dia +1201 -0
  460. data/test/progit/ru/figures-dia/fig0515.dia +1741 -0
  461. data/test/progit/ru/figures-dia/fig0702.dia +851 -0
  462. data/test/progit/ru/figures-dia/fig0703.dia +851 -0
  463. data/test/progit/sr/01-introduction/01-chapter1.markdown +257 -0
  464. data/test/progit/summary.rb +29 -0
  465. data/test/progit/th/01-introduction/01-chapter1.markdown +257 -0
  466. data/test/progit/th/02-git-basics/01-chapter2.markdown +1126 -0
  467. data/test/progit/th/README.md +47 -0
  468. data/test/progit/tr/01-introduction/01-chapter1.markdown +258 -0
  469. data/test/progit/tr/02-git-basics/01-chapter2.markdown +1129 -0
  470. data/test/progit/tr/03-git-branching/01-chapter3.markdown +598 -0
  471. data/test/progit/tr/04-git-server/01-chapter4.markdown +73 -0
  472. data/test/progit/tr/05-distributed-git/01-chapter5.markdown +215 -0
  473. data/test/progit/uk/01-introduction/01-chapter1.markdown +522 -0
  474. data/test/progit/vi/01-introduction/01-chapter1.markdown +259 -0
  475. data/test/progit/vi/02-git-basics/01-chapter2.markdown +1172 -0
  476. data/test/progit/vi/03-git-branching/01-chapter3.markdown +598 -0
  477. data/test/progit/zh-tw/01-introduction/01-chapter1.markdown +259 -0
  478. data/test/progit/zh-tw/02-git-basics/01-chapter2.markdown +1183 -0
  479. data/test/progit/zh-tw/03-git-branching/01-chapter3.markdown +604 -0
  480. data/test/progit/zh-tw/04-git-server/01-chapter4.markdown +866 -0
  481. data/test/progit/zh-tw/05-distributed-git/01-chapter5.markdown +912 -0
  482. data/test/progit/zh-tw/06-git-tools/01-chapter6.markdown +1139 -0
  483. data/test/progit/zh-tw/07-customizing-git/01-chapter7.markdown +932 -0
  484. data/test/progit/zh-tw/08-git-and-other-scms/01-chapter8.markdown +689 -0
  485. data/test/progit/zh-tw/09-git-internals/01-chapter9.markdown +977 -0
  486. data/test/progit/zh/01-introduction/01-chapter1.markdown +259 -0
  487. data/test/progit/zh/02-git-basics/01-chapter2.markdown +1177 -0
  488. data/test/progit/zh/03-git-branching/01-chapter3.markdown +604 -0
  489. data/test/progit/zh/04-git-server/01-chapter4.markdown +866 -0
  490. data/test/progit/zh/05-distributed-git/01-chapter5.markdown +912 -0
  491. data/test/progit/zh/06-git-tools/01-chapter6.markdown +1125 -0
  492. data/test/progit/zh/07-customizing-git/01-chapter7.markdown +935 -0
  493. data/test/progit/zh/08-git-and-other-scms/01-chapter8.markdown +689 -0
  494. data/test/progit/zh/09-git-internals/01-chapter9.markdown +976 -0
  495. data/test/spec_tests.json +4382 -4070
  496. data/test/test_basics.rb +1 -1
  497. data/test/test_helper.rb +1 -0
  498. data/test/test_maliciousness.rb +4 -2
  499. data/test/test_pathological_inputs.rb +31 -30
  500. data/test/test_spec.rb +5 -4
  501. metadata +972 -4
@@ -0,0 +1,977 @@
1
+ # Git изнутри #
2
+
3
+ Вы могли прочитать почти всю книгу перед тем, как приступить к этой главе, а могли только часть. Так или иначе, в данной главе рассматриваются внутренние процессы Git'а и особенности его реализации. На мой взгляд, изучение этих вещей — это основа понимания того, насколько Git полезный и мощный инструмент. Хотя некоторые утверждают, что изложение этого материала может сбить новичков с толку и оказаться для них неоправданно сложным. Именно поэтому эта глава отнесена в конец, давая возможность заинтересованным освоить её раньше, а сомневающимся — позже.
4
+
5
+ Итак, приступим. Во-первых, напомню, что Git — это, по сути, контентно-адресуемая файловая система с пользовательским СКВ-интерфейсом поверх неё. Довольно скоро станет понятнее, что это значит.
6
+
7
+ На заре развития Git'а (примерно до версии 1.5), интерфейс был значительно сложнее, поскольку был более похож на интерфейс доступа к файловой системе, чем на законченную СКВ. За последние годы, интерфейс значительно улучшился и по удобству не уступает аналогам; у некоторых, тем не менее, с тех пор сохранился стереотип о том, что интерфейс у Git'а чересчур сложный и труден для изучения.
8
+
9
+ Контентно-адресуемая файловая система — основа Git'а, очень интересна, именно её мы сначала рассмотрим в этой главе; далее будут рассмотрены транспортные механизмы и инструменты обслуживания репозитория, с которыми вам в своё время, возможно, придётся столкнуться.
10
+
11
+ ## Сантехника и фарфор ##
12
+
13
+ В этой книге было описано, как пользоваться Git'ом, применяя примерно три десятка команд, например, `checkout`, `branch`, `remote` и т.п. Но так как сначала Git был скорее инструментарием для создания СКВ, чем СКВ, удобной для пользователей, в нём полно команд, выполняющих низкоуровневые операции, которые спроектированы так, чтобы их можно было использовать в цепочку в стиле UNIX, а также использовать в сценариях. Эти команды, как правило, называют служебными ("plumbing" — трубопровод), а ориентированные на пользователя называют пользовательскими ("porcelain" — фарфор).
14
+
15
+ Первые восемь глав книги были посвящены практически только пользовательским командам. В данной главе же рассматриваются именно низкоуровневые служебные команды, дающие контроль над внутренними процессами Git'а и показывающие, как он работает и почему он работает так, а не иначе. Предполагается, что данные команды не будут использоваться напрямую из командной строки, а будут служить в качестве строительных блоков для новых команд и пользовательских сценариев.
16
+
17
+ Когда вы выполняете `git init` в новом или существовавшем ранее каталоге, Git создаёт подкаталог `.git`, в котором располагается почти всё, чем он заправляет. Если требуется выполнить резервное копирование или клонирование репозитория, достаточно скопировать всего лишь один этот каталог, чтобы получить почти всё необходимое. И данная глава почти полностью посвящена его содержимому. Вот так он выглядит:
18
+
19
+ $ ls
20
+ HEAD
21
+ branches/
22
+ config
23
+ description
24
+ hooks/
25
+ index
26
+ info/
27
+ objects/
28
+ refs/
29
+
30
+ Там могут быть и другие файлы, но непосредственно после `git init` вы увидите именно это. Каталог `branches` не используется новыми версиями Git'а, а файл `description` требуется только программе GitWeb, на них не стоит обращать особого внимания. Файл `config` содержит настройки проекта, а каталог `info` — файл с глобальным фильтром, игнорирующим те файлы, которые вы не хотите поместить в .gitignore. В каталоге `hooks` располагаются клиентские и серверные перехватчики, подробно рассмотренные в главе 7.
31
+
32
+ Итак, осталось четыре важных элемента: файлы `HEAD`, `index` и каталоги `objects`, `refs`. Это ключевые элементы хранилища Git'а. В каталоге `objects` находится, собственно, база данных, в `refs` — ссылки на объекты коммитов в этой базе (ветки). Файл `HEAD` указывает на текущую ветку, и в файле `index` хранится информация индекса. В последующих разделах данные элементы будут рассмотрены более подробно.
33
+
34
+ ## Объекты в Git ##
35
+
36
+ Git — контентно-адресуемая файловая система. Здорово. Но что это означает?
37
+ А означает это, что в своей основе Git — простое хранилище ключ-значение. Можно добавить туда любое содержимое, в ответ будет выдан ключ, по которому это содержимое можно извлечь. Для примера, можно воспользоваться служебной командой `hash-object`, которая добавляет данные в каталог `.git` и возвращает ключ. Для начала создадим новый Git-репозиторий и убедимся, что каталог `objects` пуст:
38
+
39
+ $ mkdir test
40
+ $ cd test
41
+ $ git init
42
+ Initialized empty Git repository in /tmp/test/.git/
43
+ $ find .git/objects
44
+ .git/objects
45
+ .git/objects/info
46
+ .git/objects/pack
47
+ $ find .git/objects -type f
48
+ $
49
+
50
+ Git проинициализировал каталог `objects` и создал в нём подкаталоги `pack` и `info`, пока без файлов. Теперь добавим кое-какое текстовое содержимое в базу Git'а:
51
+
52
+ $ echo 'test content' | git hash-object -w --stdin
53
+ d670460b4b4aece5915caf5c68d12f560a9fe3e4
54
+
55
+ Ключ `-w` команды `hash-object` указывает, что объект необходимо сохранить, иначе команда просто выведет ключ и всё. Флаг `--stdin` указывает, что данные необходимо считать со стандартного ввода, в противном случае `hash-object` ожидает имя файла. Вывод команды — 40-символьная контрольная сумма. Это хеш SHA-1 — контрольная сумма содержимого и заголовка, который будет рассмотрен позднее. Теперь можно увидеть, в каком виде будут сохранены ваши данные:
56
+
57
+ $ find .git/objects -type f
58
+ .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
59
+
60
+ В каталоге `objects` появился файл. Это и есть начальное внутреннее представление данных в Git'е — один файл на единицу хранения с именем, являющимся контрольной суммой содержимого и заголовка. Первые два символа SHA определяют подкаталог файла, остальные 38 — собственно, имя.
61
+
62
+ Получить обратно содержимое объекта можно командой `cat-file`. Это своеобразный швейцарский армейский нож для проверки объектов в Git'е. Ключ `-p` означает автоматическое определение типа содержимого и вывод содержимого на печать в удобном виде:
63
+
64
+ $ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
65
+ test content
66
+
67
+ Теперь вы умеете добавлять данные в Git и извлекать их обратно. То же самое можно делать и с файлами. Рассмотрим пример. Наиболее простой контроль версий файла можно осуществить, создав его и сохранив в базе:
68
+
69
+ $ echo 'version 1' > test.txt
70
+ $ git hash-object -w test.txt
71
+ 83baae61804e65cc73a7201a7252750c76066a30
72
+
73
+ Теперь изменим файл и сохраним его в базе ещё раз:
74
+
75
+ $ echo 'version 2' > test.txt
76
+ $ git hash-object -w test.txt
77
+ 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
78
+
79
+ Теперь в базе содержатся две версии файла test.txt, а также самый первый сохранённый объект:
80
+
81
+ $ find .git/objects -type f
82
+ .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
83
+ .git/objects/83/baae61804e65cc73a7201a7252750c76066a30
84
+ .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
85
+
86
+ Теперь можно откатить файл к его первой версии:
87
+
88
+ $ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
89
+ $ cat test.txt
90
+ version 1
91
+
92
+ или второй:
93
+
94
+ $ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
95
+ $ cat test.txt
96
+ version 2
97
+
98
+ Однако запоминать хеш для каждой версии неудобно, к тому же теряется само имя файла, сохраняется лишь содержимое. Объекты такого типа называют блобами (англ. binary large object). Имея SHA-1 объекта, можно попросить Git показать нам его тип с помощью команды `cat-file -t`:
99
+
100
+ $ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
101
+ blob
102
+
103
+ ### Объекты-деревья ###
104
+
105
+ Рассмотрим другой тип объектов Git'а — деревья. Они решают проблему хранения имён файлов, а также позволяют хранить группы файлов вместе. Git хранит данные сходным с файловыми системами UNIX способом, но в немного упрощённом виде. Содержимое хранится в объектах-деревьях и блобах, дерево соответствует записи каталога в ФС, а блоб более или менее соответствует inode или содержимому файла. Объект-дерево может содержать одну и более записей, каждая из которых представляет собой набор из SHA-1 хеша, соответствующего блобу или поддереву, режима доступа к файлу, типа и имени файла. Например, в проекте simplegit последнее дерево выглядит так:
106
+
107
+ $ git cat-file -p master^{tree}
108
+ 100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
109
+ 100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
110
+ 040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
111
+
112
+ Запись `master^{tree}` означает объект-дерево, на который указывает последний коммит ветки `master`. Заметьте, что подкаталог `lib` — не блоб, а указатель на другое дерево:
113
+
114
+ $ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
115
+ 100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
116
+
117
+ Схематически, данные, хранящиеся в Git'е, выглядят примерно так, как это изображено на рисунке 9-1.
118
+
119
+ Insert 18333fig0901.png
120
+ Рисунок 9-1. Упрощённая модель данных Git'а.
121
+
122
+ Вручную можно создавать не только блобы, но и деревья. Git обычно создаёт дерево, исходя из состояния индекса, и затем сохраняет соответствующий объект-дерево. Поэтому для создания объекта-дерева необходимо проиндексировать какие-нибудь файлы. Для создания индекса из одной записи — первой версии файла test.txt, воспользуемся командой `update-index`. Данная команда может искусственно добавить более раннюю версию test.txt в новый индекс. Необходимо передать опции `--add`, т.к. файл ещё не существует в индексе (да и самого индекса ещё нет), и `--cacheinfo`, т.к. добавляемого файла нет в рабочем каталоге, но он есть в базе данных. Также необходимо передать режим доступа, хеш и имя файла:
123
+
124
+ $ git update-index --add --cacheinfo 100644 \
125
+ 83baae61804e65cc73a7201a7252750c76066a30 test.txt
126
+
127
+ В данном случае режим доступа — `100644`, что означает обычный файл. Другие возможные варианты: `100755` — исполняемый файл, `120000` — символическая ссылка. Режимы доступа в Git'е сделаны по аналогии с режимами доступа в UNIX, но они гораздо менее гибки: данные три режима — единственные доступные для файлов (блобов) в Git'е (хотя существуют и другие режимы, используемые для каталогов и подмодулей).
128
+
129
+ Теперь можно воспользоваться командой `write-tree` для сохранения индекса в объект-дерево. Здесь опция `-w` не требуется — вызов `write-tree` автоматически создаст объект-дерево по состоянию индекса, если такого дерева ещё не существует:
130
+
131
+ $ git write-tree
132
+ d8329fc1cc938780ffdd9f94e0d364e0ea74f579
133
+ $ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
134
+ 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
135
+
136
+ Также можно проверить, что мы действительно создали объект-дерево:
137
+
138
+ $ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
139
+ tree
140
+
141
+ Создадим новое дерево со второй версией файла test.txt и ещё одним файлом:
142
+
143
+ $ echo 'new file' > new.txt
144
+ $ git update-index test.txt
145
+ $ git update-index --add new.txt
146
+
147
+ Теперь в индексе содержится новая версия файла test.txt и новый файл new.txt. Запишем это дерево (сохранив состояние индекса в объект-дерево) и посмотрим, что из этого получилось:
148
+
149
+ $ git write-tree
150
+ 0155eb4229851634a0f03eb265b69f5a2d56f341
151
+ $ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
152
+ 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
153
+ 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
154
+
155
+ Заметьте, что в данном дереве находятся записи для обоих файлов, а также, что хеш файла test.txt это хеш "второй версии" этого файла (`1f7a7a`). Для интереса, добавим первое дерево как подкаталог для текущего. Зачитать дерево в индекс можно командой `read-tree`. В нашем случае, чтобы прочитать уже существующее дерево в индекс и сделать его поддеревом, необходимо использовать опцию `--prefix`:
156
+
157
+ $ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
158
+ $ git write-tree
159
+ 3c4e9cd789d88d8d89c1073707c3585e41b0e614
160
+ $ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
161
+ 040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
162
+ 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
163
+ 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
164
+
165
+ Если бы вы создали рабочий каталог, соответствующий только что созданному дереву, вы бы получили два файла в корне и подкаталог `bak` со старой версией файла test.txt. Данные, хранящиеся в Git'е для такой структуры, могут быть представлены так, как показано на рисунке 9-2.
166
+
167
+ Insert 18333fig0902.png
168
+ Рисунок 9-2. Структура данных Git'а для текущего дерева.
169
+
170
+ ### Объекты-коммиты ###
171
+
172
+ У нас есть три дерева, соответствующих разным состояниям проекта, но предыдущая проблема с необходимостью запоминать все три значения SHA-1, чтобы иметь возможность восстановить какое-либо из этих состояний, ещё не решена. К тому же у нас нет никакой информации о том, кто, когда и почему сохранил их. Такие данные — основная информация, которая хранится в объекте-коммите.
173
+
174
+ Для создания объекта-коммита необходимо вызвать `commit-tree` и задать SHA-1 нужного дерева и, если необходимо, родительские объекты-коммиты. Для начала создадим коммит для самого первого дерева:
175
+
176
+ $ echo 'first commit' | git commit-tree d8329f
177
+ fdf4fc3344e67ab068f836878b6c4951e3b15f3d
178
+
179
+ Просмотреть вновь созданный объект-коммит можно командой `cat-file`:
180
+
181
+ $ git cat-file -p fdf4fc3
182
+ tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
183
+ author Scott Chacon <schacon@gmail.com> 1243040974 -0700
184
+ committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
185
+
186
+ first commit
187
+
188
+ Формат объекта-коммита прост: в нём указано дерево верхнего уровня, соответствующее состоянию проекта на некоторый момент; имена автора и коммитера берутся из полей конфигурации `user.name` и `user.email`; также добавляется текущая временная метка, пустая строка и затем сообщение коммита.
189
+
190
+ Далее, создадим ещё два объекта-коммита, каждый из которых будет ссылаться на предыдущий коммит:
191
+
192
+ $ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
193
+ cac0cab538b970a37ea1e769cbbde608743bc96d
194
+ $ echo 'third commit' | git commit-tree 3c4e9c -p cac0cab
195
+ 1a410efbd13591db07496601ebc7a059dd55cfe9
196
+
197
+ Каждый из трёх объектов-коммитов указывает на одно из состояний проекта. Может показаться странным, но теперь у нас есть полноценная Git-история, которую можно посмотреть командой `git log`, указав хеш последнего коммита:
198
+
199
+ $ git log --stat 1a410e
200
+ commit 1a410efbd13591db07496601ebc7a059dd55cfe9
201
+ Author: Scott Chacon <schacon@gmail.com>
202
+ Date: Fri May 22 18:15:24 2009 -0700
203
+
204
+ third commit
205
+
206
+ bak/test.txt | 1 +
207
+ 1 files changed, 1 insertions(+), 0 deletions(-)
208
+
209
+ commit cac0cab538b970a37ea1e769cbbde608743bc96d
210
+ Author: Scott Chacon <schacon@gmail.com>
211
+ Date: Fri May 22 18:14:29 2009 -0700
212
+
213
+ second commit
214
+
215
+ new.txt | 1 +
216
+ test.txt | 2 +-
217
+ 2 files changed, 2 insertions(+), 1 deletions(-)
218
+
219
+ commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
220
+ Author: Scott Chacon <schacon@gmail.com>
221
+ Date: Fri May 22 18:09:34 2009 -0700
222
+
223
+ first commit
224
+
225
+ test.txt | 1 +
226
+ 1 files changed, 1 insertions(+), 0 deletions(-)
227
+
228
+ Поразительно. Мы только что выполнили низкоуровневые операции для построения истории без использования высокоуровневых интерфейсов. По существу, именно это делает Git, когда выполняются команды `git add` и `git commit` — сохраняет блобы для изменённых файлов, обновляет индекс, записывает объекты-деревья и коммит-объекты, ссылающиеся на объекты-деревья верхнего уровня и предшествующие коммиты. Эти три основных вида объектов Git'а: блоб, дерево и коммит — первоначально сохраняются как отдельные файлы в каталоге `.git/objects`. Вот все объекты, которые сейчас лежат в каталоге с примером (в комментариях написано чему объекты соответствует):
229
+
230
+ $ find .git/objects -type f
231
+ .git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
232
+ .git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
233
+ .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
234
+ .git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
235
+ .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
236
+ .git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
237
+ .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
238
+ .git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
239
+ .git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
240
+ .git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1
241
+
242
+ Если пройти по всем внутренним ссылкам, получится граф объектов такой, как на рисунке 9-3.
243
+
244
+ Insert 18333fig0903.png
245
+ Рисунок 9-3. Все объекты в каталоге Git'а.
246
+
247
+ ### Хранение объектов ###
248
+
249
+ Ранее я упоминал, что заголовок сохраняется вместе с содержимым. Давайте посмотрим, как сохраняются объекты Git'а на диске. Мы рассмотрим сохранение блоб-объекта, в данном случае это будет строка "есть проблемы, шеф?". Пример будет выполнен на языке Ruby. Для запуска интерактивного интерпретатора воспользуйтесь командой `irb`:
250
+
251
+ $ irb
252
+ >> content = "есть проблемы, шеф?"
253
+ => "есть проблемы, шеф?"
254
+
255
+ Git создаёт заголовок, начинающийся с типа объекта, в данном случае это блоб. Далее добавляется пробел, размер содержимого и в конце нулевой байт:
256
+
257
+ >> header = "blob #{content.length}\0"
258
+ => "blob 34\000"
259
+
260
+ Git дописывает содержимое после заголовка и вычисляет SHA-1 сумму для полученного результата. В Ruby значение SHA-1 для строки можно получить, подключив соответствующую библиотеку командой `require` и затем воспользовавшись вызовом `Digest::SHA1.hexdigest()`:
261
+
262
+ >> store = header + content
263
+ => "blob 34\000\320\225\321\201\321\202\321\214 \320\277\321\200\320\276\320\261\320\273\320\265\320\274\321\213, \321\210\320\265\321\204?"
264
+ >> require 'digest/sha1'
265
+ => true
266
+ >> sha1 = Digest::SHA1.hexdigest(store)
267
+ => "d8a734f44240bdf766c8df342664fde23d421d64"
268
+
269
+ Git сжимает новые данные при помощи zlib. В Ruby это можно сделать с помощью одноимённой библиотеки. Сперва необходимо подключить её, а после вызвать `Zlib::Deflate.deflate()` с данными в качестве параметра:
270
+
271
+ >> require 'zlib'
272
+ => true
273
+ >> zlib_content = Zlib::Deflate.deflate(store)
274
+ => "x\234\001*\000\325\377blob 34\000\320\225\321\201\321\202\321\214 \320\277\321\200\320\276\320\261\320\273\320\265\320\274\321\213, \321\210\320\265\321\204?\3453\030S"
275
+
276
+ После этого запишем сжатую zlib'ом строку в объект на диск. Определим путь к файлу, который будет записан (первые два символа хеша используются в качестве названия подкаталога, оставшиеся 38 — в качестве имени файла в этом каталоге). В Ruby для этой задачи можно использовать функцию `FileUtils.mkdir_p()` для создания подкаталога, если он не существует. Далее, откроем файл вызовом `File.open()` и запишем наши сжатые данные вызовом `write()` для полученного файлового дескриптора:
277
+
278
+ >> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
279
+ => ".git/objects/d8/a734f44240bdf766c8df342664fde23d421d64"
280
+ >> require 'fileutils'
281
+ => true
282
+ >> FileUtils.mkdir_p(File.dirname(path))
283
+ => ".git/objects/bd"
284
+ >> File.open(path, 'w') { |f| f.write zlib_content }
285
+ => 32
286
+
287
+ Вот и всё, мы создали корректный объект-блоб для Git'а. Все другие объекты создаются аналогично, меняется только запись о типе в заголовке (blob, commit, tree). Стоит добавить, что хотя в блобе может храниться почти любое содержимое, содержимое объектов-деревьев и объектов-коммитов записывается в очень строгом формате.
288
+
289
+ ## Ссылки в Git ##
290
+
291
+ Для просмотра всей истории можно выполнить команду вроде `git log 1a410e`, но, опять же, требуется помнить, что именно коммит `1a410e` является последним, чтобы иметь возможность найти все наши объекты. Нам нужен файл-указатель с простым именем, который бы содержал это значение хеша SHA-1, чтобы можно было пользоваться этим файлом вместо хеша.
292
+
293
+ В Git'е такие файлы, содержащие SHA-1, называются ссылками ("refs") и располагаются в каталоге `.git/refs`. В нашем проекте этот каталог пока пуст, но в нём уже существует некоторая структура каталогов:
294
+
295
+ $ find .git/refs
296
+ .git/refs
297
+ .git/refs/heads
298
+ .git/refs/tags
299
+ $ find .git/refs -type f
300
+ $
301
+
302
+ Чтобы создать новую ссылку, которая поможет вам вспомнить, какой коммит последний, по сути, необходимо сделать всего лишь следующее:
303
+
304
+ $ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master
305
+
306
+ Теперь в командах Git'а вместо хеша можно использовать ссылку, только что созданную в каталоге heads:
307
+
308
+ $ git log --pretty=oneline master
309
+ 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
310
+ cac0cab538b970a37ea1e769cbbde608743bc96d second commit
311
+ fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
312
+
313
+ Тем не менее, редактировать данные файлы напрямую не рекомендуется. Git предоставляет безопасную команду `update-ref` для изменения ссылок:
314
+
315
+ $ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
316
+
317
+ Вот что такое, по сути, ветка в Git'е — простой указатель или ссылка на последнюю версию в работе. Для создания ветки, соответствующей состоянию второго коммита, можно выполнить следующее:
318
+
319
+ $ git update-ref refs/heads/test cac0ca
320
+
321
+ Данная ветка будет содержать только коммиты, предшествующие выбранному:
322
+
323
+ $ git log --pretty=oneline test
324
+ cac0cab538b970a37ea1e769cbbde608743bc96d second commit
325
+ fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
326
+
327
+ Теперь наша база данных Git'а схематично выглядит так, как показано на рисунке 9.4.
328
+
329
+ Insert 18333fig0904.png
330
+ Рисунок 9-4. Объекты в каталоге .git, а также указатели на вершины веток.
331
+
332
+ Когда выполняется команда `git branch (имя ветки)`, Git, по сути, выполняет `update-ref` для добавления хеша последнего коммита текущей ветки под указанным именем в виде новой ссылки.
333
+
334
+ ### HEAD ###
335
+
336
+ Вопрос в том, как же Git получает хеш последнего коммита при выполнении `git branch (имя ветки)`? Ответ содержится в файле HEAD. Данный файл является символической ссылкой на текущую ветку. Символическая ссылка отличается от обычной тем, что она содержит не сам хеш SHA-1, а указатель на другую ссылку. Если вы заглянете в этот файл, то увидите что-то такое:
337
+
338
+ $ cat .git/HEAD
339
+ ref: refs/heads/master
340
+
341
+ Если выполнить `git checkout test`, то содержимое файла изменится:
342
+
343
+ $ cat .git/HEAD
344
+ ref: refs/heads/test
345
+
346
+ При выполнении `git commit` Git создаёт объект-коммит, указывая его родителем тот объект, SHA-1 которого содержится в файле, на который ссылается HEAD.
347
+
348
+ Данный файл, конечно, можно редактировать вручную, но безопаснее использовать команду `symbolic-ref`. Получить значение HEAD данной командой можно так:
349
+
350
+ $ git symbolic-ref HEAD
351
+ refs/heads/master
352
+
353
+ Изменить значение HEAD можно так:
354
+
355
+ $ git symbolic-ref HEAD refs/heads/test
356
+ $ cat .git/HEAD
357
+ ref: refs/heads/test
358
+
359
+ Символическую ссылку на файл вне refs поставить нельзя:
360
+
361
+ $ git symbolic-ref HEAD test
362
+ fatal: Refusing to point HEAD outside of refs/
363
+
364
+ ### Метки ###
365
+
366
+ Мы рассмотрели три основных типа объектов Git'а, но есть и четвёртый. Объект-метка очень похож на объект-коммит: он содержит имя поставившего метку, дату, сообщение и указатель. Разница же в том, что метка указывает на коммит, а не на дерево. Она похожа на ветку, которая никогда не перемещается — она всегда указывает на один и тот же коммит, она просто даёт ему понятное имя.
367
+
368
+ Как было сказано в главе 2, метки бывают двух типов: аннотированные и легковесные. Легковесную метку можно сделать следующей командой:
369
+
370
+ $ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
371
+
372
+ Вот и всё! Легковесная метка — это ветка, которая никогда не перемещается. Аннотированная метка имеет более сложную структуру. При создании аннотированной метки Git создаёт специальный объект, на который будет указывать ссылка, а не просто указатель на коммит. Мы можем увидеть это, создав аннотированную метку (`-a` задаёт аннотированные метки):
373
+
374
+ $ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'
375
+
376
+ Вот значение SHA-1 созданного объекта:
377
+
378
+ $ cat .git/refs/tags/v1.1
379
+ 9585191f37f7b0fb9444f35a9bf50de191beadc2
380
+
381
+ Теперь выполним `cat-file` для этого хеша:
382
+
383
+ $ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
384
+ object 1a410efbd13591db07496601ebc7a059dd55cfe9
385
+ type commit
386
+ tag v1.1
387
+ tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700
388
+
389
+ test tag
390
+
391
+ Заметьте, в поле object записан SHA-1 коммита, для которого мы делали метку. Также стоит отметить, что это поле не обязательно указывает на коммит, но на любой объект в Git'е. Например, в исходный код Git'а мейнтейнер добавил свой открытый GPG-ключ в качестве блоба и поставил для него метку. Увидеть этот ключ можно, выполнив команду
392
+
393
+ $ git cat-file blob junio-gpg-pub
394
+
395
+ в репозитории с исходным кодом Git'а. В репозитории ядра Linux также есть метка, указывающая не на коммит — первая метка указывает на дерево первичного импорта.
396
+
397
+ ### Ссылки на удалённые ветки ###
398
+
399
+ Третий тип ссылок, который мы рассмотрим — ссылка на удалённую ветку. Если вы добавили удалённый репозиторий и отправили (push) на него изменения, Git сохранит последнее отправленное значение SHA-1 в каталоге `refs/remotes` для всех отправленных веток. Например, можно добавить удалённый репозиторий `origin` и отправить туда ветку `master`:
400
+
401
+ $ git remote add origin git@github.com:schacon/simplegit-progit.git
402
+ $ git push origin master
403
+ Counting objects: 11, done.
404
+ Compressing objects: 100% (5/5), done.
405
+ Writing objects: 100% (7/7), 716 bytes, done.
406
+ Total 7 (delta 2), reused 4 (delta 1)
407
+ To git@github.com:schacon/simplegit-progit.git
408
+ a11bef0..ca82a6d master -> master
409
+
410
+ Позже вы сможете посмотреть, где находилась ветка `master` с сервера `origin` во время последнего соединения с сервером, заглянув в файл `refs/remotes/origin/master`:
411
+
412
+ $ cat .git/refs/remotes/origin/master
413
+ ca82a6dff817ec66f44342007202690a93763949
414
+
415
+ Ссылки на удалённые ветки отличаются от обычных веток (ссылки в `refs/heads`) тем, что на них нельзя переключиться с помощью `git checkout`. Git работает с ними как с закладками, указывающими на последнее состояние соответствующих веток на ваших серверах.
416
+
417
+ ## Pack-файлы ##
418
+
419
+ Вернёмся к базе объектов в нашем тестовом репозитории. К этому моменту их должно быть 11 штук: 4 блоба, 3 дерева, 3 коммита и одна метка:
420
+
421
+ $ find .git/objects -type f
422
+ .git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
423
+ .git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
424
+ .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
425
+ .git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
426
+ .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
427
+ .git/objects/95/85191f37f7b0fb9444f35a9bf50de191beadc2 # tag
428
+ .git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
429
+ .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
430
+ .git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
431
+ .git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
432
+ .git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1
433
+
434
+ Git сжал содержимое этих файлов при помощи zlib, к тому же мы не записывали много данных, поэтому все эти файлы вместе занимают всего 925 байт. Для того чтобы продемонстрировать одну интересную возможность Git'а, добавим файл побольше. Добавим файл repo.rb из библиотеки Grit, с которой мы работали ранее, он занимает примерно 12 Кбайт:
435
+
436
+ $ curl -L https://raw.github.com/mojombo/grit/master/lib/grit/repo.rb > repo.rb
437
+ $ git add repo.rb
438
+ $ git commit -m 'added repo.rb'
439
+ [master 484a592] added repo.rb
440
+ 3 files changed, 459 insertions(+), 2 deletions(-)
441
+ delete mode 100644 bak/test.txt
442
+ create mode 100644 repo.rb
443
+ rewrite test.txt (100%)
444
+
445
+ Если мы посмотрим на полученное дерево, мы увидим значение SHA-1, которое получил блоб для файла repo.rb:
446
+
447
+ $ git cat-file -p master^{tree}
448
+ 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
449
+ 100644 blob 9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e repo.rb
450
+ 100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt
451
+
452
+ Посмотрим, сколько этот объект занимает места на диске:
453
+
454
+ $ du -b .git/objects/9b/c1dc421dcd51b4ac296e3e5b6e2a99cf44391e
455
+ 4102 .git/objects/9b/c1dc421dcd51b4ac296e3e5b6e2a99cf44391e
456
+
457
+ Теперь изменим немного данный файл и посмотрим на результат:
458
+
459
+ $ echo '# testing' >> repo.rb
460
+ $ git commit -am 'modified repo a bit'
461
+ [master ab1afef] modified repo a bit
462
+ 1 files changed, 1 insertions(+), 0 deletions(-)
463
+
464
+ Взглянув на дерево, полученное в результате коммита, мы увидим любопытную вещь:
465
+
466
+ $ git cat-file -p master^{tree}
467
+ 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
468
+ 100644 blob 05408d195263d853f09dca71d55116663690c27c repo.rb
469
+ 100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt
470
+
471
+ Теперь файлу repo.rb соответствует другой объект-блоб. Это означает, что даже одна единственная строка, добавленная в конец 400-строчного файла, требует создания абсолютно нового объекта:
472
+
473
+ $ du -b .git/objects/05/408d195263d853f09dca71d55116663690c27c
474
+ 4109 .git/objects/05/408d195263d853f09dca71d55116663690c27c
475
+
476
+ Итак, мы имеем два почти одинаковых объекта занимающих по 4 Кбайта на диске. Было бы неплохо, если бы Git сохранял только один объект целиком, а другой как разницу между ним и первым объектом.
477
+
478
+ Оказывается, что Git так и делает. Первоначальный формат для сохранения объектов в Git'е называется рыхлым форматом (loose format) объектов. Однако, время от времени Git упаковывает несколько таких объектов в один pack-файл (pack в пер. с англ. — упаковывать, уплотнять) для сохранения места на диске и повышения эффективности. Это происходит, когда "рыхлых" объектов становится слишком много, а также при вызове `git gc` вручную, и при отправке изменений на удалённый сервер. Чтобы посмотреть, как происходит упаковка, можно выполнить команду `git gc`:
479
+
480
+ $ git gc
481
+ Counting objects: 17, done.
482
+ Delta compression using 2 threads.
483
+ Compressing objects: 100% (13/13), done.
484
+ Writing objects: 100% (17/17), done.
485
+ Total 17 (delta 1), reused 10 (delta 0)
486
+
487
+ Если вы загляните в каталог с объектами, вы обнаружите, что большая часть объектов исчезла, зато появились два новых файла:
488
+
489
+ $ find .git/objects -type f
490
+ .git/objects/71/08f7ecb345ee9d0084193f147cdad4d2998293
491
+ .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
492
+ .git/objects/info/packs
493
+ .git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.idx
494
+ .git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.pack
495
+
496
+ Оставшиеся объекты — блобы, на которые не указывает ни один коммит. В нашем случае это созданные ранее объекты: содержащий строку "есть проблемы, шеф?", и блоб содержащий "test content". В силу того, что ни в одном коммите данные файлы не присутствуют, они считаются "висячими" и не упаковываются.
497
+
498
+ Остальные файлы — это pack-файл и его индекс. Pack-файл — это файл, который теперь содержит все объекты, которые были удалены. А индекс — это файл, в котором записаны их смещения в pack-файле, что даёт возможность быстро найти нужный объект. Упаковка данных положительно повлияла на общий размер файлов, если до вызова `gc` они занимали примерно 8 Кбайт, то pack-файл занимает всего 4 Кбайт. Упаковкой объектов мы смогли сократить место, занятое на диске, в два раза.
499
+
500
+ Как Git это делает? При упаковке Git ищет файлы, которые похожи по имени и размеру, и сохраняет только разницу между двумя версиями файла. Можно рассмотреть pack-файл подробнее и понять, какие действия были выполнены для сжатия. Для просмотра содержимого упакованного файла существует служебная команда `git verify-pack`:
501
+
502
+ $ git verify-pack -v \
503
+ .git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.idx
504
+ 0155eb4229851634a0f03eb265b69f5a2d56f341 tree 71 76 5400
505
+ 05408d195263d853f09dca71d55116663690c27c blob 12908 3478 874
506
+ 09f01cea547666f58d6a8d809583841a7c6f0130 tree 106 107 5086
507
+ 1a410efbd13591db07496601ebc7a059dd55cfe9 commit 225 151 322
508
+ 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob 10 19 5381
509
+ 3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree 101 105 5211
510
+ 484a59275031909e19aadb7c92262719cfcdf19a commit 226 153 169
511
+ 83baae61804e65cc73a7201a7252750c76066a30 blob 10 19 5362
512
+ 9585191f37f7b0fb9444f35a9bf50de191beadc2 tag 136 127 5476
513
+ 9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e blob 7 18 5193 1 \
514
+ 05408d195263d853f09dca71d55116663690c27c
515
+ ab1afef80fac8e34258ff41fc1b867c702daa24b commit 232 157 12
516
+ cac0cab538b970a37ea1e769cbbde608743bc96d commit 226 154 473
517
+ d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree 36 46 5316
518
+ e3f094f522629ae358806b17daf78246c27c007b blob 1486 734 4352
519
+ f8f51d7d8a1760462eca26eebafde32087499533 tree 106 107 749
520
+ fa49b077972391ad58037050f2a75f74e3671e92 blob 9 18 856
521
+ fdf4fc3344e67ab068f836878b6c4951e3b15f3d commit 177 122 627
522
+ chain length = 1: 1 object
523
+ pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.pack: ok
524
+
525
+ Здесь блоб `9bc1d`, который, как мы помним, был первой версией файла repo.rb, ссылается на блоб `05408`, который был второй его версией. Третья колонка в выводе — это размер содержимого объекта. Как видите, содержимое `05408` занимает 12 Кбайт, при этом содержимое `9bc1d` занимает всего лишь 7 байт. Что интересно, вторая версия сохраняется "как есть", а исходная — в виде дельты. Это из-за того, что необходимость получения доступа к последней версии файла является более вероятной.
526
+
527
+ Также здорово, что переупаковку можно выполнять в любое время. Время от времени Git будет выполнять её автоматически, чтобы сэкономить место на диске. Если вдруг этого недостаточно, всегда можно выполнить `git gc` вручную.
528
+
529
+ ## Спецификации ссылок ##
530
+
531
+ Во всей книге использовались простые связи между ветками в удалённых репозиториях и локальными ветками, но они могут быть и более сложными.
532
+ Предположим, мы добавили следующий удалённый репозиторий:
533
+
534
+ $ git remote add origin git@github.com:schacon/simplegit-progit.git
535
+
536
+ Данный вызов добавляет секцию в файл `.git/config`, в которой заданы имя удалённого репозитория (`origin`), его URL и спецификация ссылок для извлечения данных:
537
+
538
+ [remote "origin"]
539
+ url = git@github.com:schacon/simplegit-progit.git
540
+ fetch = +refs/heads/*:refs/remotes/origin/*
541
+
542
+ Формат спецификации следующий: опциональный `+`, далее пара `<src>:<dst>`, где `<src>` — шаблон ссылок в удалённом репозитории, а `<dst>` — соответствующий шаблон локальных ссылок. Символ `+` сообщает Git'у, что обновление необходимо выполнять даже в том случае, если оно не является перемоткой.
543
+
544
+ В случае настроек по умолчанию, которые записываются во время выполнения `git remote add`, Git выбирает все ссылки из `refs/heads/` на стороне сервера, и записывает их в локальный каталог `refs/remotes/origin/`. Таким образом, если на сервере есть ветка `master`, журнал данной ветки можно получить, вызвав:
545
+
546
+ $ git log origin/master
547
+ $ git log remotes/origin/master
548
+ $ git log refs/remotes/origin/master
549
+
550
+ Все эти команды эквивалентны, так как Git развернёт каждую запись до `refs/remotes/origin/master`.
551
+
552
+ Если хочется, чтобы Git забирал при обновлении только ветку `master`, а не все доступные на сервере, можно изменить соответствующую строку в файле конфигурации на следующее:
553
+
554
+ fetch = +refs/heads/master:refs/remotes/origin/master
555
+
556
+ Данный refspec будет использоваться по умолчанию при вызове `git fetch` для данного удалённого репозитория. Если же вам нужно изменить спецификацию всего раз, можно задать refspec в командной строке. Например, чтобы получить данные из ветки `master` из удалённого репозитория в локальную `origin/mymaster`, можно выполнить
557
+
558
+ $ git fetch origin master:refs/remotes/origin/mymaster
559
+
560
+ Конечно, можно задать несколько спецификаций. Получить данные нескольких веток из командной строки можно так:
561
+
562
+ $ git fetch origin master:refs/remotes/origin/mymaster \
563
+ topic:refs/remotes/origin/topic
564
+ From git@github.com:schacon/simplegit
565
+ ! [rejected] master -> origin/mymaster (non fast forward)
566
+ * [new branch] topic -> origin/topic
567
+
568
+ В данном случае слияние ветки master выполнить не удалось, поскольку слияние не было просто перемоткой. Такое поведение можно изменить, добавив перед спецификацией знак `+`.
569
+
570
+ В конфигурационном файле также можно задавать несколько спецификаций для получения обновлений. Чтобы каждый раз получать обновления веток master и experiment, добавьте две такие строки:
571
+
572
+ [remote "origin"]
573
+ url = git@github.com:schacon/simplegit-progit.git
574
+ fetch = +refs/heads/master:refs/remotes/origin/master
575
+ fetch = +refs/heads/experiment:refs/remotes/origin/experiment
576
+
577
+ Задавать частичные регулярные выражения в спецификации нельзя, следующая запись неверна:
578
+
579
+ fetch = +refs/heads/qa*:refs/remotes/origin/qa*
580
+
581
+ Тем не менее, можно использовать пространства имён для получения похожего результата. Если имеется команда QA (сокр. от quality assurance — контроль качества), которая использует свои несколько веток, и вы хотите получать только ветку master и все ветки команды QA, а остальные — нет, то можно добавить в конфигурацию следующее:
582
+
583
+ [remote "origin"]
584
+ url = git@github.com:schacon/simplegit-progit.git
585
+ fetch = +refs/heads/master:refs/remotes/origin/master
586
+ fetch = +refs/heads/qa/*:refs/remotes/origin/qa/*
587
+
588
+ Если ваш рабочий процесс является сложным, и разные команды: разработчики, тестеры, внедренцы — коммитят в разные ветки одного и того же проекта, то так вы с лёгкостью можете разделить их по разным пространствам имён.
589
+
590
+ ### Спецификации ссылок для команды push ###
591
+
592
+ Это хорошо, что мы научились получать данные по ссылкам в отдельных пространствах имён, но нам же ещё надо сделать так, чтобы команда QA сначала смогла отправить свои ветки в пространство имён `qa/`. Мы решим эту задачу, используя спецификации ссылок для команды `push`.
593
+
594
+ Если разработчик из команды QA хочет отправить изменения из локальной ветки `master` в `qa/master` на удалённом сервере, он может выполнить команду
595
+
596
+ $ git push origin master:refs/heads/qa/master
597
+
598
+ Если хочется, чтобы Git автоматически делал так при вызове `git push origin`, можно добавить в конфигурационный файл значение для `push`:
599
+
600
+ [remote "origin"]
601
+ url = git@github.com:schacon/simplegit-progit.git
602
+ fetch = +refs/heads/*:refs/remotes/origin/*
603
+ push = refs/heads/master:refs/heads/qa/master
604
+
605
+ Опять же, это приведёт к тому, что при вызове `git push origin` локальная ветка `master` будет по умолчанию отправляться в удалённую ветку `qa/master`.
606
+
607
+ ### Удаление ссылок ###
608
+
609
+ Кроме всего прочего, спецификации ссылок можно использовать следующим образом для удаления ссылок на удалённом сервере:
610
+
611
+ $ git push origin :topic
612
+
613
+ Так как спецификация ссылки задаётся в виде `<src>:<dst>`, опускание `<src>` означает, что указанную ветку на удалённом сервере надо сделать пустой, что приводит к её удалению.
614
+
615
+ ## Протоколы передачи ##
616
+
617
+ Git может передавать данные между репозиториями одним из двух основных способов: через HTTP или через "умные" протоколы для транспортов `file://`, `ssh://` и `git://`. В данном разделе мы кратко рассмотрим, как эти два протокола работают.
618
+
619
+ ### Тупой протокол ###
620
+
621
+ Git-транспорт, работающий по HTTP, часто называют "тупым" протоколом, потому что для его работы во время передачи данных не требуется исполнения никакого Git-специфичного кода на стороне сервера. Процесс извлечения данных представляет собой последовательность GET-запросов, клиент обращается к стандартной структуре каталогов Git'а. Давайте рассмотрим процесс получения данных по HTTP на примере библиотеки simplegit:
622
+
623
+ $ git clone http://github.com/schacon/simplegit-progit.git
624
+
625
+ Первое действие, выполняемое данной командой — загрузка файла `info/refs`. Данный файл записывается командой `update-server-info`, поэтому для использования HTTP-транспорта необходимо запускать эту команду в перехватчике `post-receive`:
626
+
627
+ => GET info/refs
628
+ ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
629
+
630
+ Теперь у нас имеется список удалённых веток и их хеши. Далее, нам надо посмотреть, куда ссылается HEAD, чтобы знать на какую версию переключиться после завершения работы команды.
631
+
632
+ => GET HEAD
633
+ ref: refs/heads/master
634
+
635
+ Нам надо переключиться на ветку `master` после завершения процесса.
636
+ На данном этапе можно начать обход дерева. Начальной точкой является объект-коммит `ca82a6`, о чём мы узнали из файла `info/refs`, и мы начинаем с его загрузки:
637
+
638
+ => GET objects/ca/82a6dff817ec66f44342007202690a93763949
639
+ (179 bytes of binary data)
640
+
641
+ Объект получен, он был в рыхлом формате на сервере, и мы получили его по HTTP, используя статический GET-запрос. Теперь можно его разархивировать, отрезать заголовок и посмотреть на его содержимое:
642
+
643
+ $ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
644
+ tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
645
+ parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
646
+ author Scott Chacon <schacon@gmail.com> 1205815931 -0700
647
+ committer Scott Chacon <schacon@gmail.com> 1240030591 -0700
648
+
649
+ changed the version number
650
+
651
+ Далее, необходимо загрузить ещё два объекта: `cfda3b` — объект-дерево, который обозначен как содержимое только что загруженного коммита, и `085bb3` — родительский коммит:
652
+
653
+ => GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
654
+ (179 bytes of data)
655
+
656
+ Так, мы получили следующий объект-коммит. Прихватим и наш объект-дерево:
657
+
658
+ => GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
659
+ (404 - Not Found)
660
+
661
+ Ой! Похоже, этого объекта-дерева нет на сервере в рыхлом формате, поэтому мы получили ответ 404. У этого могут быть две причины: или объект в другом репозитории, или в упакованном файле текущего репозитория. Сперва Git проверяет список альтернативных репозиториев:
662
+
663
+ => GET objects/info/http-alternates
664
+ (empty file)
665
+
666
+ Если бы этот запрос вернул нам список альтернативных URL, Git обратился бы по ним в поиске "рыхлых" и pack-файлов — это такой механизм, позволяющий не дублировать данные проектам, являющимися форками друг для друга. Так как в данном случае альтернативных адресов нет, объект должен быть в pack-файле. Для того чтобы узнать, какие упакованные файлы есть на сервере, необходимо загрузить файл со списком pack-файлов: `objects/info/packs` (который также генерируется `update-server-info`):
667
+
668
+ => GET objects/info/packs
669
+ P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
670
+
671
+ На сервере имеется только один pack-файл, поэтому объект точно там, но необходимо проверить индексный файл, чтобы в этом убедиться. Если бы на сервере было несколько pack-файлов, загрузив сначала индексы, мы смогли бы определить, в каком именно pack-файле находится нужный нам объект:
672
+
673
+ => GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
674
+ (4k of binary data)
675
+
676
+ Теперь, когда мы получили индекс упакованного файла, можно проверить, тут ли наш объект. Это возможно благодаря тому, что в индексе хранятся SHA-1 объектов содержащихся в pack-файле, а также их смещения. Необходимый объект там присутствует, так что продолжим и получим весь pack-файл:
677
+
678
+ => GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
679
+ (13k of binary data)
680
+
681
+ Итак, мы получили наш объект-дерево, можно продолжить обход списка коммитов. Все они лежат внутри упакованного файла, который мы только что скачали, так что снова обращаться к серверу не надо. Git извлекает рабочую копию ветки `master`, на которую ссылается HEAD.
682
+
683
+ Полный вывод этого процесса выглядит так:
684
+
685
+ $ git clone http://github.com/schacon/simplegit-progit.git
686
+ Initialized empty Git repository in /private/tmp/simplegit-progit/.git/
687
+ got ca82a6dff817ec66f44342007202690a93763949
688
+ walk ca82a6dff817ec66f44342007202690a93763949
689
+ got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
690
+ Getting alternates list for http://github.com/schacon/simplegit-progit.git
691
+ Getting pack list for http://github.com/schacon/simplegit-progit.git
692
+ Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835
693
+ Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835
694
+ which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf
695
+ walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
696
+ walk a11bef06a3f659402fe7563abf99ad00de2209e6
697
+
698
+ ### Умный протокол ###
699
+
700
+ Методика работы HTTP проста, но неэффективна, поэтому чаще используются "умные" протоколы. Эти протоколы обслуживаются процессом на стороне сервера, который учитывает особенности работы Git'а — он считывает локальные данные, выясняет, что есть и чего не хватает на клиенте, и генерирует для него соответствующие данные. Существует два набора процессов передачи данных: процессы для загрузки данных и процессы для скачивания.
701
+
702
+ #### Загрузка данных ####
703
+
704
+ Для загрузки данных на удалённый сервер используются процессы `send-pack` и `receive-pack`. Процесс `send-pack` запускается на стороне клиента и подключается к `receive-pack` на стороне сервера.
705
+
706
+ Например, выполняется команда `git push origin master` и `origin` определён как URL использующий протокол SSH. Git запускает процесс `send-pack`, который устанавливает соединение с сервером по протоколу SSH. Он пытается запустить команду на удалённом сервере через вызов команды ssh, который выглядит следующим образом:
707
+
708
+ $ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'"
709
+ 005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs
710
+ 003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic
711
+ 0000
712
+
713
+ Команда `git-receive-pack` тут же посылает в ответ по одной строке на каждую из имеющихся в наличии ссылок — в данном случае только ветку `master` и её SHA. Первая строка также содержит список возможностей сервера (здесь это `report-status` и `delete-refs`).
714
+
715
+ Каждая строка начинается с 4-байтового шестнадцатеричного значения, содержащего длину оставшейся строки. Первая строка начинается с 005b, это 91 в 16-ричном виде, значит в этой строке ещё 91 байт. Следующая строка начинается с 003e, что означает 62, то есть надо прочитать 62 байта. Далее следует строка 0000, которая означает, что сервер закончил листинг своих ссылок.
716
+
717
+ Теперь, когда процесс `send-pack` выяснил состояние сервера, он определяет коммиты, которые есть локально, но которых нет на сервере. Для каждой ссылки, которая будет обновлена текущей командой `push`, процесс `send-pack` передаёт процессу `receive-pack` эти данные. Например, если мы обновляем ветку `master`, и добавляем ветку `experiment`, ответ `send-pack` будет выглядеть следующим образом:
718
+
719
+ 0085ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status
720
+ 00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment
721
+ 0000
722
+
723
+ Значение SHA-1 из одних нулей означает, что раньше здесь ничего не было — так получилось из-за того, что мы добавили новую ссылку `experiment`. Если бы мы удаляли ссылку, было бы на оборот: одни нули были бы справа.
724
+
725
+ Git отправляет строку для каждой ссылки, для которой производится обновление. В строке содержится старый хеш, новый хеш и имя обновляемой ссылки. Первая строка также содержит возможности клиента. Далее, клиент загружает упакованный файл со всеми объектами, которых ещё нет на сервере. В конце, сервер отвечает статусным сообщением сообщающем об успехе (или ошибке):
726
+
727
+ 000Aunpack ok
728
+
729
+ #### Скачивание данных ####
730
+
731
+ Если выполняется скачивание данных, используются процессы `fetch-pack` и `upload-pack`. Клиент запускает процесс `fetch-pack`, который подключается к процессу `upload-pack` на удалённой машине для определения, какие данные будут переданы.
732
+
733
+ Существуют разные способы запуска `upload-pack` на удалённом репозитории. Можно запустить его по SSH так же, как и `receive-pack`. Ещё можно вызвать процесс через Git-демон, по умолчанию принимающий соединения на порте 9418. Процесс `fetch-pack` после подключения отправляет демону данные примерно следующего вида:
734
+
735
+ 003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0
736
+
737
+ Начальные 4 байта задают размер последующих данных, далее следует команда, которую следует запустить, завершаемая нулевым байтом, а потом имя сервера и последний нулевой байт. Git-демон проверяет возможность выполнения команды, а также, что репозиторий существует и имеет необходимые права доступа. Если всё хорошо, демон запускает процесс `upload-pack` и передаёт запрос ему.
738
+
739
+ Если извлечение данных производится по SSH, `fetch-pack` выполняет другие действия:
740
+
741
+ $ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"
742
+
743
+ В обоих случаях, после того как `fetch-pack` подключится, `upload-pack` передаст обратно следующее:
744
+
745
+ 0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \
746
+ side-band side-band-64k ofs-delta shallow no-progress include-tag
747
+ 003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
748
+ 003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic
749
+ 0000
750
+
751
+ Это очень похоже на ответ `receive-pack`, но только возможности другие. Вдобавок `upload-pack` отсылает обратно ссылку HEAD, чтобы клиент понимал, на какую ветку переключиться, если выполняется клонирование.
752
+
753
+ На данном этапе процесс `fetch-pack` смотрит на объекты, имеющиеся в наличии, и для недостающих объектов отвечает словом "want" и за ним SHA объекта. Для уже имеющихся объектов процесс отправляет их хеши со словом "have". В конце списка он пишет "done", и это даёт понять процессу `upload-pack`, что пора начинать отправлять упакованный файл с необходимыми данными:
754
+
755
+ 0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta
756
+ 0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
757
+ 0000
758
+ 0009done
759
+
760
+ Это самый основной случай передачи данных. В более сложных случаях клиент поддерживает функции `multi_ack` или `side-band`, но этот пример иллюстрирует основные взаимодействия, используемые процессами умного протокола.
761
+
762
+ ## Обслуживание и восстановление данных ##
763
+
764
+ Иногда требуется выполнить очистку — сделать репозиторий более компактным, почистить импортированный репозиторий или восстановить потерянную работу. Данный раздел охватывает некоторые из этих сценариев.
765
+
766
+ ### Обслуживание ###
767
+
768
+ Иногда Git сам выполняет команду, запускающую автоматический сборщик мусора. Чаще всего эта команда ничего не делает. Однако, если неупакованных объектов слишком много или у вас слишком много pack-файлов, Git запускает полноценную команду `git gc`. Здесь `gc` это сокращение от "garbage collect", что означает "сборка мусора". Эта команда выполняет несколько действий: собирает все объекты в рыхлом формате и упаковывает их в pack-файлы, объединяет несколько упакованных файлов в один большой, удаляет объекты, недостижимые ни из одного коммита и хранящиеся дольше нескольких месяцев.
769
+
770
+ Вы также можете запустить сборку мусора вручную:
771
+
772
+ $ git gc --auto
773
+
774
+ Опять же, как правило, эта команда ничего не делает. Необходимо иметь 7000 несжатых объектов или более 50 упакованных файлов, чтобы запустился настоящий `gc`. Данные пределы можно изменить с помощью параметров `gc.auto` и `gc.autopacklimit` в конфигурационном файле.
775
+
776
+ Другое действие, выполняемое `gc` — упаковка ссылок в единый файл. Предположим, репозиторий содержит следующие ветки и теги:
777
+
778
+ $ find .git/refs -type f
779
+ .git/refs/heads/experiment
780
+ .git/refs/heads/master
781
+ .git/refs/tags/v1.0
782
+ .git/refs/tags/v1.1
783
+
784
+ Если выполнить `git gc`, данные файлы в каталоге `refs` перестанут существовать. Git перенесёт их в файл `.git/packed-refs` в угоду эффективности. Файл будет иметь следующий вид:
785
+
786
+ $ cat .git/packed-refs
787
+ # pack-refs with: peeled
788
+ cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
789
+ ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
790
+ cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
791
+ 9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
792
+ ^1a410efbd13591db07496601ebc7a059dd55cfe9
793
+
794
+ При обновлении ссылки Git не будет редактировать этот файл, а добавит новый файл в `refs/heads`. Для получения хеша для нужной ссылки Git сначала проверит наличие ссылки в каталоге `refs`, а к файлу `packed-refs` обратится только в случае неудачи. Однако, если в каталоге `refs` файла нет, скорее всего, он в `packed-refs`.
795
+
796
+ Заметьте, последняя строка файла начинается с `^`. Это означает, что метка непосредственно над ней является аннотированной и данная строка — это коммит, на который аннотированная метка указывает.
797
+
798
+ ### Восстановление данных ###
799
+
800
+ В какой-то момент при работе с Git'ом вы нечаянно можете потерять коммит. Как правило, такое случается, когда вы удаляете ветку, в которой находились некоторые наработки, а потом оказывается, что они всё-таки были нужными. Либо вы жёстко сбросили ветку, тем самым отказавшись от коммитов, которые теперь понадобились. Как же в таком случае заполучить свои коммиты обратно?
801
+
802
+ Рассмотрим пример, в котором жёстко сбросим ветку master в тестовом репозитории на какой-нибудь более ранний коммит и затем восстановим потерянные коммиты. Для начала рассмотрим, в каком состоянии находится репозиторий на данном этапе:
803
+
804
+ $ git log --pretty=oneline
805
+ ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
806
+ 484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
807
+ 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
808
+ cac0cab538b970a37ea1e769cbbde608743bc96d second commit
809
+ fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
810
+
811
+ Теперь сдвинем ветку `master` на несколько коммитов назад:
812
+
813
+ $ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
814
+ HEAD is now at 1a410ef third commit
815
+ $ git log --pretty=oneline
816
+ 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
817
+ cac0cab538b970a37ea1e769cbbde608743bc96d second commit
818
+ fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
819
+
820
+ Итак, теперь два последних коммита по-настоящему потеряны — они не достижимы ни из одной ветки. Необходимо найти SHA последнего коммита и создать ветку, указывающую на него. Сложность в том, чтобы найти этот самый SHA последнего коммита, ведь вряд ли вы его запомнили, да?
821
+
822
+ Зачастую самый быстрый способ — использовать инструмент под названием `git reflog`. Во время вашей работы Git записывает все изменения HEAD. Каждый раз при переключении веток и коммите, добавляется запись в reflog. Также обновление производится при вызове `git update-ref`, это, в частности, является причиной необходимости использования этой команды вместо прямой записи значения хеша в ref-файл, как было рассмотрено в разделе "Ссылки в Git" в этой главе. Итак, изменения HEAD в хронологическом порядке можно увидеть, вызвав `git reflog`:
823
+
824
+ $ git reflog
825
+ 1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating HEAD
826
+ ab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEAD
827
+
828
+ Здесь мы видим два коммита, на которых мы когда-то находились, однако информации не так много. Более интересный вывод можно получить, используя `git log -g`, что даст стандартный вывод лога для записей из reflog:
829
+
830
+ $ git log -g
831
+ commit 1a410efbd13591db07496601ebc7a059dd55cfe9
832
+ Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
833
+ Reflog message: updating HEAD
834
+ Author: Scott Chacon <schacon@gmail.com>
835
+ Date: Fri May 22 18:22:37 2009 -0700
836
+
837
+ third commit
838
+
839
+ commit ab1afef80fac8e34258ff41fc1b867c702daa24b
840
+ Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
841
+ Reflog message: updating HEAD
842
+ Author: Scott Chacon <schacon@gmail.com>
843
+ Date: Fri May 22 18:15:24 2009 -0700
844
+
845
+ modified repo a bit
846
+
847
+ Похоже, что нижний коммит — это тот, который мы потеряли, и он может быть восстановлен созданием ветки, указывающей на него. Например, создадим ветку с именем `recover-branch`, указывающую на этот коммит (ab1afef):
848
+
849
+ $ git branch recover-branch ab1afef
850
+ $ git log --pretty=oneline recover-branch
851
+ ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
852
+ 484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
853
+ 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
854
+ cac0cab538b970a37ea1e769cbbde608743bc96d second commit
855
+ fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
856
+
857
+ Здорово, теперь у нас есть ветка `recover-branch`, указывающая туда, куда ранее указывала `master`, и потерянные коммиты вновь доступны.
858
+ Теперь, положим, потерянная ветка по какой-то причине не попала в reflog, для этого удалим восстановленную ветку и весь reflog. Теперь два первых коммита недоступны ниоткуда:
859
+
860
+ $ git branch -D recover-branch
861
+ $ rm -Rf .git/logs/
862
+
863
+ Теперь данные из `.git/logs/` удалены, а значит, и reflog больше нет, так как все его данные находились там. Как восстановить коммиты теперь? Один способ — использовать утилиту `git fsck`, проверяющую базу на целостность. Если выполнить её с ключом `--full`, будут показаны все объекты, недостижимые из других объектов:
864
+
865
+ $ git fsck --full
866
+ dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
867
+ dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
868
+ dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
869
+ dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293
870
+
871
+ В данном случае потерянный коммит указан после слов "dangling commit" (dangling commit в пер. с англ. — "висячий" коммит). Его можно восстановить аналогичным образом, добавив ветку, указывающую на данный хеш.
872
+
873
+ ### Удаление объектов ###
874
+
875
+ У Git'а есть много замечательных особенностей, но одна из них способна вызвать проблемы — команда `git clone` загружает проект вместе со всей историей, включая все версии всех файлов. Это нормально, если в репозитории хранится только исходный код, так как Git хорошо оптимизирован под такой тип данных и может эффективно сжимать их. Однако, если когда-либо в проект был добавлен большой файл, каждый, кто потом захочет клонировать проект, будет вынужден скачивать этот большой файл, даже если он был удалён в следующем же коммите. Он будет в базе всегда, просто потому, что он доступен в истории.
876
+
877
+ Это может стать огромной проблемой при конвертации репозиториев Subversion или Perforce в Git. В данных системах вам не нужно загружать всю историю, поэтому добавление больших бинарных файлов не имеет там особых последствий. Если при импорте из другой системы или при каких-либо других обстоятельствах стало ясно, что ваш репозиторий намного больше, чем он должен быть, то как раз сейчас мы расскажем как можно найти и удалить большие объекты.
878
+
879
+ Будьте внимательны, данный способ разрушителен по отношению к истории коммитов. Каждый коммит будет переписан, начиная с самого раннего, из которого вы удалите ссылку на большой файл. Если сделать это непосредственно после импорта, когда никто ещё не работал с репозиторием, всё хорошо, иначе придётся сообщать всем участникам разработки о необходимости перемещения их правок на новые коммиты.
880
+
881
+ Для примера добавим большой файл в свой тестовый репозиторий, удалим его в следующем коммите, а потом найдём и удалим его полностью из базы. Для начала добавим большой файл в нашу историю:
882
+
883
+ $ curl http://kernel.org/pub/software/scm/git/git-1.6.3.1.tar.bz2 > git.tbz2
884
+ $ git add git.tbz2
885
+ $ git commit -am 'added git tarball'
886
+ [master 6df7640] added git tarball
887
+ 1 files changed, 0 insertions(+), 0 deletions(-)
888
+ create mode 100644 git.tbz2
889
+
890
+ Упс, кажется, этот огромный архив нам в проекте не нужен. Избавимся от него:
891
+
892
+ $ git rm git.tbz2
893
+ rm 'git.tbz2'
894
+ $ git commit -m 'oops - removed large tarball'
895
+ [master da3f30d] oops - removed large tarball
896
+ 1 files changed, 0 insertions(+), 0 deletions(-)
897
+ delete mode 100644 git.tbz2
898
+
899
+ Теперь "соберём мусор" в базе и узнаем её размер:
900
+
901
+ $ git gc
902
+ Counting objects: 21, done.
903
+ Delta compression using 2 threads.
904
+ Compressing objects: 100% (16/16), done.
905
+ Writing objects: 100% (21/21), done.
906
+ Total 21 (delta 3), reused 15 (delta 1)
907
+
908
+ Чтобы быстро узнать, сколько у нас занято места, можно воспользоваться командой `count-objects`:
909
+
910
+ $ git count-objects -v
911
+ count: 4
912
+ size: 16
913
+ in-pack: 21
914
+ packs: 1
915
+ size-pack: 2016
916
+ prune-packable: 0
917
+ garbage: 0
918
+
919
+ Запись `size-pack` — это размер упакованных файлов в килобайтах, то есть всего занято 2 MБ. Перед последним коммитом использовалось около 2 КБ, то есть удаление файла не удалило его из истории. Из-за того, что мы однажды случайно добавили большой файл, при каждом клонировании этого репозитория каждому человеку придётся скачивать все эти 2 МБ, только для того, чтобы получить этот крошечный проект. Попробуем избавиться от этого файла.
920
+
921
+ Сперва найдём его. В данном случае, мы знаем, что это за файл. Но если бы не знали, как можно было бы определить, какие файлы занимают много места? При вызове `git gc` все объекты упаковываются в один файл, но, несмотря на это, определить самые крупные файлы можно, запустив служебную команду `git verify-pack`, и отсортировав её вывод по третьей колонке, в которой записан размер файла. К тому же, так как нас интересуют только самые крупные файлы, оставим только последние несколько строк, направив вывод команде `tail`:
922
+
923
+ $ git verify-pack -v .git/objects/pack/pack-3f8c0...bb.idx | sort -k 3 -n | tail -3
924
+ e3f094f522629ae358806b17daf78246c27c007b blob 1486 734 4667
925
+ 05408d195263d853f09dca71d55116663690c27c blob 12908 3478 1189
926
+ 7a9eb2fba2b1811321254ac360970fc169ba2330 blob 2056716 2056872 5401
927
+
928
+ Большой объект в самом внизу, его размер — 2 МБ. Для того чтобы узнать, что это за файл, воспользуемся командой `rev-list`, которая уже упоминалась в главе 7. Если передать ей ключ `--objects`, то она выдаст хеши всех коммитов, а также хеши объектов и соответствующие им имена файлов. Воспользуемся этим для определения имени выбранного объекта:
929
+
930
+ $ git rev-list --objects --all | grep 7a9eb2fb
931
+ 7a9eb2fba2b1811321254ac360970fc169ba2330 git.tbz2
932
+
933
+ Теперь необходимо удалить данный файл из всех деревьев в прошлом по истории. Легко получить все коммиты, которые меняли данный файл:
934
+
935
+ $ git log --pretty=oneline --branches -- git.tbz2
936
+ da3f30d019005479c99eb4c3406225613985a1db oops - removed large tarball
937
+ 6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 added git tarball
938
+
939
+ Необходимо переписать все коммиты, начиная с `6df76` для полного удаления данного файла. Для этого воспользуемся командой `filter-branch`, которая приводилась в главе 6:
940
+
941
+ $ git filter-branch --index-filter \
942
+ 'git rm --cached --ignore-unmatch git.tbz2' -- 6df7640^..
943
+ Rewrite 6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 (1/2)rm 'git.tbz2'
944
+ Rewrite da3f30d019005479c99eb4c3406225613985a1db (2/2)
945
+ Ref 'refs/heads/master' was rewritten
946
+
947
+ Опция `--index-filter` похожа на `--tree-filter`, использовавшуюся в главе 6, за исключением того, что вместо передачи команды, модифицирующей файлы на диске, мы используем команду, изменяющую файлы в индексе. Вместо удаления файла чем-то вроде `rm file`, стоит сделать это командой `git rm --cached`, так как нам надо удалить файл из индекса, а не с диска. Причина, по которой мы делаем именно так, — скорость. Нет необходимости извлекать каждую ревизию на диск, чтобы применить фильтр, а это может очень сильно ускорить процесс. Можете использовать и `tree-filter` для получения аналогичного результата, если хотите. Опция `--ignore-unmatch` команды `git rm` отключает вывод сообщения об ошибке в случае отсутствия файлов, соответствующих шаблону. И последнее, команда `filter-branch` переписывает историю, начиная с коммита `6df7640`, потому что мы знаем, что именно с этого коммита появилась проблема. По умолчанию перезапись начинается с самого первого коммита, что потребовало бы гораздо больше времени.
948
+
949
+ Теперь наша история не содержит ссылок на данный файл. Однако, в reflog и в новом наборе ссылок, добавленном Git'ом в `.git/refs/original` после выполнения `filter-branch`, ссылки на него всё ещё присутствуют. Поэтому необходимо их удалить, а потом переупаковать базу. Необходимо избавиться от всех возможных ссылок на старые коммиты перед переупаковкой:
950
+
951
+ $ rm -Rf .git/refs/original
952
+ $ rm -Rf .git/logs/
953
+ $ git gc
954
+ Counting objects: 19, done.
955
+ Delta compression using 2 threads.
956
+ Compressing objects: 100% (14/14), done.
957
+ Writing objects: 100% (19/19), done.
958
+ Total 19 (delta 3), reused 16 (delta 1)
959
+
960
+ Посмотрим, сколько места удалось сохранить:
961
+
962
+ $ git count-objects -v
963
+ count: 8
964
+ size: 2040
965
+ in-pack: 19
966
+ packs: 1
967
+ size-pack: 7
968
+ prune-packable: 0
969
+ garbage: 0
970
+
971
+ Размер упакованного репозитория сократился до 7 КБ, что намного лучше, чем 2 МБ. Из значения поля size видно, что большой объект всё ещё хранится в одном из ваших "рыхлых" объектов, но, что самое важное, при любой последующей отправке данных наружу и в том числе при клонировании он передаваться не будет. Если очень хочется, можно удалить его навсегда локально, выполнив `git prune --expire`.
972
+
973
+ ## Итоги ##
974
+
975
+ Теперь вы довольно хорошо понимаете, что Git делает в фоне и, в некоторой степени, как он написан. В данной главе мы рассмотрели несколько служебных команд — простых команд, работающих на более низком уровне, чем обычные пользовательские команды, описанные в остальной части книги. Понимание принципов работы Git'а на низком уровне упрощает понимание работы Git'а в целом и даёт возможность написания собственных утилит и сценариев для организации специфического процесса работы с Git'ом.
976
+
977
+ Git как контентно-адресуемая файловая система — это очень мощный инструмент, который можно использовать не только как систему контроля версий. Надеюсь, полученное знание внутренней реализации Git'а поможет вам в написании ваших собственных интересных приложений, использующих данные технологии, и сделает вашу работу с Git'ом более продвинутой и комфортной.