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,938 @@
1
+ # Настройка Git #
2
+
3
+ До этого момента мы описывали основы того, как Git работает, и как его использовать. Также мы познакомились с несколькими предоставляемыми Git'ом инструментами, которые делают его использование простым и эффективным. В этой главе мы пройдёмся по некоторым действиям, которые вы можете предпринять, чтобы заставить Git работать в нужной именно вам манере. Мы рассмотрим несколько важных настроек и систему перехватчиков (hook). С их помощью легко сделать так, чтобы Git работал именно так, как вам, вашей компании или вашей группе нужно.
4
+
5
+ ## Конфигурирование Git ##
6
+
7
+ В первой главе вкратце было рассказано, как можно изменить настройки Git'а с помощью команды `git config`. Одна из первых вещей, которую мы тогда сделали, это установили свои имя и e-mail адрес:
8
+
9
+ $ git config --global user.name "John Doe"
10
+ $ git config --global user.email johndoe@example.com
11
+
12
+ Теперь мы разберём пару более интересных опций, которые вы можете задать тем же образом, чтобы настроить Git под себя.
13
+
14
+ Мы уже рассмотрели некоторые детали настройки Git'а в первой главе, но давайте сейчас быстренько пройдёмся по ним снова. Git использует набор конфигурационных файлов для задания желаемого нестандартного поведения. Первым местом, в котором Git ищет заданные параметры, является файл `/etc/gitconfig`, содержащий значения, действующие для всех пользователей системы и всех их репозиториев. Когда вы передаёте `git config` опцию `--system`, происходит чтение или запись именно этого файла.
15
+
16
+ Следующее место, в которое Git заглядывает, это файл `~/.gitconfig`, который для каждого пользователя свой. Вы можете заставить Git читать или писать этот файл, передав опцию `--global`.
17
+
18
+ И наконец, Git ищет заданные настройки в конфигурационном файле в Git-каталоге (`.git/config`) того репозитория, который вы используете в данный момент. Значения оттуда относятся к данному конкретному репозиторию. Значения настроек на новом уровне переписывают значения, заданные на предыдущем уровне. Поэтому, например, значения из `.git/config` перебивают значения в `/etc/gitconfig`. Позволяется задавать настройки путём редактирования конфигурационного файла вручную, используя правильный синтаксис, но, как правило, проще воспользоваться командой `git config`.
19
+
20
+ ### Основные настройки клиента ###
21
+
22
+ Настройки конфигурации, поддерживаемые Git'ом, можно разделить на две категории: клиентские и серверные. Большинство опций — клиентские, они задают предпочтения в вашей личной работе. Несмотря на то, что опций доступно великое множество, мы рассмотрим только некоторые из них — те, которые широко используются или значительно влияют на вашу работу. Многие опции полезны только в редких случаях, которые мы не будем здесь рассматривать. Если вы хотите посмотреть список всех опций, которые есть в вашем Git'е, выполните:
23
+
24
+ $ git config --help
25
+
26
+ В странице руководства для `git config` все доступные опции описаны довольно подробно.
27
+
28
+ #### core.editor ####
29
+
30
+ Для создания и редактирования сообщений коммитов и меток Git по умолчанию использует тот редактор, который установлен текстовым редактором по умолчанию в вашей системе, или, как запасной вариант, редактор Vi. Чтобы сменить это умолчание на что-нибудь другое, используйте настройку `core.editor`:
31
+
32
+ $ git config --global core.editor emacs
33
+
34
+ Теперь неважно, что установлено в качестве вашего редактора по умолчанию в переменной оболочки, при редактировании сообщений Git будет запускать Emacs.
35
+
36
+ #### commit.template ####
37
+
38
+ Если установить в этой настройке путь к какому-нибудь файлу в вашей системе, Git будет использовать содержимое этого файла в качестве сообщения по умолчанию при коммите. Например, предположим, что вы создали шаблонный файл `$HOME/.gitmessage.txt`, который выглядит следующим образом:
39
+
40
+ заголовок
41
+
42
+ что произошло
43
+
44
+ [карточка: X]
45
+
46
+ Чтобы попросить Git использовать это в качестве сообщения по умолчанию, которое будет появляться в вашем редакторе при выполнении `git commit`, задайте значение настройки `commit.template`:
47
+
48
+ $ git config --global commit.template $HOME/.gitmessage.txt
49
+ $ git commit
50
+
51
+ После этого, когда во время создания коммита запустится ваш редактор, в нём в качестве сообщения-заглушки будет находиться что-то вроде такого:
52
+
53
+ заголовок
54
+
55
+ что произошло
56
+
57
+ [карточка: X]
58
+ # Please enter the commit message for your changes. Lines starting
59
+ # with '#' will be ignored, and an empty message aborts the commit.
60
+ # On branch master
61
+ # Changes to be committed:
62
+ # (use "git reset HEAD <file>..." to unstage)
63
+ #
64
+ # modified: lib/test.rb
65
+ #
66
+ ~
67
+ ~
68
+ ".git/COMMIT_EDITMSG" 14L, 297C
69
+
70
+ Если у вас существует определённая политика для сообщений коммитов, то задание шаблона, соответствующего этой политике, и настройка Git'а на использование его по умолчанию могут увеличить вероятность того, что этой политики будут придерживаться постоянно.
71
+
72
+ #### core.pager ####
73
+
74
+ Настройка core.pager определяет, какой пейджер использовать при постраничном отображении вывода таких команд, как `log` и `diff`. Вы можете указать здесь `more` или свой любимый пейджер (по умолчанию используется `less`), или можно отключить его, указав пустую строку:
75
+
76
+ $ git config --global core.pager ''
77
+
78
+ Если это выполнить, Git будет выдавать весь вывод полностью для всех команд вне зависимости от того, насколько он большой.
79
+
80
+ #### user.signingkey ####
81
+
82
+ Если вы делаете подписанные аннотированные метки (смотри главу 2), то, чтобы облегчить этот процесс, можно задать свой GPG-ключ для подписи в настройках. Задать ID своего ключа можно так:
83
+
84
+ $ git config --global user.signingkey <id-gpg-ключа>
85
+
86
+ Теперь, чтобы подписать метку, не обязательно каждый раз указывать свой ключ команде `git tag`:
87
+
88
+ $ git tag -s <имя-метки>
89
+
90
+ #### core.excludesfile ####
91
+
92
+ Чтобы Git не видел определённые файлы проекта как неотслеживаемые и не пытался добавить их в индекс при выполнении `git add`, можно задать для них шаблоны в файл `.gitignore`, как это описано главе 2. Однако, если вам необходим другой файл, который будет хранить эти или дополнительные значения вне вашего проекта, то вы можете указать Git'у расположение такого файла с помощью настройки `core.excludesfile`. Просто задайте там путь к файлу, в котором написано то же, что пишется в `.gitignore`.
93
+
94
+ #### help.autocorrect ####
95
+
96
+ Эта опция доступна только в Git 1.6.1 и более поздних. Если вы неправильно наберёте команду в Git'е, он выдаст что-то вроде этого:
97
+
98
+ $ git com
99
+ git: 'com' is not a git-command. See 'git --help'.
100
+
101
+ Did you mean this?
102
+ commit
103
+
104
+ Если установить `help.autocorrect` в 1, Git автоматически запустит нужную команду, если она была единственным вариантом при этом сценарии.
105
+
106
+ ### Цвета в Git ###
107
+
108
+ Git умеет раскрашивать свой вывод для терминала, что может помочь вам быстрее и легче визуально анализировать вывод. Множество опций в настройках помогут вам установить цвета в соответствии со своими предпочтениями.
109
+
110
+ #### color.ui ####
111
+
112
+ Git автоматически раскрасит большую часть своего вывода, если вы его об этом попросите. Вы можете очень тонко задать, что вы хотите раскрасить и как. Но, чтобы просто включить весь предустановленный цветной вывод для терминала, установите `color.ui` в true:
113
+
114
+ $ git config --global color.ui true
115
+
116
+ Когда установлено это значение, Git раскрашивает свой вывод в случае, если вывод идёт на терминал. Другие доступные значения это: false, при котором вывод никогда не раскрашивается, и always, при котором цвета добавляются всегда, даже если вы перенаправляете вывод команд Git'а в файл или через конвейер другой команде.
117
+
118
+ Вам вряд ли понадобится использовать `color.ui = always`. В большинстве случаев, если вам нужны коды цветов в перенаправленном выводе, то вы можете просто передать команде флаг `--color`, чтобы заставить её добавить коды цветов. Настройка `color.ui = true` — это почти всегда именно то, что вам нужно.
119
+
120
+ #### `color.*` ####
121
+
122
+ Если вам необходимо более точно задать какие команды и как должны быть раскрашены, то в Git'е есть возможность задать настройки цветов для каждой команды отдельно. Каждая из этих настроек может быть установлена в `true`, `false` или `always`:
123
+
124
+ color.branch
125
+ color.diff
126
+ color.interactive
127
+ color.status
128
+
129
+ Кроме того, каждая из этих настроек имеет свои поднастройки, которые можно использовать для задания определённого цвета для какой-то части вывода, если вы хотите перезадать цвета. Например, чтобы получить метаинформацию в выводе команды diff в синем цвете с чёрным фоном и жирным шрифтом, выполните
130
+
131
+ $ git config --global color.diff.meta “blue black bold”
132
+
133
+ Цвет может принимать любое из следующих значений: normal, black, red, green, yellow, blue, magenta, cyan и white. Если вы хотите задать атрибут вроде bold, как мы делали в предыдущем примере, то на выбор представлены: bold, dim, ul, blink и reverse.
134
+
135
+ Если вам это интересно, загляните в страницу руководства для `git config`, чтобы узнать обо всех доступных для конфигурации настройках.
136
+
137
+ ### Внешние утилиты merge и diff ###
138
+
139
+ Хоть в Git'е и есть внутренняя реализация diff, которой мы и пользовались до этого момента, вы можете заменить её внешней утилитой. И ещё вы можете установить графическую утилиту для разрешения конфликтов слияния, вместо того, чтобы разрешать конфликты вручную. Мы рассмотрим настройку Perforce Visual Merge Tool (P4Merge) в качестве замены diff и для разрешения конфликтов слияния, потому что это удобная графическая утилита и к тому же бесплатная.
140
+
141
+ Если вам захотелось её попробовать, то P4Merge работает на всех основных платформах, поэтому проблем с ней быть не должно. В примерах мы будем использовать пути к файлам, которые используются на Mac'е и Linux'е; для Windows вам надо заменить `/usr/local/bin` на тот путь к исполняемым файлам, который используется в вашей среде.
142
+
143
+ Скачать P4Merge можно здесь:
144
+
145
+ http://www.perforce.com/product/components/perforce-visual-merge-and-diff-tools
146
+
147
+ Для начала сделаем внешние сценарии-обёртки для запуска нужных команд. Я буду использовать Mac'овский путь к исполняемым файлам; для других систем это будет тот путь, куда установлен ваш файл `p4merge`. Сделайте для слияния сценарий-обёртку с именем `extMerge`, он будет вызывать бинарник со всеми переданными аргументами:
148
+
149
+ $ cat /usr/local/bin/extMerge
150
+ #!/bin/sh
151
+ /Applications/p4merge.app/Contents/MacOS/p4merge $*
152
+
153
+ Обёртка для команды `diff` проверяет, что ей было передано семь аргументов, и передаёт два из них вашему сценарию для слияния. По умолчанию Git передаёт следующие аргументы программе, выполняющей `diff`:
154
+
155
+ путь старый-файл старый-хеш старые-права новый-файл новый-хеш новые-права
156
+
157
+ Так как нам нужны только `старый-файл` и `новый-файл`, воспользуемся сценарием-обёрткой, чтобы передать только те аргументы, которые нам нужны:
158
+
159
+ $ cat /usr/local/bin/extDiff
160
+ #!/bin/sh
161
+ [ $# -eq 7 ] && /usr/local/bin/extMerge "$2" "$5"
162
+
163
+ Ещё следует убедиться, что наши сценарии имеют права на исполнение:
164
+
165
+ $ sudo chmod +x /usr/local/bin/extMerge
166
+ $ sudo chmod +x /usr/local/bin/extDiff
167
+
168
+ Теперь мы можем настроить свой конфигурационный файл на использование наших собственных утилит для разрешения слияний и diff'а. Для этого нам потребуется поменять несколько настроек: `merge.tool`, чтобы указать Git'у на то, какую стратегию использовать; `mergetool.*.cmd`, чтобы указать, как запустить команду; `mergetool.trustExitCode`, чтобы указать Git'у, можно ли по коду возврата определить, было разрешение конфликта слияния успешным или нет; и `diff.external` для того, чтобы задать команду, используемую для diff. Таким образом, вам надо либо выполнить четыре команды `git config`
169
+
170
+ $ git config --global merge.tool extMerge
171
+ $ git config --global mergetool.extMerge.cmd \
172
+ 'extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'
173
+ $ git config --global mergetool.trustExitCode false
174
+ $ git config --global diff.external extDiff
175
+
176
+ либо отредактировать свой файл `~/.gitconfig` и добавить туда следующие строки:
177
+
178
+ [merge]
179
+ tool = extMerge
180
+ [mergetool "extMerge"]
181
+ cmd = extMerge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"
182
+ trustExitCode = false
183
+ [diff]
184
+ external = extDiff
185
+
186
+ Если после того, как всё это настроено, вы выполните команду `diff` следующим образом:
187
+
188
+ $ git diff 32d1776b1^ 32d1776b1
189
+
190
+ то вместо того, чтобы получить вывод команды `diff` в терминал, Git запустит P4Merge, как это показано на рисунке 7-1.
191
+
192
+ Insert 18333fig0701.png
193
+ Рисунок 7-1. P4Merge.
194
+
195
+ Если при попытке слияния двух веток вы получите конфликт, запустите команду `git mergetool` — она запустит графическую утилиту P4Merge, с помощью которой вы сможете разрешить свои конфликты.
196
+
197
+ Что удобно в нашей настройке с обёртками, так это то, что вы с лёгкостью можете поменять утилиты для слияния и diff'а. Например, чтобы изменить свои утилиты `extDiff` и `extMerge` так, чтобы они использовали утилиту KDiff3, всё, что вам надо сделать, это отредактировать свой файл `extMerge`:
198
+
199
+ $ cat /usr/local/bin/extMerge
200
+ #!/bin/sh
201
+ /Applications/kdiff3.app/Contents/MacOS/kdiff3 $*
202
+
203
+ Теперь Git будет использовать утилиту KDiff3 для просмотра diff'ов и разрешения конфликтов слияния.
204
+
205
+ В Git'е уже есть предустановленные настройки для множества других утилит для разрешения слияний, для которых вам не надо полностью прописывать команду для запуска, а достаточно просто указать имя утилиты. К таким утилитам относятся: kdiff3, opendiff, tkdiff, meld, xxdiff, emerge, vimdiff и gvimdiff. Например, если вам неинтересно использовать KDiff3 для diff'ов, а хочется использовать его только для разрешения слияний, и команда `kdiff3` находится в пути, то вы можете выполнить
206
+
207
+ $ git config --global merge.tool kdiff3
208
+
209
+ Если вместо настройки файлов `extMerge` и `extDiff` вы выполните эту команду, Git будет использовать KDiff3 для разрешения слияний и обычный свой инструмент diff для diff'ов.
210
+
211
+ ### Форматирование и пробельные символы ###
212
+
213
+ Проблемы с форматированием и пробельными символами — одни из самых дурацких и трудноуловимых проблем из тех, с которыми сталкиваются многие разработчики при совместной работе над проектами, особенно если разработка ведётся на разных платформах. Очень просто внести малозаметные изменения с помощью пробельных символов при, например, подготовке патчей из-за того, что текстовые редакторы добавляют их без предупреждения, или в кросс-платформенных проектах Windows-программисты добавляют символы возврата каретки в конце изменяемых ими строк. В Git'е есть несколько опций для того, чтобы помочь с решением подобных проблем.
214
+
215
+ #### core.autocrlf ####
216
+
217
+ Если вы пишете код на Windows или пользуетесь другой системой, но работаете с людьми, которые пишут на Windows, то наверняка рано или поздно столкнётесь с проблемой конца строк. Она возникает из-за того, что Windows использует для переноса строк и символ возврата каретки, и символ перехода на новую строку, в то время как в системах Mac и Linux используется только символ перехода на новую строку. Это незначительное, но невероятно раздражающее обстоятельство при кросс-платформенной работе.
218
+
219
+ Git может справиться с этим, автоматически конвертируя CRLF-концы строк в LF при коммите и в обратную сторону при выгрузке кода из репозитория на файловую систему. Данную функциональность можно включить с помощью настройки `core.autocrlf`. Если вы используете Windows, установите настройку в `true`, тогда концы строк из LF будут сконвертированы в CRLF при выгрузке кода:
220
+
221
+ $ git config --global core.autocrlf true
222
+
223
+ Если вы сидите на Linux'е или Mac'е, где используются LF-концы строк, вам не надо, чтобы Git автоматически конвертировал их при выгрузке файлов из репозитория. Однако, если вдруг случайно кто-то добавил файл с CRLF-концами строк, то хотелось бы, чтобы Git исправил это. Можно указать Git'у, чтобы он конвертировал CRLF в LF только при коммитах, установив настройку `core.autocrlf` в `input`:
224
+
225
+ $ git config --global core.autocrlf input
226
+
227
+ Такая настройка даст вам CRLF-концы в выгруженном коде на Windows-системах и LF-концы на Mac'ах и Linux'е, и в репозитории.
228
+
229
+ Если вы Windows-программист, пишущий проект, предназначенный только для Windows, то можете отключить данную функциональность и записывать символы возврата каретки в репозиторий, установив значение настройки в `false`:
230
+
231
+ $ git config --global core.autocrlf false
232
+
233
+ #### core.whitespace ####
234
+
235
+ Git заранее настроен на обнаружение и исправление некоторых проблем, связанных с пробелами. Он может находить четыре основные проблемы с пробелами — две из них по умолчанию отслеживаются, но могут быть выключены, и две по умолчанию не отслеживаются, но их можно включить.
236
+
237
+ Те две настройки, которые включены по умолчанию — это `trailing-space`, которая ищет пробелы в конце строк, и `space-before-tab`, которая ищет пробелы перед символами табуляции в начале строк.
238
+
239
+ Те две, которые по умолчанию выключены, но могут быть включены — это `indent-with-non-tab`, которая ищет строки, начинающиеся с восьми или более пробелов вместо символов табуляции, и `cr-at-eol`, которая сообщает Git'у, что символы возврата каретки в конце строк допустимы.
240
+
241
+ Вы можете указать Git'у, какие из этих настроек вы хотите включить, задав их в `core.whitespace` через запятую. Отключить настройку можно либо опустив её в списке, либо дописав знак `-` перед соответствующим значением. Например, если вы хотите установить все проверки, кроме `cr-at-eol`, то это можно сделать так:
242
+
243
+ $ git config --global core.whitespace \
244
+ trailing-space,space-before-tab,indent-with-non-tab
245
+
246
+ Git будет выявлять эти проблемы при запуске команды `git diff` и пытаться выделить их цветом так, чтобы можно было их исправить ещё до коммита. Кроме того, эти значения будут использоваться, чтобы помочь с применением патчей с помощью `git apply`. Когда будете принимать патч, можете попросить Git предупредить вас о наличии в патче заданных проблем с пробельными символами:
247
+
248
+ $ git apply --whitespace=warn <патч>
249
+
250
+ Или же Git может попытаться автоматически исправить проблему перед применением патча:
251
+
252
+ $ git apply --whitespace=fix <патч>
253
+
254
+ Данные настройки также относятся и к команде `git rebase`. Если вы вдруг сделали коммиты, в которых есть проблемы с пробельными символами, но ещё не отправили их на сервер, запустите `rebase` с опцией `--whitespace=fix`, чтобы Git автоматически исправил ошибки во время переписывания патчей.
255
+
256
+ ### Настройка сервера ###
257
+
258
+ Для серверной части Git'а доступно не так уж много настроек, но среди них есть несколько интересных, на которые следует обратить внимание.
259
+
260
+ #### receive.fsckObjects ####
261
+
262
+ По умолчанию Git не проверяет все отправленные на сервер объекты на целостность. Хотя Git и может проверять, что каждый объект всё ещё совпадает со своей контрольной суммой SHA-1 и указывает на допустимые объекты, по умолчанию Git не делает этого при каждом запуске команды `push`. Эта операция довольно затратна и может значительно увеличить время выполнения `git push` в зависимости от размера репозитория и количества отправляемых данных. Если вы хотите, чтобы Git проверял целостность объектов при каждой отправке данных, сделать это можно, установив `receive.fsckObjects` в true:
263
+
264
+ $ git config --system receive.fsckObjects true
265
+
266
+ Теперь Git, перед тем как принять новые данные от клиента, будет проверять целостность вашего репозитория, чтобы убедиться, что какой-нибудь неисправный клиент не внёс повреждённые данные.
267
+
268
+ #### receive.denyNonFastForwards ####
269
+
270
+ Если вы переместили с помощью команды `rebase` уже отправленные на сервер коммиты и затем пытаетесь отправить их снова или, иначе, пытаетесь отправить коммит в такую удалённую ветку, которая не содержит коммит, на который на текущий момент указывает удалённая ветка — вам будет в этом отказано. Обычно это хорошая стратегия. Но в случае если вы переместили коммиты, хорошо понимая, зачем это вам нужно, вы можете вынудить Git обновить удалённую ветку, передав команде `push` флаг `-f`.
271
+
272
+ Чтобы отключить возможность принудительного обновления веток, задайте `receive.denyNonFastForwards`:
273
+
274
+ $ git config --system receive.denyNonFastForwards true
275
+
276
+ Есть ещё один способ сделать это — с помощью перехватчиков, работающих на приём (receive hooks), на стороне сервера, которые мы рассмотрим вкратце позднее. Такой подход позволит сделать более сложные вещи, такие как, например, запрет принудительных обновлений только для определённой группы пользователей.
277
+
278
+ #### receive.denyDeletes ####
279
+
280
+ Один из способов обойти политику `denyNonFastForwards` — это удалить ветку, а затем отправить новую ссылку на её место. В новых версиях Git'а (начиная с версии 1.6.1) вы можете установить `receive.denyDeletes` в true:
281
+
282
+ $ git config --system receive.denyDeletes true
283
+
284
+ Этим вы запретите удаление веток и меток с помощью команды `push` для всех сразу — ни один из пользователей не сможет этого сделать. Чтобы удалить ветку на сервере, вам придётся удалить файлы ссылок с сервера вручную. Также есть и другие более интересные способы добиться этого, но уже для отдельных пользователей с помощью ACL (списков контроля доступа), как мы увидим в конце этой главы.
285
+
286
+ ## Git-атрибуты ##
287
+
288
+ Некоторые настройки могут быть заданы для отдельных путей, и тогда Git будет применять их только для некоторых подкаталогов или набора файлов. Такие настройки, специфичные по отношению к путям, называются атрибутами и задаются либо в файле `.gitattributes` в одном из каталогов проекта (обычно в корне), либо в файле `.git/info/attributes`, если вы не хотите, чтобы файл с атрибутами попал в коммит вместе с остальными файлами проекта.
289
+
290
+ Использование атрибутов позволяет, например, задать разные стратегии слияния для отдельных файлов или каталогов проекта, или объяснить Git'у, как сравнивать нетекстовые файлы, или сделать так, чтобы Git пропускал данные через фильтр перед тем, как выгрузить или записать данные в репозиторий. В этом разделе мы рассмотрим некоторые из доступных в Git'е атрибутов и рассмотрим несколько практических примеров их использования.
291
+
292
+ ### Бинарные файлы ###
293
+
294
+ Есть один клёвый трюк, для которого можно использовать атрибуты — можно указать Git'у, какие файлы являются бинарными (в случае если по-другому определить это не получается), и дать ему специальные инструкции о том, как с этими файлами работать. Например, некоторые текстовые файлы могут быть машинными — генерируемыми программой — для них нет смысла вычислять дельты, в то время как для некоторых бинарных файлов получение дельт может быть полезным. Дальше мы увидим, как сказать Git'у, какие файлы какие.
295
+
296
+ #### Определение бинарных файлов ####
297
+
298
+ Некоторые файлы выглядят как текстовые, но по существу должны рассматриваться как бинарные данные. Например, проекты Xcode на Mac'ах содержат файл, оканчивающийся на `.pbxproj`, который, по сути, является набором JSON-данных (текстовый формат данных для javascript), записываемым IDE, в котором сохраняются ваши настройки сборки и прочее. Хоть технически это и текстовый файл, потому что содержит только ASCII-символы, но нет смысла рассматривать его как таковой, потому что на самом деле это легковесная база данных — вы не сможете слить её содержимое, если два человека внесут в неё изменение, получение дельт тоже, как правило, ничем вам не поможет. Этот файл предназначается для обработки программой. По сути, лучше рассматривать этот файл как бинарный.
299
+
300
+ Чтобы заставить Git обращаться со всеми `pbxproj`-файлами как с бинарными, добавьте следующую строку в файл `.gitattributes`:
301
+
302
+ *.pbxproj -crlf -diff
303
+
304
+ Теперь Git не будет пытаться конвертировать CRLF-концы строк или исправлять проблемы с ними. Также он не будет пытаться получить дельту для изменений в этом файле при запуске `git show` или `git diff` в вашем проекте. В Git'е есть предустановленный макрос `binary`, который означает то же, что и `-crlf -diff`:
305
+
306
+ *.pbxproj binary
307
+
308
+ #### Получение дельты для бинарных файлов ####
309
+
310
+ Функциональность атрибутов Git'а может быть использована для эффективного получения дельт бинарных файлов. Сделать это можно, объяснив Git'у, как сконвертировать ваши бинарные данные в текстовый формат, для которого можно выполнить сравнение с помощью обычного diff. Осталось только понять, как получить текстовое представление для *бинарных* данных. Идеальный вариант — найти подходящую утилиту для конвертирования нужного формата в текстовый вид. Но, к сожалению, получить хорошее текстовое представление можно только для весьма ограниченного набора бинарных форматов. Для большинства же бинарных форматов, например, для графических или аудио данных, получить читаемый текстовый вид не представляется возможным. Но если мы не можем получить текстовое представление содержимого, мы зачастую можем получить читаемое описание содержимого или метаданные. Метаданные не дают полное представление о содержимом файле, но, во всяком случае, это лучше чем ничего.
311
+
312
+ Далее мы рассмотрим оба подхода на примерах популярных бинарных форматов.
313
+
314
+ Замечание: Существуют разные виды бинарных файлов с текстовым содержимым, для которых вам, может быть, не удастся найти подходящий конвёртер. В данном случае вы можете попробовать вытащить текст с помощью утилиты `strings`. Некоторые из таких файлов могут использовать кодировку UTF-16 или могут быть написаны не в латинице, в таких файлах `strings` не найдёт ничего хорошего. Полезность `strings` может сильно варьироваться. Тем не менее, `strings` доступен на большинстве Mac- и Linux-систем, так что он может быть хорошим первым вариантом для того, чтобы сделать подобное со многими бинарными форматами.
315
+
316
+ ##### Документы MS Word #####
317
+
318
+ Для начала мы используем описанный подход, чтобы решить одну из самых раздражающих проблем, известных человечеству: версионный контроль документов Word. Всем известно, что Word — это самый ужасающий из всех существующих редакторов, но, как ни странно, все им пользуются. Если вы хотите поместить документы Word под версионный контроль, вы можете запихнуть их в Git-репозиторий и время от времени делать коммиты. Но что в этом хорошего? Если вы запустите `git diff` как обычно, то увидите только что-то наподобие этого:
319
+
320
+ $ git diff
321
+ diff --git a/chapter1.doc b/chapter1.doc
322
+ index 88839c4..4afcb7c 100644
323
+ Binary files a/chapter1.doc and b/chapter1.doc differ
324
+
325
+ У вас не получится сравнить две версии между собой, только если вы не выгрузите их обе и просмотрите их вручную, так? Оказывается, можно сделать это достаточно успешно, используя атрибуты Git'а. Поместите следующую строку в свой файл `.gitattributes`:
326
+
327
+ *.doc diff=word
328
+
329
+ Она говорит Git'у, что все файлы, соответствующие указанному шаблону (.doc) должны использовать фильтр "word" при попытке посмотреть дельту с изменениями. Что такое фильтр "word"? Нам нужно его изготовить. Сейчас мы настроим Git на использование программы `catdoc`, специально написанной для того, чтобы вытаскивать текстовую информацию из бинарных документов MS Word (скачать её можно по адресу `http://www.45.free.net/~vitus/software/catdoc/`), для конвертирования документов Word в читаемые текстовые файлы, которые Git затем правильно сравнит:
330
+
331
+ $ git config diff.word.textconv catdoc
332
+
333
+ Этой командой в свой `.git/config` вы добавите следующую секцию:
334
+
335
+ [diff "word"]
336
+ textconv = catdoc
337
+
338
+ Теперь Git знает, что если ему надо найти дельту между двумя снимками состояния, и какие-то их файлы заканчиваются на `.doc`, он должен прогнать эти файлы через фильтр "word", который определён как программа `catdoc`. Так вы фактически сделаете текстовые версии своих Word-файлов перед тем, как получить для них дельту.
339
+
340
+ Рассмотрим пример. Я поместил главу 1 настоящей книги в Git, добавил немного текста в один параграф и сохранил документ. Затем я выполнил `git diff`, чтобы увидеть, что изменилось:
341
+
342
+ $ git diff
343
+ diff --git a/chapter1.doc b/chapter1.doc
344
+ index c1c8a0a..b93c9e4 100644
345
+ --- a/chapter1.doc
346
+ +++ b/chapter1.doc
347
+ @@ -128,7 +128,7 @@ and data size)
348
+ Since its birth in 2005, Git has evolved and matured to be easy to use
349
+ and yet retain these initial qualities. It’s incredibly fast, it’s
350
+ very efficient with large projects, and it has an incredible branching
351
+ -system for non-linear development.
352
+ +system for non-linear development (See Chapter 3).
353
+
354
+ Git коротко и ясно дал мне знать, что я добавил строку "(See Chapter 3)", так оно и есть. Работает идеально.
355
+
356
+ ##### Текстовые файлы в формате OpenDocument #####
357
+
358
+ Тот же подход, который мы использовали для файлов MS Word (`*.doc`), может быть использован и для текстовых файлов в формате OpenDocument, созданных в OpenOffice.org.
359
+
360
+ Добавим следующую строку в файл `.gitattributes`:
361
+
362
+ *.odt diff=odt
363
+
364
+ Теперь настроим фильтр `odt` в `.git/config`:
365
+
366
+ [diff "odt"]
367
+ binary = true
368
+ textconv = /usr/local/bin/odt-to-txt
369
+
370
+ Файлы в формате OpenDocument на самом деле являются запакованными zip'ом каталогами с множеством файлов (содержимое в XML-формате, таблицы стилей, изображения и т.д.). Мы напишем свой сценарий для извлечения содержимого и вывода его в виде обычного текста. Создайте файл `/usr/local/bin/odt-to-txt` (можете создать его в любом другом каталоге) со следующим содержимым:
371
+
372
+ #! /usr/bin/env perl
373
+ # Сценарий для конвертации OpenDocument Text (.odt) в обычный текст.
374
+ # Автор: Philipp Kempgen
375
+
376
+ if (! defined($ARGV[0])) {
377
+ print STDERR "Не задано имя файла!\n";
378
+ print STDERR "Использование: $0 имя файла\n";
379
+ exit 1;
380
+ }
381
+
382
+ my $content = '';
383
+ open my $fh, '-|', 'unzip', '-qq', '-p', $ARGV[0], 'content.xml' or die $!;
384
+ {
385
+ local $/ = undef; # считываем файл целиком
386
+ $content = <$fh>;
387
+ }
388
+ close $fh;
389
+ $_ = $content;
390
+ s/<text:span\b[^>]*>//g; # удаляем span'ы
391
+ s/<text:h\b[^>]*>/\n\n***** /g; # заголовки
392
+ s/<text:list-item\b[^>]*>\s*<text:p\b[^>]*>/\n -- /g; # элементы списков
393
+ s/<text:list\b[^>]*>/\n\n/g; # списки
394
+ s/<text:p\b[^>]*>/\n /g; # параграфы
395
+ s/<[^>]+>//g; # удаляем все XML-теги
396
+ s/\n{2,}/\n\n/g; # удаляем подряд идущие пустые строки
397
+ s/\A\n+//; # удаляем пустые строки в начале
398
+ print "\n", $_, "\n\n";
399
+
400
+ Сделайте его исполняемым
401
+
402
+ chmod +x /usr/local/bin/odt-to-txt
403
+
404
+ Теперь `git diff` сможет сказать вам, что изменилось в `.odt` файлах.
405
+
406
+ ##### Изображения #####
407
+
408
+ Ещё одна интересная проблема, которую можно решить таким способом, это сравнение файлов изображений. Один из способов сделать это — прогнать PNG-файлы через фильтр, извлекающий их EXIF-информацию — метаданные, которые дописываются в большинство форматов изображений. Если скачаете и установите программу `exiftool`, то сможете воспользоваться ею, чтобы извлечь из изображений текстовую информацию о метаданных, так чтобы diff хоть как-то показал вам текстовое представление произошедших изменений:
409
+
410
+ $ echo '*.png diff=exif' >> .gitattributes
411
+ $ git config diff.exif.textconv exiftool
412
+
413
+ Если вы замените в проекте изображение и запустите `git diff`, то получите что-то вроде такого:
414
+
415
+ diff --git a/image.png b/image.png
416
+ index 88839c4..4afcb7c 100644
417
+ --- a/image.png
418
+ +++ b/image.png
419
+ @@ -1,12 +1,12 @@
420
+ ExifTool Version Number : 7.74
421
+ -File Size : 70 kB
422
+ -File Modification Date/Time : 2009:04:17 10:12:35-07:00
423
+ +File Size : 94 kB
424
+ +File Modification Date/Time : 2009:04:21 07:02:43-07:00
425
+ File Type : PNG
426
+ MIME Type : image/png
427
+ -Image Width : 1058
428
+ -Image Height : 889
429
+ +Image Width : 1056
430
+ +Image Height : 827
431
+ Bit Depth : 8
432
+ Color Type : RGB with Alpha
433
+
434
+ Легко можно заметить, что размер файла, а также высота и ширина изображения поменялись.
435
+
436
+ ### Развёртывание ключа ###
437
+
438
+ Разработчики, привыкшие к SVN или CVS, часто хотят получить в Git'е возможность развёртывания ключа в стиле этих систем. Основная проблема с реализацией этой функциональности в Git'е это то, что нельзя записать в файл информацию о коммите после того, как коммит был сделан, так как Git сначала считает контрольную сумму для файла. Несмотря на это, вы можете вставлять текст в файл во время его выгрузки и удалять его перед добавлением в коммит. Атрибуты Git'а предлагают два варианта сделать это.
439
+
440
+ Во-первых, вы можете внедрять SHA-1-сумму блоба в поле `$Id$` в файл автоматически. Если установить соответствующий атрибут для одного или нескольких файлов, то в следующий раз, когда вы будете выгружать данные из этой ветки, Git будет заменять это поле SHA-суммой блоба. Обратите внимание, что это SHA-1 не коммита, а самого блоба.
441
+
442
+ $ echo '*.txt ident' >> .gitattributes
443
+ $ echo '$Id$' > test.txt
444
+ $ git add test.txt
445
+
446
+ В следующий раз, когда вы будете выгружать этот файл, Git автоматически вставит в него SHA его блоба:
447
+
448
+ $ rm test.txt
449
+ $ git checkout -- test.txt
450
+ $ cat test.txt
451
+ $Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $
452
+
453
+ Однако, такой результат мало применим. Если вы раньше пользовались развёртыванием ключа в CVS или Subversion, можете добавлять метку даты — SHA не особенно полезен, так как он довольно случаен, и к тому же, глядя на две SHA-суммы, никак не определить какая из них новее.
454
+
455
+ Как оказывается, можно написать свои собственные фильтры, которые будут делать подстановки в файлах при коммитах и выгрузке файлов. Для этого надо задать фильтры "clean" и "smudge". В файле `.gitattributes` можно задать фильтр для определённых путей и затем установить сценарии, которые будут обрабатывать файлы непосредственно перед выгрузкой ("smudge", см. рис. 7-2) и прямо перед коммитом ("clean", см. рис. 7-3). Эти фильтры можно настроить на совершение абсолютно любых действий.
456
+
457
+ Insert 18333fig0702.png
458
+ Рисунок 7-2. Фильтр “smudge” выполняется при checkout.
459
+
460
+ Insert 18333fig0703.png
461
+ Рисунок 7-3. Фильтр “clean” выполняется при помещении файлов в индекс.
462
+
463
+ В сообщении первоначального коммита, добавляющего эту функциональность, дан простой пример того, как можно пропустить весь свой исходный код на C через программу `indent` перед коммитом. Сделать это можно, задав атрибут filter в файле `.gitattributes` так, чтобы он пропускал файлы `*.c` через фильтр "indent":
464
+
465
+ *.c filter=indent
466
+
467
+ Затем укажите Git'у, что должен делать фильтр "indent" при smudge и clean:
468
+
469
+ $ git config --global filter.indent.clean indent
470
+ $ git config --global filter.indent.smudge cat
471
+
472
+ В нашем случае, когда вы будете делать коммит, содержащий файлы, соответствующие шаблону `*.c`, Git прогонит их через программу `indent` перед коммитом, а потом через программу `cat` перед тем как выгрузить их на диск. Программа `cat`, по сути, является холостой — она выдаёт те же данные, которые получила. Фактически эта комбинация профильтровывает все файлы с исходным кодом на C через `indent` перед тем, как сделать коммит.
473
+
474
+ Ещё один интересный пример — это развёртывание ключа `$Date$` в стиле RCS. Чтобы сделать его правильно, нам понадобится небольшой сценарий, который принимает на вход имя файла, определяет дату последнего коммита в проекте и вставляет эту дату в наш файл. Вот небольшой сценарий на Ruby, который делает именно это:
475
+
476
+ #! /usr/bin/env ruby
477
+ data = STDIN.read
478
+ last_date = `git log --pretty=format:"%ad" -1`
479
+ puts data.gsub('$Date$', '$Date: ' + last_date.to_s + '$')
480
+
481
+ Всё, что делает этот сценарий, это получает дату последнего коммита с помощью команды `git log`, засовывает её во все строки `$Date$`, которые видит в stdin, и выводит результат — такое должно быть несложно реализовать на любом удобном вам языке. Давайте назовём этот файл `expand_date` и поместим в путь. Теперь в Git'е необходимо настроить фильтр (назовём его `dater`) и указать, что надо использовать фильтр `expand_date` при выполнении smudge во время выгрузки файлов. Воспользуемся регулярным выражением Perl, чтобы убрать изменения при коммите:
482
+
483
+ $ git config filter.dater.smudge expand_date
484
+ $ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'
485
+
486
+ Этот фрагмент кода на Perl'е вырезает всё, что находит в строке `$Date$` так, чтобы вернуть всё в начальное состояние. Теперь, когда наш фильтр готов, можете протестировать его, создав файл с ключом `$Date$` и установив для этого файла Git-атрибут, который задействует для него новый фильтр:
487
+
488
+ $ echo '# $Date$' > date_test.txt
489
+ $ echo 'date*.txt filter=dater' >> .gitattributes
490
+
491
+ Если мы сейчас добавим эти изменения в коммит и снова выгрузим файл, то мы увидим, что ключевое слово было заменено правильно:
492
+
493
+ $ git add date_test.txt .gitattributes
494
+ $ git commit -m "Testing date expansion in Git"
495
+ $ rm date_test.txt
496
+ $ git checkout date_test.txt
497
+ $ cat date_test.txt
498
+ # $Date: Tue Apr 21 07:26:52 2009 -0700$
499
+
500
+ Как видите, такая техника может быть весьма мощной для настройки проекта под свои нужды. Но вы должны быть осторожны, ибо файл `.gitattributes` вы добавите в коммит и будете его распространять вместе с проектом, а драйвер (в нашем случае `dater`) — нет. Так что не везде оно будет работать. Когда будете проектировать свои фильтры, постарайтесь сделать так, чтобы при возникновении в них ошибки проект не переставал работать правильно.
501
+
502
+ ### Экспорт репозитория ###
503
+
504
+ Ещё атрибуты в Git'е позволяют делать некоторые интересные вещи при экспортировании архива с проектом.
505
+
506
+ #### export-ignore ####
507
+
508
+ Вы можете попросить Git не экспортировать определённые файлы и каталоги при создании архива. Если у вас есть подкаталог или файл, который вы не желаете включать в архив, но хотите, чтобы в проекте он был, можете установить для такого файла атрибут `export-ignore`.
509
+
510
+ Например, скажем, у вас в подкаталоге `test/` имеются некоторые тестовые файлы, и нет никакого смысла добавлять их в тарбол при экспорте проекта. Тогда добавим следующую строку в файл с Git-атрибутами:
511
+
512
+ test/ export-ignore
513
+
514
+ Теперь, если вы запустите `git archive`, чтобы создать тарбол с проектом, этот каталог в архив включён не будет.
515
+
516
+ #### export-subst ####
517
+
518
+ Ещё одна вещь, которую можно сделать с архивами, — это сделать какую-нибудь простую подстановку ключевых слов. Git позволяет добавить в любой файл строку вида `$Format:$` с любыми кодами форматирования, доступными в `--pretty=format` (многие из этих кодов мы рассматривали в главе 2). Например, если вам захотелось добавить в проект файл с именем `LAST_COMMIT`, в который при запуске `git archive` будет автоматически помещаться дата последнего коммита, то такой файл вы можете сделать следующим образом:
519
+
520
+ $ echo 'Last commit date: $Format:%cd$' > LAST_COMMIT
521
+ $ echo "LAST_COMMIT export-subst" >> .gitattributes
522
+ $ git add LAST_COMMIT .gitattributes
523
+ $ git commit -am 'adding LAST_COMMIT file for archives'
524
+
525
+ После запуска `git archive` этот файл у вас в архиве будет иметь содержимое следующего вида:
526
+
527
+ $ cat LAST_COMMIT
528
+ Last commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$
529
+
530
+ ### Стратегии слияния ###
531
+
532
+ Атрибуты Git'а могут также быть использованы для того, чтобы попросить Git использовать другие стратегии слияния для определённых файлов в проекте. Одна очень полезная возможность — это сказать Git'у, чтобы он не пытался слить некоторые файлы, если для них есть конфликт, а просто выбрал ваш вариант, предпочтя его чужому.
533
+
534
+ Это полезно в том случае, если ветка в вашем проекте разошлась с исходной, но вам всё же хотелось бы иметь возможность слить изменения из неё обратно, проигнорировав некоторые файлы. Скажем, у вас есть файл с настройками базы данных, который называется database.xml, и в двух ветках он разный, и вы хотите влить другую свою ветку, не трогая файл с настройками базы данных. Задайте атрибут следующим образом:
535
+
536
+ database.xml merge=ours
537
+
538
+ При вливании другой ветки, вместо конфликтов слияния для файла database.xml, вы увидите следующее:
539
+
540
+ $ git merge topic
541
+ Auto-merging database.xml
542
+ Merge made by recursive.
543
+
544
+ В данном случае database.xml остался в том варианте, в каком и был изначально.
545
+
546
+ ## Перехватчики в Git ##
547
+
548
+ Как и во многих других системах контроля версий, в Git'е есть возможность запускать собственные сценарии в те моменты, когда происходят некоторые важные действия. Существуют две группы подобных перехватчиков (hook): на стороне клиента и на стороне сервера. Перехватчики на стороне клиента предназначены для клиентских операций, таких как создание коммита и слияние. Перехватчики на стороне сервера нужны для серверных операций, таких как приём отправленных коммитов. Перехватчики могут быть использованы для выполнения самых различных задач. О некоторых из таких задач мы и поговорим.
549
+
550
+ ### Установка перехватчика ###
551
+
552
+ Все перехватчики хранятся в подкаталоге `hooks` в Git-каталоге. В большинстве проектов это `.git/hooks`. По умолчанию Git заполняет этот каталог кучей примеров сценариев, многие из которых полезны сами по себе, но кроме того в них задокументированы входные значения для каждого из сценариев. Все эти примеры являются сценариями для командной оболочки с вкраплениями Perl'а, но вообще-то будет работать любой исполняемый сценарий с правильным именем — вы можете писать их на Ruby или Python или на чём-то ещё, что вам нравится. Эти файлы с примерами перехватчиков оканчиваются на .sample; вам надо их переименовать.
553
+
554
+ Чтобы активировать сценарий-перехватчик, положите файл в подкаталог `hooks` в Git-каталоге, дайте ему правильное имя и права на исполнение. С этого момента он будет вызываться. Основные имена перехватчиков мы сейчас рассмотрим.
555
+
556
+ ### Перехватчики на стороне клиента ###
557
+
558
+ Существует множество перехватчиков, работающих на стороне клиента. В этом разделе они поделены на перехватчики, используемые при работе над коммитами, сценарии, используемые в процессе работы с электронными письмами, и все остальные, работающие на стороне клиента.
559
+
560
+ #### Перехватчики для работы с коммитами ####
561
+
562
+ Первые четыре перехватчика относятся к процессу создания коммита. Перехватчик `pre-commit` запускается первым, ещё до того, как вы наберёте сообщение коммита. Его используют для проверки снимка состояния перед тем, как сделать коммит, чтобы проверить, не забыли ли вы что-нибудь, чтобы убедиться, что вы запустили тесты, или проверить в коде ещё что-нибудь, что вам нужно. Завершение перехватчика с ненулевым кодом прерывает создание коммита, хотя вы можете обойти это с помощью `git commit --no-verify`. Можно, например, проверить стиль кодирования (запускать lint или что-нибудь аналогичное), проверить наличие пробельных символов в конце строк (перехватчик по умолчанию занимается именно этим) или проверить наличие необходимой документации для новых методов.
563
+
564
+ Перехватчик `prepare-commit-msg` запускается до появления редактора с сообщением коммита, но после создания сообщения по умолчанию. Он позволяет отредактировать сообщение по умолчанию перед тем, как автор коммита его увидит. У этого перехватчика есть несколько опций: путь к файлу, в котором сейчас хранится сообщение коммита, тип коммита и SHA-1 коммита (если в коммит вносится правка с помощью `git commit --amend`). Как правило, данный перехватчик не представляет пользы для обычных коммитов; он скорее хорош для коммитов с автогенерируемыми сообщениями, такими как шаблонные сообщения коммитов, коммиты-слияния, уплотнённые коммиты (squashed commits) и коммиты c исправлениями (amended commits). Данный перехватчик можно использовать в связке с шаблоном для коммита, чтобы программно добавлять в него информацию.
565
+
566
+ Перехватчик `commit-msg` принимает один параметр, и снова это путь к временному файлу, содержащему текущее сообщение коммита. Когда сценарий завершается с ненулевым кодом, Git прерывает процесс создания коммита. Так что можно использовать его для проверки состояния проекта или сообщений коммита перед тем, как его одобрить. В последнем разделе главы я продемонстрирую, как использовать данный перехватчик, чтобы проверить, что сообщение коммита соответствует требуемому шаблону.
567
+
568
+ После того, как весь процесс создания коммита завершён, запускается перехватчик `post-commit`. Он не принимает никаких параметров, но вы с лёгкостью можете получить последний коммит, выполнив `git log -1 HEAD`. Как правило, этот сценарий используется для уведомлений или чего-то в этом роде.
569
+
570
+ Сценарии на стороне клиента, предназначенные для запуска во время работы над коммитами, могут быть использованы при осуществлении практически любого типа рабочего процесса. Их часто используют, чтобы обеспечить соблюдение определённых стандартов, хотя важно отметить, что данные сценарии не передаются при клонировании. Вы можете принудить к соблюдению правил на стороне сервера, отвергая присланные коммиты, если они не подчиняются некоторым правилам, но использование данных сценариев на клиентской стороне полностью зависит только от разработчика. Итак, эти сценарии призваны помочь разработчикам, и это обязанность разработчиков установить и сопровождать их, хотя разработчики и имеют возможность в любой момент подменить их или модифицировать.
571
+
572
+ #### Перехватчики для работы с e-mail ####
573
+
574
+ Для рабочих процессов, основанных на электронной почте, есть три специальных клиентских перехватчика. Все они вызываются командой `git am`, так что, если вы не пользуетесь этой командой в процессе своей работы, то можете смело переходить к следующему разделу. Если вы принимаете патчи, отправленные по e-mail и подготовленные с помощью `git format-patch`, то некоторые из них могут оказаться для вас полезными.
575
+
576
+ Первый запускаемый перехватчик — это `applypatch-msg`. Он принимает один аргумент — имя временного файла, содержащего предлагаемое сообщение коммита. Git прерывает наложение патча, если сценарий завершается с ненулевым кодом. Это может быть использовано для того, чтобы убедиться, что сообщение коммита правильно отформатировано или, чтобы нормализовать сообщение, отредактировав его на месте из сценария.
577
+
578
+ Следующий перехватчик, запускаемый во время наложения патчей с помощью `git am` — это `pre-applypatch`. У него нет аргументов, и он запускается после того, как патч наложен, поэтому его можно использовать для проверки снимка состояния перед созданием коммита. Можно запустить тесты или как-то ещё проверить рабочее дерево с помощью этого сценария. Если чего-то не хватает, или тесты не пройдены, выход с ненулевым кодом так же завершает сценарий `git am` без применения патча.
579
+
580
+ Последний перехватчик, запускаемый во время работы `git am` — это `post-applypatch`. Его можно использовать для уведомления группы или автора патча о том, что вы его применили. Этим сценарием процесс наложения патча остановить уже нельзя.
581
+
582
+ #### Другие клиентские перехватчики ####
583
+
584
+ Перехватчик `pre-rebase` запускается перед перемещением чего-либо, и может остановить процесс перемещения, если завершится с ненулевым кодом. Этот перехватчик можно использовать, чтобы запретить перемещение любых уже отправленных коммитов. Пример перехватчика `pre-rebase`, устанавливаемый Git'ом, это и делает, хотя он предполагает, что ветка, в которой вы публикуете свои изменения, называется next. Вам, скорее всего, нужно будет заменить это имя на имя своей публичной стабильной ветки.
585
+
586
+ После успешного выполнения команды `git checkout`, запускается перехватчик `post-checkout`. Его можно использовать для того, чтобы правильно настроить рабочий каталог для своей проектной среды. Под этим может подразумеваться, например, перемещение в каталог больших бинарных файлов, которые вам не хочется включать под версионный контроль, автоматическое генерирование документации или что-то ещё в таком же духе.
587
+
588
+ И наконец, перехватчик `post-merge` запускается после успешного выполнения команды `merge`. Его можно использовать для восстановления в рабочем дереве данных, которые Git не может отследить, таких как информация о правах. Этот перехватчик может также проверить наличие внешних по отношению к контролируемым Git'ом файлов, которые вам нужно скопировать в каталог при изменениях рабочего дерева.
589
+
590
+ ### Перехватчики на стороне сервера ###
591
+
592
+ В дополнение к перехватчикам на стороне клиента вы как системный администратор можете задействовать пару важных перехватчиков на стороне сервера, чтобы навязать в своём проекте правила практически любого вида. Эти сценарии выполняются до и после отправки данных на сервер. Pre-перехватчики могут быть в любое время завершены с ненулевым кодом, чтобы отклонить присланные данные, а также вывести клиенту обратно сообщение об ошибке. Вы можете установить настолько сложные правила приёма данных, насколько захотите.
593
+
594
+ #### pre-receive и post-receive ####
595
+
596
+ Первый сценарий, который выполняется при обработке отправленных клиентом данных, — это `pre-receive`. Он принимает на вход из stdin список отправленных ссылок; если он завершается с ненулевым кодом, ни одна из них не будет принята. Этот перехватчик можно использовать, чтобы, например, убедиться, что ни одна из обновлённых ссылок не выполняет ничего кроме перемотки, или, чтобы убедиться, что пользователь, запустивший `git push`, имеет права на создание, удаление или изменение для всех файлов, модифицируемых этим push'ем.
597
+
598
+ Перехватчик `post-receive` запускается после того, как весь процесс завершился, и может быть использован для обновления других сервисов или уведомления пользователей. Он получает на вход из stdin те же данные, что и перехватчик `pre-receive`. Примерами использования могут быть: отправка писем в рассылку, уведомление сервера непрерывной интеграции или обновление карточки (ticket) в системе отслеживания ошибок — вы можете даже анализировать сообщения коммитов, чтобы выяснить, нужно ли открыть, изменить или закрыть какие-то карточки. Этот сценарий не сможет остановить процесс приёма данных, но клиент не будет отключён до тех пор, пока процесс не завершится; так что будьте осторожны, если хотите сделать что-то, что может занять много времени.
599
+
600
+ #### update ####
601
+
602
+ Сценарий `update` очень похож на сценарий `pre-receive`, за исключением того, что он выполняется для каждой ветки, которую отправитель данных пытается обновить. Если отправитель пытается обновить несколько веток, то `pre-receive` выполнится только один раз, в то время как `update` выполнится по разу для каждой обновляемой ветки. Сценарий не считывает параметры из stdin, а принимает на вход три аргумента: имя ссылки (ветки), SHA-1, на которую ссылка указывала до запуска `push`, и тот SHA-1, который пользователь пытается отправить. Если сценарий `update` завершится с ненулевым кодом, то только одна ссылка будет отклонена, остальные ссылки всё ещё смогут быть обновлены.
603
+
604
+ ## Пример навязывания политики с помощью Git ##
605
+
606
+ В этом разделе мы используем ранее полученные знания для организации в Git'е такого рабочего процесса, который проверяет сообщения коммитов на соответствие заданному формату, из обновлений разрешает только перемотки и позволяет только определённым пользователям изменять определённые подкаталоги внутри проекта. Мы создадим клиентские сценарии, которые помогут разработчикам узнать, будет ли их push отклонён, и серверные сценарии, которые будут действительно вынуждать следовать установленным правилам.
607
+
608
+ Для их написания я использовал Ruby, и потому что это мой любимый язык сценариев, и потому что из всех языков сценариев он больше всего похож на псевдокод; таким образом, код должен быть вам понятен в общих чертах, даже если вы не пользуетесь Ruby. Однако любой язык сгодится. Все примеры перехватчиков, распространяемые вместе с Git'ом, написаны либо на Perl, либо на Bash, так что вы сможете просмотреть достаточно примеров перехватчиков на этих языках, заглянув в примеры.
609
+
610
+ ### Перехватчик на стороне сервера ###
611
+
612
+ Вся работа для сервера будет осуществляться в файле `update` из каталога `hooks`. Файл `update` запускается по разу для каждой отправленной ветки и принимает на вход ссылку, в которую сделано отправление, старую версию, на которой ветка находилась раньше, и новую присланную версию. Кроме того, вам будет доступно имя пользователя, приславшего данные, если `push` был выполнен по SSH. Если вы позволили подключаться всем под одним пользователем (например, "git") с аутентификацией по открытому ключу, то вам может понадобиться создать для этого пользователя обёртку командной оболочки, которая на основе открытого ключа будет определять, какой пользователь осуществил подключение, и записывать этого пользователя в какой-нибудь переменной окружения. Тут я буду предполагать, что имя подключившегося пользователя находится в переменной окружения `$USER`, так что начнём наш сценарий со сбора всей необходимой информации:
613
+
614
+ #!/usr/bin/env ruby
615
+
616
+ $refname = ARGV[0]
617
+ $oldrev = ARGV[1]
618
+ $newrev = ARGV[2]
619
+ $user = ENV['USER']
620
+
621
+ puts "Enforcing Policies... \n(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
622
+
623
+ Да, я использую глобальные переменные. Не судите строго — в таком виде получается нагляднее.
624
+
625
+ #### Установка особого формата сообщений коммитов ####
626
+
627
+ Первая наша задача — это заставить все сообщения коммитов обязательно придерживаться определённого формата. Просто чтобы было чем заняться, предположим, что каждое сообщение должно содержать строку вида "ref: 1234", так как мы хотим, чтобы каждый коммит был связан с некоторым элементом в нашей системе с карточками. Нам необходимо просмотреть все присланные коммиты, выяснить, есть ли такая строка в сообщении коммита, и, если строка отсутствует в каком-либо из этих коммитов, то завершить сценарий с ненулевым кодом, чтобы `push` был отклонён.
628
+
629
+ Список значений SHA-1 для всех присланных коммитов можно получить, взяв значения `$newrev` и `$oldrev` и передав их служебной команде `git rev-list`. По сути, это команда `git log`, но по умолчанию она выводит только SHA-1 значения и больше ничего. Таким образом, чтобы получить список SHA для всех коммитов, сделанных между одним SHA коммита и другим, достаточно выполнить следующее:
630
+
631
+ $ git rev-list 538c33..d14fc7
632
+ d14fc7c847ab946ec39590d87783c69b031bdfb7
633
+ 9f585da4401b0a3999e84113824d15245c13f0be
634
+ 234071a1be950e2a8d078e6141f5cd20c1e61ad3
635
+ dfa04c9ef3d5197182f13fb5b9b1fb7717d2222a
636
+ 17716ec0f1ff5c77eff40b7fe912f9f6cfd0e475
637
+
638
+ Можно взять этот вывод, пройти в цикле по SHA-хешам всех этих коммитов, беря их сообщения и проверяя с помощью регулярного выражения, совпадает ли сообщение с шаблоном.
639
+
640
+ Нам нужно выяснить, как из всех этих коммитов получить их сообщения, для того, чтобы их протестировать. Чтобы получить данные коммита в сыром виде, можно воспользоваться ещё одной служебной командой, которая называется `git cat-file`. Мы рассмотрим все эти служебные команды более подробно в главе 9, но пока что, вот, что эта команда нам выдала:
641
+
642
+ $ git cat-file commit ca82a6
643
+ tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
644
+ parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
645
+ author Scott Chacon <schacon@gmail.com> 1205815931 -0700
646
+ committer Scott Chacon <schacon@gmail.com> 1240030591 -0700
647
+
648
+ changed the version number
649
+
650
+ Простой способ получить сообщение коммита для коммита, чьё значение SHA-1 известно, — это дойти в выводе команды `git cat-file` до первой пустой строки и взять всё, что идёт после неё. В Unix-системах это можно сделать с помощью команды `sed`:
651
+
652
+ $ git cat-file commit ca82a6 | sed '1,/^$/d'
653
+ changed the version number
654
+
655
+ Используйте приведённую ниже абракадабру, чтобы получить для каждого отправленного коммита его сообщение и выйти, если обнаружится, что что-то не соответствует требованиям. Если хотим отклонить отправленные данные, выходим с ненулевым кодом. Весь метод целиком выглядит следующим образом:
656
+
657
+ $regex = /\[ref: (\d+)\]/
658
+
659
+ # принуждает использовать особый формат сообщений
660
+ def check_message_format
661
+ missed_revs = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")
662
+ missed_revs.each do |rev|
663
+ message = `git cat-file commit #{rev} | sed '1,/^$/d'`
664
+ if !$regex.match(message)
665
+ puts "[POLICY] Your message is not formatted correctly"
666
+ exit 1
667
+ end
668
+ end
669
+ end
670
+ check_message_format
671
+
672
+ Добавив это в свой сценарий `update`, мы запретим обновления, содержащие коммиты, сообщения которых не соблюдают наше правило.
673
+
674
+ #### Настройка системы контроля доступа для пользователей ####
675
+
676
+ Предположим, что нам хотелось бы добавить какой-нибудь механизм для использования списков контроля доступа (ACL), где указано, какие пользователи могут отправлять изменения и в какие части проекта. Несколько людей будут иметь полный доступ, а остальные будут иметь доступ на изменение только некоторых подкаталогов или отдельных файлов. Чтобы обеспечить выполнение такой политики, мы запишем правила в файл `acl`, который будет находиться в нашем "голом" репозитории на сервере. Нам нужно будет, чтобы перехватчик `update` брал эти правила, смотрел на то, какие файлы были изменены присланными коммитами, и определял, имеет ли пользователь, выполнивший `push`, право на обновление всех этих файлов.
677
+
678
+ Первое, что мы сделаем, — это напишем свой ACL. Мы сейчас будем использовать формат, очень похожий на механизм ACL в CVS. В нём используется последовательность строк, где первое поле — это `avail` или `unavail`, следующее поле — это разделённый запятыми список пользователей, для которых применяется правило, и последнее поле — это путь, к которому применяется правило (пропуск здесь означает открытый доступ). Все эти поля разделяются вертикальной чертой (`|`).
679
+
680
+ В нашем примере будет несколько администраторов, сколько-то занимающихся написанием документации с доступом к каталогу `doc` и один разработчик, который имеет доступ только к каталогам `lib` и `tests`, и наш файл `acl` будет выглядеть так:
681
+
682
+ avail|nickh,pjhyett,defunkt,tpw
683
+ avail|usinclair,cdickens,ebronte|doc
684
+ avail|schacon|lib
685
+ avail|schacon|tests
686
+
687
+ Начнём со считывания этих данных в какую-нибудь пригодную для использования структуру. В нашем случае, чтобы не усложнять пример, мы будем применять только директивы `avail`. Вот метод, который даёт нам ассоциативный массив, где ключом является имя пользователя, а значением — массив путей, для которых пользователь имеет доступ на запись:
688
+
689
+ def get_acl_access_data(acl_file)
690
+ # считывание данных ACL
691
+ acl_file = File.read(acl_file).split("\n").reject { |line| line == '' }
692
+ access = {}
693
+ acl_file.each do |line|
694
+ avail, users, path = line.split('|')
695
+ next unless avail == 'avail'
696
+ users.split(',').each do |user|
697
+ access[user] ||= []
698
+ access[user] << path
699
+ end
700
+ end
701
+ access
702
+ end
703
+
704
+ Для рассмотренного ранее ACL-файла, метод `get_acl_access_data` вернёт структуру данных следующего вида:
705
+
706
+ {"defunkt"=>[nil],
707
+ "tpw"=>[nil],
708
+ "nickh"=>[nil],
709
+ "pjhyett"=>[nil],
710
+ "schacon"=>["lib", "tests"],
711
+ "cdickens"=>["doc"],
712
+ "usinclair"=>["doc"],
713
+ "ebronte"=>["doc"]}
714
+
715
+ Теперь, когда мы разобрались с правами, нам нужно выяснить, какие пути изменяются присланными коммитами, чтобы можно было убедиться, что пользователь, выполнивший `push`, имеет ко всем ним доступ.
716
+
717
+ Мы довольно легко можем определить, какие файлы были изменены в одном коммите, с помощью опции `--name-only` для команды `git log` (мы упоминали о ней в главе 2):
718
+
719
+ $ git log -1 --name-only --pretty=format:'' 9f585d
720
+
721
+ README
722
+ lib/test.rb
723
+
724
+ Если мы воспользуемся ACL-структурой, полученной из метода `get_acl_access_data`, и сверим её со списком файлов для каждого коммита, то мы сможем определить, имеет ли пользователь право на отправку своих коммитов:
725
+
726
+ # некоторые подкаталоги в проекте разрешено модифицировать только определённым пользователям
727
+ def check_directory_perms
728
+ access = get_acl_access_data('acl')
729
+
730
+ # проверим, что никто не пытается прислать чего-то, что ему нельзя
731
+ new_commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")
732
+ new_commits.each do |rev|
733
+ files_modified = `git log -1 --name-only --pretty=format:'' #{rev}`.split("\n")
734
+ files_modified.each do |path|
735
+ next if path.size == 0
736
+ has_file_access = false
737
+ access[$user].each do |access_path|
738
+ if !access_path || # пользователь имеет полный доступ
739
+ (path.index(access_path) == 0) # доступ к этому пути
740
+ has_file_access = true
741
+ end
742
+ end
743
+ if !has_file_access
744
+ puts "[POLICY] You do not have access to push to #{path}"
745
+ exit 1
746
+ end
747
+ end
748
+ end
749
+ end
750
+
751
+ check_directory_perms
752
+
753
+ Большую часть этого кода должно быть не сложно понять. Мы получаем список присланных на сервер коммитов с помощью `git rev-list`. Затем для каждого из них мы узнаём, какие файлы были изменены, и убеждаемся, что пользователь, сделавший `push`, имеет доступ ко всем изменённым путям. Один Ruby'изм, который может быть непонятен — это `path.index(access_path) == 0`. Это условие верно, если `path` начинается с `access_path` — оно гарантирует, что `access_path` — это не просто один из разрешённых путей, а что каждый путь, к которому запрашивается доступ, начинается с одного из разрешённых путей.
754
+
755
+ Теперь наши пользователи не смогут отправить никаких коммитов с плохо отформатированными сообщениями и не смогут изменить файлы вне предназначенных для них путей.
756
+
757
+ #### Разрешение только обновлений-перемоток ####
758
+
759
+ Единственное, что нам осталось — это оставить доступными только обновления-перемотки. Чтобы добиться этого, можно просто задать настройки `receive.denyDeletes` и `receive.denyNonFastForwards`. Но осуществление этого с помощью перехватчика также будет работать, и к тому же вы сможете изменить его так, чтобы запрет действовал только для определённых пользователей, или ещё как-то, как вам захочется.
760
+
761
+ Логика здесь такая — мы проверяем, есть ли такие коммиты, которые достижимы из старой версии и не достижимы из новой. Если таких нет, то сделанный `push` был перемоткой; в противном случае мы его запрещаем:
762
+
763
+ # разрешаем только обновления-перемотки
764
+ def check_fast_forward
765
+ missed_refs = `git rev-list #{$newrev}..#{$oldrev}`
766
+ missed_ref_count = missed_refs.split("\n").size
767
+ if missed_ref_count > 0
768
+ puts "[POLICY] Cannot push a non fast-forward reference"
769
+ exit 1
770
+ end
771
+ end
772
+
773
+ check_fast_forward
774
+
775
+ Всё готово. Если вы выполните `chmod u+x .git/hooks/update` (а это тот файл, в который вы должны были поместить весь наш код) и затем попытаетесь отправить ссылку, для которой нельзя выполнить перемотку, то вы получите что-то типа такого:
776
+
777
+ $ git push -f origin master
778
+ Counting objects: 5, done.
779
+ Compressing objects: 100% (3/3), done.
780
+ Writing objects: 100% (3/3), 323 bytes, done.
781
+ Total 3 (delta 1), reused 0 (delta 0)
782
+ Unpacking objects: 100% (3/3), done.
783
+ Enforcing Policies...
784
+ (refs/heads/master) (8338c5) (c5b616)
785
+ [POLICY] Cannot push a non fast-forward reference
786
+ error: hooks/update exited with error code 1
787
+ error: hook declined to update refs/heads/master
788
+ To git@gitserver:project.git
789
+ ! [remote rejected] master -> master (hook declined)
790
+ error: failed to push some refs to 'git@gitserver:project.git'
791
+
792
+ Тут есть пара интересных моментов. Во-первых, когда перехватчик начинает свою работу, мы видим это:
793
+
794
+ Enforcing Policies...
795
+ (refs/heads/master) (8338c5) (c56860)
796
+
797
+ Обратите внимание, что мы выводили это в stdout в самом начале нашего сценария `update`. Важно отметить, что всё, что сценарий выводит в stdout, будет передано клиенту.
798
+
799
+ Следующая вещь, которую мы видим, это сообщение об ошибке:
800
+
801
+ [POLICY] Cannot push a non fast-forward reference
802
+ error: hooks/update exited with error code 1
803
+ error: hook declined to update refs/heads/master
804
+
805
+ Первую строку напечатали мы, а в остальных двух Git сообщает, что сценарий `update` завершился с ненулевым кодом, и это именно то, что отклонило ваш `push`. И, наконец, мы видим это:
806
+
807
+ To git@gitserver:project.git
808
+ ! [remote rejected] master -> master (hook declined)
809
+ error: failed to push some refs to 'git@gitserver:project.git'
810
+
811
+ Сообщение "remote rejected" будет появляться для каждой отклонённой перехватчиком ссылки. Оно сообщает нам, что ссылка была отклонена именно из-за сбоя в перехватчике.
812
+
813
+ Кроме того, при отсутствии отметки "ref" в каком-либо из коммитов, вы увидите сообщение об ошибке, которое мы для этого напечатали.
814
+
815
+ [POLICY] Your message is not formatted correctly
816
+
817
+ Или если кто-то попытается отредактировать файл, не имея к нему доступа, то, отправив коммит с этими изменениями, он получит похожее сообщение. Например, если человек, пишущий документацию, попытается отправить коммит, вносящий изменения в файлы каталога `lib`, то увидит:
818
+
819
+ [POLICY] You do not have access to push to lib/test.rb
820
+
821
+ Вот и всё. С этого момента, до тех пор пока сценарий `update` находится на своём месте и имеет права на исполнение, репозиторий никогда не будет откатан назад, в нём никогда не будет коммитов с сообщениями без вашего паттерна, и пользователи будут ограничены в доступе к файлам.
822
+
823
+ ### Перехватчики на стороне клиента ###
824
+
825
+ Обратная сторона такого подхода — это многочисленные жалобы, которые неизбежно появятся, когда отправленные пользователями коммиты будут отклонены. Когда чью-то тщательно оформленную работу отклоняют в последний момент, этот человек может быть сильно расстроен и смущён. Мало того, ему придётся отредактировать свою историю, чтобы откорректировать её, а это обычно не для слабонервных.
826
+
827
+ Решение данной проблемы — предоставить пользователям какие-нибудь перехватчики, которые будут работать на стороне пользователя и будут сообщать ему, если он делает что-то, что, скорее всего, будет отклонено. При таком подходе, пользователи смогут исправить любые проблемы до создания коммита и до того, как эти проблемы станет сложно исправить. Так как перехватчики не пересылаются при клонировании проекта, вам придётся распространять эти сценарии каким-то другим способом и потом сделать так, чтобы ваши пользователи скопировали их в свой каталог `.git/hooks` и сделали их исполняемыми. Эти перехватчики можно поместить в свой проект или даже в отдельный проект, но способа установить их автоматически не существует.
828
+
829
+ Для начала, перед записью каждого коммита нам надо проверить его сообщение, чтобы быть уверенным, что сервер не отклонит изменения из-за плохо отформатированного сообщения коммита. Чтобы сделать это, добавим перехватчик `commit-msg`. Если мы сможем прочитать сообщение из файла, переданного в качестве первого аргумента, и сравнить его с шаблоном, то можно заставить Git прервать создание коммита при обнаружении несовпадения:
830
+
831
+ #!/usr/bin/env ruby
832
+ message_file = ARGV[0]
833
+ message = File.read(message_file)
834
+
835
+ $regex = /\[ref: (\d+)\]/
836
+
837
+ if !$regex.match(message)
838
+ puts "[POLICY] Your message is not formatted correctly"
839
+ exit 1
840
+ end
841
+
842
+ Если этот сценарий находится на своём месте (в `.git/hooks/commit-msg`) и имеет права на исполнение, то при создании коммита с неправильно оформленным сообщением вы увидите это:
843
+
844
+ $ git commit -am 'test'
845
+ [POLICY] Your message is not formatted correctly
846
+
847
+ В этом случае коммит не был завершён. Однако, когда сообщение содержит правильный шаблон, Git позволяет создать коммит:
848
+
849
+ $ git commit -am 'test [ref: 132]'
850
+ [master e05c914] test [ref: 132]
851
+ 1 files changed, 1 insertions(+), 0 deletions(-)
852
+
853
+ Далее мы хотим убедиться, что пользователь не модифицирует файлы вне своей области, заданной в ACL. Если в проекте в каталоге `.git` уже есть копия файла `acl`, который мы использовали ранее, то сценарий `pre-commit` следующего вида применит эти ограничения:
854
+
855
+ #!/usr/bin/env ruby
856
+
857
+ $user = ENV['USER']
858
+
859
+ # [ insert acl_access_data method from above ]
860
+
861
+ # некоторые подкаталоги в проекте разрешено модифицировать только определённым пользователям
862
+ def check_directory_perms
863
+ access = get_acl_access_data('.git/acl')
864
+
865
+ files_modified = `git diff-index --cached --name-only HEAD`.split("\n")
866
+ files_modified.each do |path|
867
+ next if path.size == 0
868
+ has_file_access = false
869
+ access[$user].each do |access_path|
870
+ if !access_path || (path.index(access_path) == 0)
871
+ has_file_access = true
872
+ end
873
+ if !has_file_access
874
+ puts "[POLICY] You do not have access to push to #{path}"
875
+ exit 1
876
+ end
877
+ end
878
+ end
879
+
880
+ check_directory_perms
881
+
882
+ Это примерно тот же сценарий, что и на стороне сервера, но с двумя важными отличиями. Первое — файл `acl` находится в другом месте, так как этот сценарий теперь запускается из рабочего каталога, а не из Git-каталога. Нужно изменить путь к ACL-файлу с этого:
883
+
884
+ access = get_acl_access_data('acl')
885
+
886
+ на этот:
887
+
888
+ access = get_acl_access_data('.git/acl')
889
+
890
+ Другое важное отличие — это способ получения списка изменённых файлов. Так как метод, действующий на стороне сервера, смотрит в лог коммитов, а сейчас коммит ещё не был записан, нам надо получить список файлов из индекса. Вместо
891
+
892
+ files_modified = `git log -1 --name-only --pretty=format:'' #{ref}`
893
+
894
+ мы должны использовать
895
+
896
+ files_modified = `git diff-index --cached --name-only HEAD`
897
+
898
+ Но это единственные два отличия — во всём остальном этот сценарий работает точно так же. Но надо предупредить, что он предполагает, что локально вы работаете под тем же пользователем, от имени которого отправляете изменения на удалённый сервер. Если это не так, то вам необходимо задать переменную `$user` вручную.
899
+
900
+ Последнее, что нам нужно сделать, — это проверить, что пользователь не пытается отправить ссылки не с перемоткой, но это случается не так часто. Чтобы получились ссылки, не являющиеся перемоткой, надо либо переместить ветку за уже отправленный коммит, либо попытаться отправить другую локальную ветку в ту же самую удалённую ветку.
901
+
902
+ Так как сервер в любом случае сообщит вам о том, что нельзя отправлять обновления, не являющиеся перемоткой, а перехватчик запрещает принудительные `push`'и, единственная оплошность, которую вы можете попробовать предотвратить, это перемещение коммитов, которые уже были отправлены на сервер.
903
+
904
+ Вот пример сценария `pre-rebase`, который это проверяет. Он принимает на вход список всех коммитов, которые вы собираетесь переписать, и проверяет, нет ли их в какой-нибудь из ваших удалённых веток. Если найдётся такой коммит, который достижим из одной из удалённых веток, сценарий прервёт выполнение перемещения:
905
+
906
+ #!/usr/bin/env ruby
907
+
908
+ base_branch = ARGV[0]
909
+ if ARGV[1]
910
+ topic_branch = ARGV[1]
911
+ else
912
+ topic_branch = "HEAD"
913
+ end
914
+
915
+ target_shas = `git rev-list #{base_branch}..#{topic_branch}`.split("\n")
916
+ remote_refs = `git branch -r`.split("\n").map { |r| r.strip }
917
+
918
+ target_shas.each do |sha|
919
+ remote_refs.each do |remote_ref|
920
+ shas_pushed = `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}`
921
+ if shas_pushed.split(“\n”).include?(sha)
922
+ puts "[POLICY] Commit #{sha} has already been pushed to #{remote_ref}"
923
+ exit 1
924
+ end
925
+ end
926
+ end
927
+
928
+ Этот сценарий использует синтаксис, который мы не рассматривали в разделе "Выбор ревизии" в главе 6. Мы получили список коммитов, которые уже были отправлены на сервер, выполнив это:
929
+
930
+ git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}
931
+
932
+ Запись `SHA^@` означает всех родителей указанного коммита. Мы ищем какой-нибудь коммит, который достижим из последнего коммита в удалённой ветке и не достижим ни из одного из родителей какого-либо SHA, который вы пытаетесь отправить на сервер — это значит, что это перемотка.
933
+
934
+ Главный недостаток такого подхода — это то, что проверка может быть очень медленной и зачастую избыточной — если вы не пытаетесь отправить данные принудительно с помощью `-f`, сервер и так выдаст предупреждение и не примет данные. Однако, это интересное упражнение и теоретически может помочь вам избежать перемещения, к которому потом придётся вернуться, чтобы исправить.
935
+
936
+ ## Итоги ##
937
+
938
+ Мы рассмотрели большинство основных способов настройки клиента и сервера Git'а с тем, чтобы он был максимально удобен для ваших проектов и при вашей организации рабочего процесса. Мы узнали о всевозможных настройках, атрибутах файлов и о перехватчиках событий, а также рассмотрели пример настройки сервера с соблюдением политики. Теперь вам должно быть по плечу заставить Git подстроиться под практически любой тип рабочего процесса, который можно вообразить.