amp-pure 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (295) hide show
  1. data/.gitignore +1 -0
  2. data/.hgignore +26 -0
  3. data/AUTHORS +2 -0
  4. data/History.txt +6 -0
  5. data/LICENSE +37 -0
  6. data/MANIFESTO +7 -0
  7. data/Manifest.txt +294 -0
  8. data/README.md +116 -0
  9. data/Rakefile +102 -0
  10. data/SCHEDULE.markdown +12 -0
  11. data/STYLE +27 -0
  12. data/TODO.markdown +149 -0
  13. data/ampfile.rb +47 -0
  14. data/bin/amp +30 -0
  15. data/bin/amp1.9 +30 -0
  16. data/ext/amp/bz2/README.txt +39 -0
  17. data/ext/amp/bz2/bz2.c +1582 -0
  18. data/ext/amp/bz2/extconf.rb +77 -0
  19. data/ext/amp/bz2/mkmf.log +29 -0
  20. data/ext/amp/mercurial_patch/extconf.rb +5 -0
  21. data/ext/amp/mercurial_patch/mpatch.c +405 -0
  22. data/ext/amp/priority_queue/extconf.rb +5 -0
  23. data/ext/amp/priority_queue/priority_queue.c +947 -0
  24. data/ext/amp/support/extconf.rb +5 -0
  25. data/ext/amp/support/support.c +250 -0
  26. data/lib/amp.rb +200 -0
  27. data/lib/amp/commands/command.rb +507 -0
  28. data/lib/amp/commands/command_support.rb +137 -0
  29. data/lib/amp/commands/commands/config.rb +143 -0
  30. data/lib/amp/commands/commands/help.rb +29 -0
  31. data/lib/amp/commands/commands/init.rb +10 -0
  32. data/lib/amp/commands/commands/templates.rb +137 -0
  33. data/lib/amp/commands/commands/version.rb +7 -0
  34. data/lib/amp/commands/commands/workflow.rb +28 -0
  35. data/lib/amp/commands/commands/workflows/git/add.rb +65 -0
  36. data/lib/amp/commands/commands/workflows/git/copy.rb +27 -0
  37. data/lib/amp/commands/commands/workflows/git/mv.rb +23 -0
  38. data/lib/amp/commands/commands/workflows/git/rm.rb +60 -0
  39. data/lib/amp/commands/commands/workflows/hg/add.rb +53 -0
  40. data/lib/amp/commands/commands/workflows/hg/addremove.rb +86 -0
  41. data/lib/amp/commands/commands/workflows/hg/annotate.rb +46 -0
  42. data/lib/amp/commands/commands/workflows/hg/archive.rb +126 -0
  43. data/lib/amp/commands/commands/workflows/hg/branch.rb +28 -0
  44. data/lib/amp/commands/commands/workflows/hg/branches.rb +30 -0
  45. data/lib/amp/commands/commands/workflows/hg/bundle.rb +115 -0
  46. data/lib/amp/commands/commands/workflows/hg/clone.rb +95 -0
  47. data/lib/amp/commands/commands/workflows/hg/commit.rb +42 -0
  48. data/lib/amp/commands/commands/workflows/hg/copy.rb +31 -0
  49. data/lib/amp/commands/commands/workflows/hg/debug/dirstate.rb +32 -0
  50. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +36 -0
  51. data/lib/amp/commands/commands/workflows/hg/default.rb +9 -0
  52. data/lib/amp/commands/commands/workflows/hg/diff.rb +30 -0
  53. data/lib/amp/commands/commands/workflows/hg/forget.rb +11 -0
  54. data/lib/amp/commands/commands/workflows/hg/heads.rb +25 -0
  55. data/lib/amp/commands/commands/workflows/hg/identify.rb +23 -0
  56. data/lib/amp/commands/commands/workflows/hg/import.rb +135 -0
  57. data/lib/amp/commands/commands/workflows/hg/incoming.rb +85 -0
  58. data/lib/amp/commands/commands/workflows/hg/info.rb +18 -0
  59. data/lib/amp/commands/commands/workflows/hg/log.rb +21 -0
  60. data/lib/amp/commands/commands/workflows/hg/manifest.rb +13 -0
  61. data/lib/amp/commands/commands/workflows/hg/merge.rb +53 -0
  62. data/lib/amp/commands/commands/workflows/hg/move.rb +28 -0
  63. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +61 -0
  64. data/lib/amp/commands/commands/workflows/hg/pull.rb +74 -0
  65. data/lib/amp/commands/commands/workflows/hg/push.rb +20 -0
  66. data/lib/amp/commands/commands/workflows/hg/remove.rb +45 -0
  67. data/lib/amp/commands/commands/workflows/hg/resolve.rb +83 -0
  68. data/lib/amp/commands/commands/workflows/hg/revert.rb +53 -0
  69. data/lib/amp/commands/commands/workflows/hg/root.rb +13 -0
  70. data/lib/amp/commands/commands/workflows/hg/serve.rb +38 -0
  71. data/lib/amp/commands/commands/workflows/hg/status.rb +116 -0
  72. data/lib/amp/commands/commands/workflows/hg/tag.rb +69 -0
  73. data/lib/amp/commands/commands/workflows/hg/tags.rb +27 -0
  74. data/lib/amp/commands/commands/workflows/hg/tip.rb +13 -0
  75. data/lib/amp/commands/commands/workflows/hg/update.rb +27 -0
  76. data/lib/amp/commands/commands/workflows/hg/verify.rb +9 -0
  77. data/lib/amp/commands/commands/workflows/hg/view.rb +36 -0
  78. data/lib/amp/commands/dispatch.rb +181 -0
  79. data/lib/amp/commands/hooks.rb +81 -0
  80. data/lib/amp/dependencies/amp_support.rb +1 -0
  81. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +103 -0
  82. data/lib/amp/dependencies/minitar.rb +979 -0
  83. data/lib/amp/dependencies/priority_queue.rb +18 -0
  84. data/lib/amp/dependencies/priority_queue/c_priority_queue.rb +1 -0
  85. data/lib/amp/dependencies/priority_queue/poor_priority_queue.rb +46 -0
  86. data/lib/amp/dependencies/priority_queue/ruby_priority_queue.rb +525 -0
  87. data/lib/amp/dependencies/python_config.rb +211 -0
  88. data/lib/amp/dependencies/trollop.rb +713 -0
  89. data/lib/amp/dependencies/zip/ioextras.rb +155 -0
  90. data/lib/amp/dependencies/zip/stdrubyext.rb +111 -0
  91. data/lib/amp/dependencies/zip/tempfile_bugfixed.rb +186 -0
  92. data/lib/amp/dependencies/zip/zip.rb +1850 -0
  93. data/lib/amp/dependencies/zip/zipfilesystem.rb +609 -0
  94. data/lib/amp/dependencies/zip/ziprequire.rb +90 -0
  95. data/lib/amp/encoding/base85.rb +97 -0
  96. data/lib/amp/encoding/binary_diff.rb +82 -0
  97. data/lib/amp/encoding/difflib.rb +166 -0
  98. data/lib/amp/encoding/mercurial_diff.rb +378 -0
  99. data/lib/amp/encoding/mercurial_patch.rb +1 -0
  100. data/lib/amp/encoding/patch.rb +292 -0
  101. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +123 -0
  102. data/lib/amp/extensions/ditz.rb +41 -0
  103. data/lib/amp/extensions/lighthouse.rb +167 -0
  104. data/lib/amp/graphs/ancestor.rb +147 -0
  105. data/lib/amp/graphs/copies.rb +261 -0
  106. data/lib/amp/merges/merge_state.rb +164 -0
  107. data/lib/amp/merges/merge_ui.rb +322 -0
  108. data/lib/amp/merges/simple_merge.rb +450 -0
  109. data/lib/amp/profiling_hacks.rb +36 -0
  110. data/lib/amp/repository/branch_manager.rb +234 -0
  111. data/lib/amp/repository/dir_state.rb +950 -0
  112. data/lib/amp/repository/journal.rb +203 -0
  113. data/lib/amp/repository/lock.rb +207 -0
  114. data/lib/amp/repository/repositories/bundle_repository.rb +214 -0
  115. data/lib/amp/repository/repositories/http_repository.rb +377 -0
  116. data/lib/amp/repository/repositories/local_repository.rb +2661 -0
  117. data/lib/amp/repository/repository.rb +94 -0
  118. data/lib/amp/repository/store.rb +485 -0
  119. data/lib/amp/repository/tag_manager.rb +319 -0
  120. data/lib/amp/repository/updatable.rb +532 -0
  121. data/lib/amp/repository/verification.rb +431 -0
  122. data/lib/amp/repository/versioned_file.rb +475 -0
  123. data/lib/amp/revlogs/bundle_revlogs.rb +246 -0
  124. data/lib/amp/revlogs/changegroup.rb +217 -0
  125. data/lib/amp/revlogs/changelog.rb +338 -0
  126. data/lib/amp/revlogs/changeset.rb +521 -0
  127. data/lib/amp/revlogs/file_log.rb +165 -0
  128. data/lib/amp/revlogs/index.rb +493 -0
  129. data/lib/amp/revlogs/manifest.rb +195 -0
  130. data/lib/amp/revlogs/node.rb +18 -0
  131. data/lib/amp/revlogs/revlog.rb +1032 -0
  132. data/lib/amp/revlogs/revlog_support.rb +126 -0
  133. data/lib/amp/server/amp_user.rb +44 -0
  134. data/lib/amp/server/extension/amp_extension.rb +396 -0
  135. data/lib/amp/server/extension/authorization.rb +201 -0
  136. data/lib/amp/server/fancy_http_server.rb +252 -0
  137. data/lib/amp/server/fancy_views/_browser.haml +28 -0
  138. data/lib/amp/server/fancy_views/_diff_file.haml +13 -0
  139. data/lib/amp/server/fancy_views/_navbar.haml +17 -0
  140. data/lib/amp/server/fancy_views/changeset.haml +31 -0
  141. data/lib/amp/server/fancy_views/commits.haml +32 -0
  142. data/lib/amp/server/fancy_views/file.haml +35 -0
  143. data/lib/amp/server/fancy_views/file_diff.haml +23 -0
  144. data/lib/amp/server/fancy_views/harshcss/all_hallows_eve.css +72 -0
  145. data/lib/amp/server/fancy_views/harshcss/amy.css +147 -0
  146. data/lib/amp/server/fancy_views/harshcss/twilight.css +138 -0
  147. data/lib/amp/server/fancy_views/stylesheet.sass +175 -0
  148. data/lib/amp/server/http_server.rb +140 -0
  149. data/lib/amp/server/repo_user_management.rb +287 -0
  150. data/lib/amp/support/amp_config.rb +164 -0
  151. data/lib/amp/support/amp_ui.rb +287 -0
  152. data/lib/amp/support/docs.rb +54 -0
  153. data/lib/amp/support/generator.rb +78 -0
  154. data/lib/amp/support/ignore.rb +144 -0
  155. data/lib/amp/support/loaders.rb +93 -0
  156. data/lib/amp/support/logger.rb +103 -0
  157. data/lib/amp/support/match.rb +151 -0
  158. data/lib/amp/support/multi_io.rb +87 -0
  159. data/lib/amp/support/openers.rb +121 -0
  160. data/lib/amp/support/ruby_19_compatibility.rb +66 -0
  161. data/lib/amp/support/support.rb +1095 -0
  162. data/lib/amp/templates/blank.commit.erb +23 -0
  163. data/lib/amp/templates/blank.log.erb +18 -0
  164. data/lib/amp/templates/default.commit.erb +23 -0
  165. data/lib/amp/templates/default.log.erb +26 -0
  166. data/lib/amp/templates/template.rb +165 -0
  167. data/site/Rakefile +24 -0
  168. data/site/src/about/ampfile.haml +57 -0
  169. data/site/src/about/commands.haml +106 -0
  170. data/site/src/about/index.haml +33 -0
  171. data/site/src/about/performance.haml +31 -0
  172. data/site/src/about/workflows.haml +34 -0
  173. data/site/src/contribute/index.haml +65 -0
  174. data/site/src/contribute/style.haml +297 -0
  175. data/site/src/css/active4d.css +114 -0
  176. data/site/src/css/all_hallows_eve.css +72 -0
  177. data/site/src/css/all_themes.css +3299 -0
  178. data/site/src/css/amp.css +260 -0
  179. data/site/src/css/amy.css +147 -0
  180. data/site/src/css/blackboard.css +88 -0
  181. data/site/src/css/brilliance_black.css +605 -0
  182. data/site/src/css/brilliance_dull.css +599 -0
  183. data/site/src/css/cobalt.css +149 -0
  184. data/site/src/css/cur_amp.css +185 -0
  185. data/site/src/css/dawn.css +121 -0
  186. data/site/src/css/eiffel.css +121 -0
  187. data/site/src/css/espresso_libre.css +109 -0
  188. data/site/src/css/idle.css +62 -0
  189. data/site/src/css/iplastic.css +80 -0
  190. data/site/src/css/lazy.css +73 -0
  191. data/site/src/css/mac_classic.css +123 -0
  192. data/site/src/css/magicwb_amiga.css +104 -0
  193. data/site/src/css/pastels_on_dark.css +188 -0
  194. data/site/src/css/reset.css +55 -0
  195. data/site/src/css/slush_poppies.css +85 -0
  196. data/site/src/css/spacecadet.css +51 -0
  197. data/site/src/css/sunburst.css +180 -0
  198. data/site/src/css/twilight.css +137 -0
  199. data/site/src/css/zenburnesque.css +91 -0
  200. data/site/src/get/index.haml +32 -0
  201. data/site/src/helpers.rb +121 -0
  202. data/site/src/images/amp_logo.png +0 -0
  203. data/site/src/images/carbonica.png +0 -0
  204. data/site/src/images/revolution.png +0 -0
  205. data/site/src/images/tab-bg.png +0 -0
  206. data/site/src/images/tab-sliding-left.png +0 -0
  207. data/site/src/images/tab-sliding-right.png +0 -0
  208. data/site/src/include/_footer.haml +22 -0
  209. data/site/src/include/_header.haml +17 -0
  210. data/site/src/index.haml +104 -0
  211. data/site/src/learn/index.haml +46 -0
  212. data/site/src/scripts/jquery-1.3.2.min.js +19 -0
  213. data/site/src/scripts/jquery.cookie.js +96 -0
  214. data/tasks/stats.rake +155 -0
  215. data/tasks/yard.rake +171 -0
  216. data/test/dirstate_tests/dirstate +0 -0
  217. data/test/dirstate_tests/hgrc +5 -0
  218. data/test/dirstate_tests/test_dir_state.rb +192 -0
  219. data/test/functional_tests/resources/.hgignore +2 -0
  220. data/test/functional_tests/resources/STYLE.txt +25 -0
  221. data/test/functional_tests/resources/command.rb +372 -0
  222. data/test/functional_tests/resources/commands/annotate.rb +57 -0
  223. data/test/functional_tests/resources/commands/experimental/lolcats.rb +17 -0
  224. data/test/functional_tests/resources/commands/heads.rb +22 -0
  225. data/test/functional_tests/resources/commands/manifest.rb +12 -0
  226. data/test/functional_tests/resources/commands/status.rb +90 -0
  227. data/test/functional_tests/resources/version2/.hgignore +5 -0
  228. data/test/functional_tests/resources/version2/STYLE.txt +25 -0
  229. data/test/functional_tests/resources/version2/command.rb +372 -0
  230. data/test/functional_tests/resources/version2/commands/annotate.rb +45 -0
  231. data/test/functional_tests/resources/version2/commands/experimental/lolcats.rb +17 -0
  232. data/test/functional_tests/resources/version2/commands/heads.rb +22 -0
  233. data/test/functional_tests/resources/version2/commands/manifest.rb +12 -0
  234. data/test/functional_tests/resources/version2/commands/status.rb +90 -0
  235. data/test/functional_tests/resources/version3/.hgignore +5 -0
  236. data/test/functional_tests/resources/version3/STYLE.txt +31 -0
  237. data/test/functional_tests/resources/version3/command.rb +376 -0
  238. data/test/functional_tests/resources/version3/commands/annotate.rb +45 -0
  239. data/test/functional_tests/resources/version3/commands/experimental/lolcats.rb +17 -0
  240. data/test/functional_tests/resources/version3/commands/heads.rb +22 -0
  241. data/test/functional_tests/resources/version3/commands/manifest.rb +12 -0
  242. data/test/functional_tests/resources/version3/commands/status.rb +90 -0
  243. data/test/functional_tests/resources/version4/.hgignore +5 -0
  244. data/test/functional_tests/resources/version4/STYLE.txt +31 -0
  245. data/test/functional_tests/resources/version4/command.rb +376 -0
  246. data/test/functional_tests/resources/version4/commands/experimental/lolcats.rb +17 -0
  247. data/test/functional_tests/resources/version4/commands/heads.rb +22 -0
  248. data/test/functional_tests/resources/version4/commands/manifest.rb +12 -0
  249. data/test/functional_tests/resources/version4/commands/stats.rb +25 -0
  250. data/test/functional_tests/resources/version4/commands/status.rb +90 -0
  251. data/test/functional_tests/resources/version5_1/.hgignore +5 -0
  252. data/test/functional_tests/resources/version5_1/STYLE.txt +2 -0
  253. data/test/functional_tests/resources/version5_1/command.rb +374 -0
  254. data/test/functional_tests/resources/version5_1/commands/experimental/lolcats.rb +17 -0
  255. data/test/functional_tests/resources/version5_1/commands/heads.rb +22 -0
  256. data/test/functional_tests/resources/version5_1/commands/manifest.rb +12 -0
  257. data/test/functional_tests/resources/version5_1/commands/stats.rb +25 -0
  258. data/test/functional_tests/resources/version5_1/commands/status.rb +90 -0
  259. data/test/functional_tests/resources/version5_2/.hgignore +5 -0
  260. data/test/functional_tests/resources/version5_2/STYLE.txt +14 -0
  261. data/test/functional_tests/resources/version5_2/command.rb +376 -0
  262. data/test/functional_tests/resources/version5_2/commands/experimental/lolcats.rb +17 -0
  263. data/test/functional_tests/resources/version5_2/commands/manifest.rb +12 -0
  264. data/test/functional_tests/resources/version5_2/commands/newz.rb +12 -0
  265. data/test/functional_tests/resources/version5_2/commands/stats.rb +25 -0
  266. data/test/functional_tests/resources/version5_2/commands/status.rb +90 -0
  267. data/test/functional_tests/test_functional.rb +604 -0
  268. data/test/localrepo_tests/test_local_repo.rb +121 -0
  269. data/test/localrepo_tests/testrepo.tar.gz +0 -0
  270. data/test/manifest_tests/00manifest.i +0 -0
  271. data/test/manifest_tests/test_manifest.rb +72 -0
  272. data/test/merge_tests/base.txt +10 -0
  273. data/test/merge_tests/expected.local.txt +16 -0
  274. data/test/merge_tests/local.txt +11 -0
  275. data/test/merge_tests/remote.txt +11 -0
  276. data/test/merge_tests/test_merge.rb +26 -0
  277. data/test/revlog_tests/00changelog.i +0 -0
  278. data/test/revlog_tests/revision_added_changelog.i +0 -0
  279. data/test/revlog_tests/test_adding_index.i +0 -0
  280. data/test/revlog_tests/test_revlog.rb +333 -0
  281. data/test/revlog_tests/testindex.i +0 -0
  282. data/test/store_tests/store.tar.gz +0 -0
  283. data/test/store_tests/test_fncache_store.rb +122 -0
  284. data/test/test_amp.rb +9 -0
  285. data/test/test_base85.rb +14 -0
  286. data/test/test_bdiff.rb +42 -0
  287. data/test/test_commands.rb +122 -0
  288. data/test/test_difflib.rb +50 -0
  289. data/test/test_helper.rb +15 -0
  290. data/test/test_journal.rb +29 -0
  291. data/test/test_match.rb +134 -0
  292. data/test/test_mdiff.rb +74 -0
  293. data/test/test_mpatch.rb +14 -0
  294. data/test/test_support.rb +24 -0
  295. metadata +382 -0
@@ -0,0 +1,234 @@
1
+ module Amp
2
+ module Repositories
3
+ ##
4
+ # = BranchManager
5
+ # Michael Scott for Amp.
6
+ #
7
+ # More seriously, this class handles reading/writing to the branch cache
8
+ # and figuring out what the head revisions are for each branch and such.
9
+ module BranchManager
10
+ include Amp::RevlogSupport::Node
11
+
12
+ ##
13
+ # Saves the branches with the given "partial" and the last_rev index.
14
+ def save_branch_cache(partial, last_rev)
15
+ tiprev = self.size - 1
16
+ # If our cache is outdated, then update it and re-write it
17
+ if last_rev != tiprev
18
+ # search for new heads
19
+ update_branches!(partial, last_rev+1, tiprev+1)
20
+ # write our data out
21
+ write_branches!(partial, self.changelog.tip, tiprev)
22
+ end
23
+ # return our mappings
24
+ partial
25
+ end
26
+
27
+ ##
28
+ # Loads the head revisions for each branch. Each branch has at least one, but
29
+ # possible more than one, head.
30
+ #
31
+ # @return [Hash] a map, where the branch names are keys and the values
32
+ # are the heads of the branch
33
+ def branch_heads
34
+ # Gets the mapping of branch names to branch heads, but uses caching to avoid
35
+ # doing IO and tedious computation over and over. As long as our tip doesn't
36
+ # change, the cache will remain valid.
37
+
38
+ # Check our current tip
39
+ tip = self.changelog.tip
40
+ # Do we have a cache, and if we do, is the saved cache == tip?
41
+ if !@branch_cache.nil? && @branch_cache_tip == tip
42
+ # if so, cache HIT
43
+ return @branch_cache
44
+ end
45
+
46
+ # nope? cache miss
47
+ # save the old tip
48
+ oldtip = @branch_cache_tip
49
+ # save the new tip
50
+ @branch_cache_tip = tip
51
+
52
+ # reset the branch cache
53
+ @branch_cache = @branch_cache.nil? ? {} : @branch_cache.clear # returns same hash, but empty
54
+ # if we didn't have an old cache, or it was invalid, read in the branches again
55
+ if oldtip.nil? || self.changelog.node_map[oldtip].nil?
56
+ partial, last, last_rev = read_branches!
57
+ else
58
+ # Otherwise, dig up the cached hash!
59
+ last_rev = self.changelog.rev(oldtip)
60
+ # Get the last branch cache
61
+ partial = @u_branch_cache
62
+ end
63
+ # Save the branch cache (updating it if we have to)
64
+ save_branch_cache(partial, last_rev)
65
+
66
+ # Cache our saved hash
67
+ @u_branch_cache = partial
68
+
69
+ # Copy the partial into the branch_cache
70
+ partial.each { |k, v| @branch_cache[k] = v }
71
+ @branch_cache
72
+ end
73
+
74
+ # Returns a dict where branch names map to the tipmost head of
75
+ # the branch, open heads come before closed
76
+ def branch_tags
77
+ tags = {}
78
+ branch_heads.each do |branch_node, local_heads|
79
+ head = nil
80
+ local_heads.reverse_each do |h| # get the tip if its a closed
81
+ if !(changelog.read(h)[5].include?("close"))
82
+ head = h
83
+ break
84
+ end
85
+ end
86
+ head = local_heads.last if head.nil?
87
+ tags[branch_node] = head # it's the tip
88
+ end
89
+ return tags
90
+ end
91
+
92
+ ##
93
+ # Saves the branches, the tip-most node, and the tip-most revision
94
+ # to the branch cache.
95
+ #
96
+ def save_branches(branches, tip, tip_revision)
97
+ write_branches!(branches, tip, tip_revision)
98
+ end
99
+
100
+ private
101
+
102
+ ##
103
+ # Reads in the branches from the branch.cache file, stored in the root
104
+ # of the repository's .hg folder. While the repository could figure out
105
+ # what each branch's heads are each time the program is run, that would
106
+ # be quite slow. So we cache them in a file, along with the tip of the
107
+ # repository, so we know if our cache has become inaccurate.
108
+ # The format is very simple:
109
+ # [tip_node_id] [tip_revision_number]
110
+ # [branch_head_node_id] [branch_name]
111
+ # [branch_head_node_id] [branch_name]
112
+ # [branch_head_node_id] [branch_name]
113
+ #
114
+ # Example:
115
+ # 0abc3135810abc3135810abc3135810abc313581 603
116
+ # 0abc3135810abc3135810abc3135810abc313581 default
117
+ # 1234567890123456789012345678901234567890 other_branch
118
+ # 0987654321098765432109876543210987654321 other_branch
119
+ #
120
+ # In the example, other_branch has 2 heads. This is acceptable. The tip of the
121
+ # repository is node 0abc3135, revision 603, which is the only head of the default
122
+ # branch.
123
+ #
124
+ # @return [[Hash, String, Integer]] The results are returned in the form of:
125
+ # [partial, tip_node_id, tip_rev_index], where +partial+ is a mapping of
126
+ # branch names to an array of their heads.
127
+ def read_branches!
128
+ partial, last, last_rev = {}, nil, nil
129
+ lines = nil
130
+ invalid = false
131
+
132
+ begin
133
+ # read in all the lines. This file should be short, so don't worry about
134
+ # performance concerns of a File.read() call (this call is actually
135
+ # Opener#read, which then calls File.read)
136
+ lines = @hg_opener.read("branchheads.cache").split("\n")
137
+ rescue SystemCallError # IO Errors, i.e. if there is no branch.cache file
138
+ return {}, NULL_ID, NULL_REV
139
+ end
140
+ # use catch, not exceptions (exceptions are more costly)
141
+ valid = catch(:invalid) do
142
+ # Read in the tip node and tip revision #
143
+ last, last_rev = lines.shift.split(" ", 2)
144
+ last, last_rev = last.unhexlify, last_rev.to_i
145
+
146
+ # if we aren't matching up with the current repo, then invalidate the cache
147
+ if last_rev > self.size || self[last_rev].node != last
148
+ throw :invalid, false
149
+ end
150
+
151
+ # Go through each next line and read in a head-branch pair
152
+ lines.each do |line|
153
+ # empty = useless line
154
+ next if line.nil? || line.empty?
155
+ # split on " ", only once so we can have a space in a branch name
156
+ node, _label = line.split(" ", 2)
157
+ # and assign to our "partial" i.e. our list of branch-heads
158
+ partial[_label.strip] ||= []
159
+ partial[_label.strip] << node.unhexlify
160
+ end
161
+ end
162
+
163
+ # if invalid was thrown.... bail
164
+ unless valid
165
+ UI.puts("invalidating branch cache (tip different)")
166
+ partial, last, last_rev = {}, NULL_ID, NULL_REV
167
+ end
168
+
169
+ # Return our results!
170
+ [partial, last, last_rev]
171
+ end
172
+
173
+ ##
174
+ # Invalidates the tag cache. Removes all ivars relating to tags.
175
+ def invalidate_branch_cache!
176
+ @branch_cache = nil
177
+ @branch_cache_tip = nil
178
+ @u_branch_cache = nil
179
+ end
180
+
181
+ ##
182
+ # Goes through from revision +start+ to revision +stop+ and searches for
183
+ # new branch heads for each branch. Annoying, yes. But necessary to keep the
184
+ # cache up to date.
185
+ #
186
+ # @param [Hash] partial the current pairing of branch names to heads. Might
187
+ # be incomplete, which is why it's called "partial"
188
+ # @param [Integer] start the revision # to start looking new branch heads
189
+ # @param [Integer] stop the last revision in which to look for branch heads
190
+ def update_branches!(partial, start, stop)
191
+ (start..(stop-1)).each do |r|
192
+ # get the changeset
193
+ changeset = self[r]
194
+ # look at its branch
195
+ branch = changeset.branch
196
+ # get that branch's partial list of heads
197
+
198
+ branch_heads = (partial[branch] ||= [])
199
+ # add this changeset
200
+ branch_heads << changeset.node
201
+ # remove our parents from this branch's list of heads if they're in there,
202
+ # because if they have children, they aren't heads.
203
+ changeset.parents.each do |parent|
204
+ # get the node_id
205
+ parent_node = parent.node
206
+ # remove the parent
207
+ branch_heads.delete parent_node if branch_heads.include? parent_node
208
+ end
209
+ end
210
+ end
211
+
212
+ ##
213
+ # Writes the branches out to the branch cache. Simple as that. See #read_branches!
214
+ # for the file format.
215
+ #
216
+ # @see read_branches!
217
+ # @param [Hash] branches a mapping of branch names to arrays of their head node IDs
218
+ # @param [String] tip the tip of this repository's node ID
219
+ # @param [Integer] tip_revision the index # of this repository's tip
220
+ def write_branches!(branches, tip, tip_revision)
221
+ @hg_opener.open "branchheads.cache", "w" do |f|
222
+ f << "#{tip.hexlify} #{tip_revision}\n"
223
+ branches.each do |_label, nodes|
224
+ nodes.each {|node| f << "#{node.hexlify} #{_label}\n" }
225
+ end
226
+ end
227
+ rescue SystemCallError
228
+
229
+ end
230
+ end
231
+ MichaelScott = BranchManager # hehehe
232
+ end
233
+ end
234
+
@@ -0,0 +1,950 @@
1
+ module Amp
2
+ module Repositories
3
+
4
+ ##
5
+ # An entry in the dirstate. Similar to IndexEntry for revlogs. Simple struct, that's
6
+ # all.
7
+ class DirStateEntry < Struct.new(:status, :mode, :size, :mtime)
8
+
9
+ ##
10
+ # shortcuts!
11
+ def removed?; self.status == :removed; end
12
+ def added?; self.status == :added; end
13
+ def untracked?; self.status == :untracked; end
14
+ def modified?; self.status == :modified; end
15
+ def merged?; self.status == :merged; end
16
+ def normal?; self.status == :normal; end
17
+ def forgotten?; self.status == :forgotten; end
18
+
19
+ ##
20
+ # Do I represent a dirty object?
21
+ #
22
+ # @return [Boolean] does this array represent a dirty object in a DirState?
23
+ def dirty?
24
+ self[-2] == -2 && self[-1] == -1 && self.normal?
25
+ end
26
+
27
+ ##
28
+ # Do I possibly represent a dirty object?
29
+ #
30
+ # @return [Boolean] does this array possibly represent a dirty object in a DirState?
31
+ def maybe_dirty?
32
+ self[-2] == -1 && self[-1] == -1 && self.normal?
33
+ end
34
+ end
35
+
36
+ ##
37
+ # = DirState
38
+ # This class handles parsing and manipulating the "dirstate" file, which is stored
39
+ # in the .hg folder. This file handles which files are marked for addition, removal,
40
+ # copies, and so on. The structure of each entry is below.
41
+ #
42
+ #
43
+ # class DirStateEntry < BitStruct
44
+ # default_options :endian => :network
45
+ #
46
+ # char :status , 8, "the state of the file"
47
+ # signed :mode , 32, "mode"
48
+ # signed :size , 32, "size"
49
+ # signed :mtime , 32, "mtime"
50
+ # signed :fname_size , 32, "filename size"
51
+ #
52
+ # end
53
+ class DirState
54
+ include Ignore
55
+ include RevlogSupport::Node
56
+
57
+ UNKNOWN = DirStateEntry.new(:untracked, 0, 0, 0)
58
+ FORMAT = "cNNNN"
59
+
60
+ class FileNotInRootError < StandardError; end
61
+ class AbsolutePathNeededError < StandardError; end
62
+
63
+ # The parents of the current state. If there's been an uncommitted merge,
64
+ # it will be two. Otherwise it will just be one parent and +NULL_ID+
65
+ attr_reader :parents
66
+
67
+ # The number of directories in each base ["dir" => #_of_dirs]
68
+ attr_reader :dirs
69
+
70
+ # The files mapped to their stats (state, mode, size, mtime)
71
+ # [state, mode, size, mtime]
72
+ attr_reader :files
73
+
74
+ # A map of files to be copied, because we want to preserve their history
75
+ # "source" => "dest"
76
+ attr_reader :copy_map
77
+
78
+ # I still don't know what this does
79
+ attr_reader :folds
80
+
81
+ # The conglomerate config object of global configs and the repo
82
+ # specific config.
83
+ attr_reader :config
84
+
85
+ # The root of the repository
86
+ attr_reader :root
87
+
88
+ # The opener to access files. The only files that will be touched lie
89
+ # in the .hg/ directory, so the default MUST be +:open_hg+.
90
+ attr_reader :opener
91
+
92
+ ##
93
+ # Creates a DirState object. This is used to represent, in memory (and
94
+ # occasionally on file) how the repository is being changed.
95
+ # It's really simple, and it is really the basis for _using_ the repo
96
+ # (contrary to how Revlog is the basis for _saving_ the repo).
97
+ #
98
+ # @param [String] root the absolute path to the root of the repository
99
+ # @param [Amp::AmpConfig] config the config file of hgrc
100
+ # @param [Amp::Opener] opener the opener to open files with
101
+ def initialize(root, config, opener)
102
+ unless root[0, 1] == "/"
103
+ raise AbsolutePathNeededError, "#{root} is not an absolute path!"
104
+ end
105
+
106
+ # root must be an aboslute path with no ending slash
107
+ @root = root[-1, 1] == "/" ? root[0..-2] : root # the root of the repo
108
+ @config = config # the config file where we get defaults
109
+ @opener = opener # opener to retrieve files (default: open_hg)
110
+ @dirty = false # has something changed, and do we need to write?
111
+ @dirty_parents = false
112
+ @parents = [NULL_ID, NULL_ID] # the parent revisions
113
+ @dirs = {} # number of directories in each base ["dir" => #_of_dirs]
114
+ @files = {} # the files mapped to their statistics
115
+ @copy_map = {} # src => dest
116
+ @ignore = [] # dirs and files to ignore
117
+ @folds = []
118
+ @check_exec = nil
119
+ generate_ignore
120
+ end
121
+
122
+ ##
123
+ # Retrieve a file's status from +@files+. If it's not there
124
+ # then return :untracked
125
+ #
126
+ # @param [String] key the path of the file
127
+ # @return [Symbol] status of the file, either :removed, :added, :untracked,
128
+ # :merged, :normal, :forgotten, or :untracked
129
+ def [](key)
130
+ lookup = @files[key]
131
+ lookup || DirStateEntry.new(:untracked)
132
+ end
133
+
134
+ ##
135
+ # Determine if +path+ is a link or an executable.
136
+ #
137
+ # @param [String] path the path to the file
138
+ # @return [String] either 'l' for a link and 'x' for an executable. Returns
139
+ # '' if neither
140
+ def flags(path)
141
+ return 'l' if File.ftype(path) == 'link'
142
+ return 'x' if File.executable? path
143
+ ''
144
+ end
145
+
146
+ ##
147
+ # just a lil' reader to find if the repo is dirty or not
148
+ # by dirty i mean "no longer in sync with the cache"
149
+ #
150
+ # @return [Boolean] is the dirstate no longer in sync with the cache located
151
+ # at .hg/branch.cache
152
+ def dirty?
153
+ @dirty
154
+ end
155
+
156
+ ##
157
+ # The directories and path matches that we're ignoringzorz. It will call
158
+ # the ignorer generated by .hgignore, but only if @ignore_all is nil (really
159
+ # only if @ignore_all isn't a Boolean value, but we set it to nil)
160
+ #
161
+ # @param [String] file the path to the file that will be checked by
162
+ # the .hgignore file
163
+ # @return [Boolean] whether we're ignoring the path or not
164
+ def ignore(file)
165
+ return true if @ignore_all == true
166
+ return false if @ignore_all == false
167
+ @ignore_matches ||= parse_ignore @root, @ignore
168
+ @ignore_matches.call file
169
+ end
170
+
171
+ ##
172
+ # Gets the current branch.
173
+ #
174
+ # @return [String] the current branch in the working directory
175
+ def branch
176
+ text = @opener.read('branch').strip
177
+ @branch ||= text.empty? ? "default" : text
178
+ rescue
179
+ @branch = "default"
180
+ end
181
+
182
+ ##
183
+ # Set the branch to +branch+.
184
+ #
185
+ # @param [#to_s] brnch the branch to switch to
186
+ # @return [String] +brnch+.to_s
187
+ def branch=(brnch)
188
+ @branch = brnch.to_s
189
+
190
+ @opener.open 'branch', 'w' do |f|
191
+ f.puts brnch.to_s
192
+ end
193
+ @branch
194
+ end
195
+
196
+ ##
197
+ # Set the parents to +p+
198
+ #
199
+ # @param [Array<String>] p the parents as binary strings
200
+ # @return [Array<String>] the parents, as will be used by the dirstate
201
+ def parents=(p)
202
+ @parents = if p.is_a? Array
203
+ p.size == 1 ? p + [NULL_ID] : p[0..1]
204
+ else
205
+ [p, NULL_ID]
206
+ end
207
+
208
+ @dirty_parents = true
209
+ @dirty = true
210
+ @parents # return this
211
+ end
212
+ alias_method :parent, :parents
213
+
214
+ ##
215
+ # Set the file as "to be added".
216
+ #
217
+ # @param [String] file the path of the file to add
218
+ # @return [Boolean] a success marker
219
+ def add(file)
220
+ add_path file, true
221
+
222
+ @dirty = true
223
+ @files[file] = DirStateEntry.new(:added, 0, -1, -1)
224
+ @copy_map.delete file
225
+ true # success
226
+ end
227
+
228
+ ##
229
+ # Set the file as "normal", meaning no changes. This is the same
230
+ # as dirstate.normal in dirstate.py, for those referencing both.
231
+ #
232
+ # @param [String] file the path of the file to clean
233
+ # @return [Boolean] a success marker
234
+ def normal(file)
235
+ @dirty = true
236
+ add_path file, true
237
+
238
+ f = File.lstat "#{@root}/#{file}"
239
+ @files[file] = DirStateEntry.new(:normal, f.mode, f.size, f.mtime.to_i)
240
+ @copy_map.delete file
241
+ true # success
242
+ end
243
+ alias_method :clean, :normal
244
+
245
+ ##
246
+ # Set the file as normal, but possibly dirty. It's like when you
247
+ # meet a cool girl, and she seems really innocent and it's a chance
248
+ # for you to maybe change yourself and make a new friend, but then
249
+ # she *might* actually be a total slut. Better milk that grapevine
250
+ # to find out the truth. Oddly specific, huh.
251
+ #
252
+ # THUS IS THE HISTORY OF THIS METHOD!
253
+ #
254
+ # And then one day you go to the movies with some other girl, and the
255
+ # original crazy slutty girl is the cashier next to you. Unsure of
256
+ # what to do, you don't do anything. Next thing you know, she's trying
257
+ # to get your attention to say hey. WTF? Anyone know what's up with this
258
+ # girl?
259
+ #
260
+ # After milking that grapevine, you find out that she's not a great person.
261
+ # There's nothing interesting there and you should just move on.
262
+ #
263
+ # *sigh* girls.
264
+ #
265
+ # @param [String] file the path of the file to mark
266
+ # @return [Boolean] a success marker
267
+ def maybe_dirty(file)
268
+ if @files[file] && @parents.last != NULL_ID
269
+ # if there's a merge happening and the file was either modified
270
+ # or dirty before being removed, restore that state.
271
+ # I'm quoting the python with that one.
272
+ # I guess what it's saying is that if a file is being removed
273
+ # by a merge, but it was altered somehow beforehand on the local
274
+ # repo, then play it safe and bring back the dead. Divine intervention
275
+ # on the side of the local repo.
276
+
277
+ # info here is a standard array of info
278
+ # [action, mode, size, mtime]
279
+ info = @files[file]
280
+
281
+ if info.removed? and [-1, -2].member? info.size
282
+ source = @copy_map[file]
283
+
284
+ # do the appropriate action
285
+ case info.size
286
+ when -1 # either merge it
287
+ merge file
288
+ when -2 # or mark it as dirty
289
+ dirty file
290
+ end
291
+
292
+ copy source => file if source
293
+ return
294
+ end
295
+
296
+ # next step... the base case!
297
+ return true if info.modified? || info.maybe_dirty? and info.size == -2
298
+ end
299
+
300
+ @dirty = true # make the repo dirty
301
+ add_path file # add the file
302
+
303
+ @files[file] = DirStateEntry.new(:normal, 0, -1, -1) # give it info
304
+ @copy_map.delete file # we're not copying it since we're adding it
305
+ true # success
306
+ end
307
+
308
+ ##
309
+ # Checks whether the dirstate is tracking the given file.
310
+ #
311
+ # @param f the file to check for
312
+ # @return [Boolean] whether or not the file is being tracked.
313
+ def include?(path)
314
+ not @files[path].nil?
315
+ end
316
+ alias_method :tracking?, :include?
317
+
318
+ ##
319
+ # Mark the file as "dirty"
320
+ #
321
+ # @param [String] file the path of the file to mark
322
+ # @return [Boolean] a success marker
323
+ def dirty(file)
324
+ @dirty = true
325
+ add_path file
326
+
327
+ @files[file] = DirStateEntry.new(:normal, 0, -2, -1)
328
+ @copy_map.delete file
329
+ true # success
330
+ end
331
+
332
+ ##
333
+ # Set the file as "to be removed"
334
+ #
335
+ # @param [String] file the path of the file to remove
336
+ # @return [Boolean] a success marker
337
+ def remove(file)
338
+ @dirty = true
339
+ drop_path file
340
+
341
+ size = 0
342
+ if @parents.last.null? && (info = @files[file])
343
+ if info.merged?
344
+ size = -1
345
+ elsif info.normal? && info.size == -2
346
+ size = -2
347
+ end
348
+ end
349
+ @files[file] = DirStateEntry.new(:removed, 0, size, 0)
350
+ @copy_map.delete file if size.zero?
351
+ true # success
352
+ end
353
+
354
+ ##
355
+ # Prepare the file to be merged
356
+ #
357
+ # @param [String] file the path of the file to merge
358
+ # @return [Boolean] a success marker
359
+ def merge(file)
360
+ @dirty = true
361
+ add_path file
362
+
363
+ stats = File.lstat "#{@root}/#{file}"
364
+ add_path file
365
+ @files[file] = DirStateEntry.new(:merged, stats.mode, stats.size, stats.mtime.to_i)
366
+ @copy_map.delete file
367
+ true # success
368
+ end
369
+
370
+ ##
371
+ # Forget the file, erase it from the repo
372
+ #
373
+ # @param [String] file the path of the file to forget
374
+ # @return [Boolean] a success marker
375
+ def forget(file)
376
+ @dirty = true
377
+ drop_path file
378
+ @files.delete file
379
+ true # success
380
+ end
381
+
382
+ ##
383
+ # Invalidates the dirstate, making it completely unusable until it is
384
+ # re-read. Should only be used in error situations.
385
+ def invalidate!
386
+ %w(@files @copy_map @folds @branch @parents @dirs @ignore).each do |ivar|
387
+ instance_variable_set(ivar, nil)
388
+ end
389
+ @dirty = false
390
+ end
391
+
392
+ ##
393
+ # Refresh the directory's state, making everything empty.
394
+ # Called by #rebuild.
395
+ #
396
+ # This is not the same as #initialize, so we can't just run
397
+ # `send :initialize` and call it a day :-(
398
+ #
399
+ # @return [Boolean] a success marker
400
+ def clear
401
+ @files = {}
402
+ @dirs = {}
403
+ @copy_map = {}
404
+ @parents = [NULL_ID, NULL_ID]
405
+ @dirty = true
406
+
407
+ true # success
408
+ end
409
+
410
+ ##
411
+ # Rebuild the directory's state. Needs Manifest, as that's
412
+ # what the files really are.
413
+ #
414
+ # @param [String] parent the binary format of the parent
415
+ # @param [ManifestEntry] files the files in a specific revision
416
+ # @return [Boolean] a success marker
417
+ def rebuild(parent, files)
418
+ clear
419
+
420
+ # alter each file according to its flags
421
+ files.each do |f|
422
+ mode = files.flags(f).include?('x') ? 0777 : 0666
423
+ @files[f] = DirStateEntry.new(:normal, mode, -1, 0)
424
+ end
425
+
426
+ @parents = [parent, NULL_ID]
427
+ @dirty_parents = true
428
+ true # success
429
+ end
430
+
431
+ ##
432
+ # Save the data to .hg/dirstate.
433
+ # Uses mode: "w", so it overwrites everything
434
+ #
435
+ # @todo watch memory usage - +si+ could grow unrestrictedly which would
436
+ # bog down the entire program
437
+ # @return [Boolean] a success marker
438
+ def write
439
+ return true unless @dirty
440
+ begin
441
+ @opener.open "dirstate", 'w' do |state|
442
+ gran = @config['dirstate']['granularity'] || 1 # self._ui.config('dirstate', 'granularity', 1)
443
+
444
+ limit = 2147483647 # sorry for the literal use...
445
+ limit = state.mtime - gran if gran > 0
446
+
447
+ si = StringIO.new "", (ruby_19? ? "w+:ASCII-8BIT" : "w+")
448
+ si.write @parents.join
449
+
450
+ @files.each do |file, info|
451
+ file = file.dup # so we don't corrupt vars
452
+ info = info.dup.to_a # UNLIKE PYTHON
453
+ info[0] = info[0].to_hg_int
454
+
455
+ # I should probably do mah physics hw. nah, i'll do it
456
+ # tomorrow during my break
457
+ # good news - i did pretty well on my physics test by using
458
+ # brian ford's name instead of my own.
459
+ file = "#{file}\0#{@copy_map[file]}" if @copy_map[file]
460
+ info = [info[0], 0, (-1).to_signed(32), (-1).to_signed(32)] if info[3].to_i > limit.to_i and info[0] == :normal
461
+ info << file.size # the final element to make it pass, which is the length of the filename
462
+ info = info.pack FORMAT # pack them their lunch
463
+ si.write info # and send them off
464
+ si.write file # to school
465
+ end
466
+
467
+ state.write si.string
468
+ @dirty = false
469
+ @dirty_parents = false
470
+
471
+ true # success
472
+ end
473
+ rescue IOError
474
+ false
475
+ end
476
+ end
477
+
478
+ ##
479
+ # Copies the files in h (represented as "source" => "dest").
480
+ #
481
+ # @param [Hash<String => String>] h the keys are sources and the values
482
+ # are dests
483
+ # @return [Boolean] a success marker
484
+ def copy(h={})
485
+ h.each do |source, dest|
486
+ next if source == dest
487
+ return true unless source
488
+
489
+ @dirty = true
490
+
491
+ if @copy_map[dest]
492
+ then @copy_map.delete dest
493
+ else @copy_map[dest] = source
494
+ end
495
+ end
496
+
497
+ true # success
498
+ end
499
+
500
+ ##
501
+ # The current directory from where the command is being called, with
502
+ # the path shortened if it's within the repo.
503
+ #
504
+ # @return [String] effectively Dir.pwd
505
+ def cwd
506
+ path = Dir.pwd
507
+ return '' if path == @root
508
+
509
+ # return a more local path if possible...
510
+ return path[@root.length..-1] if path.start_with? @root
511
+ path # else we're outside the repo
512
+ end
513
+ alias_method :pwd, :cwd
514
+
515
+ ##
516
+ # Returns the relative path from +src+ to +dest+.
517
+ #
518
+ # @param [String] src This is a directory! If this is relative,
519
+ # it is assumed to be relative to the root.
520
+ # @param [String] dest This MUST be within root! It also is a file.
521
+ # @return [String] the relative path
522
+ def path_to(src, dest)
523
+ # first, make both paths absolute, for ease of use.
524
+ # @root is guarenteed to be absolute, so we're leethax here
525
+ src = File.join @root, src
526
+ dest = File.join @root, dest
527
+
528
+ # lil' bit of error checking...
529
+ [src, dest].map do |f|
530
+ unless File.exist? f # does both files and directories...
531
+ raise FileNotInRootError, "#{f} is not in the root, #{@root}"
532
+ end
533
+ end
534
+
535
+ # now we find the differences
536
+ # these both are now arrays!!!
537
+ src = src.split '/'
538
+ dest = dest.split '/'
539
+
540
+ while src.first == dest.first
541
+ src.shift and dest.shift
542
+ end
543
+
544
+ # now, src and dest are just where they differ
545
+ path = ['..'] * src.size # we want to go back this many directories
546
+ path += dest
547
+ path.join '/' # tadah!
548
+ end
549
+
550
+ ##
551
+ # Walk recursively through the directory tree, finding all
552
+ # files matched by the regexp in match.
553
+ #
554
+ # Step 1: find all explicit files
555
+ # Step 2: visit subdirectories
556
+ # Step 3: report unseen items in the @files hash
557
+ #
558
+ # @param [Boolean] unknown
559
+ # @param [Boolean] ignored
560
+ # @return [Hash<String => [NilClass, File::Stat]>] nil for directories and
561
+ # stuff, File::Stat for files and links
562
+ def walk(unknown, ignored, match)
563
+ files = match.files
564
+
565
+ bad_type = proc do |file|
566
+ UI::warn "#{file}: unsupported file type (type is #{File.ftype file})"
567
+ end
568
+
569
+ if ignored
570
+ @ignore_all = false
571
+ elsif not unknown
572
+ @ignore_all = true
573
+ end
574
+
575
+ work = [@root]
576
+
577
+ files = match.files ? match.files.uniq : [] # because [].uniq! is a major fuckup
578
+
579
+ # why do we overwrite the entire array if it includes the current dir?
580
+ # we even kill posisbly good things
581
+ files = [''] if files.empty? || files.include?('.') # strange thing to do
582
+ results = {'.hg' => true}
583
+
584
+ # Step 1: find all explicit files
585
+ files.sort.each do |file|
586
+ next if results[file] || file == ""
587
+
588
+ begin
589
+ stats = File.lstat File.join(@root, file)
590
+ kind = File.ftype File.join(@root, file)
591
+
592
+ # we'll take it! but only if it's a directory, which means we have
593
+ # more work to do...
594
+ if kind == 'directory'
595
+ # add it to the list of dirs we have to search in
596
+ work << File.join(@root, file) unless ignoring_directory? file
597
+ elsif kind == 'file' || kind == 'link'
598
+ # ARGH WE FOUND ZE BOOTY
599
+ results[file] = stats
600
+ else
601
+ # user you are a fuckup in life please exit the world
602
+ bad_type[file]
603
+ results[file] = nil if @files[file]
604
+ end
605
+ rescue => e
606
+ keep = false
607
+ prefix = file + '/'
608
+
609
+ @files.each do |f, _|
610
+ if f == file || f.start_with?(prefix)
611
+ keep = true
612
+ break
613
+ end
614
+ end
615
+
616
+ unless keep
617
+ bad_type[file]
618
+ results[file] = nil if (@files[file] || !ignore(file)) && match.call(file)
619
+ end
620
+ end
621
+ end
622
+
623
+ # step 2: visit subdirectories in `work`
624
+ until work.empty?
625
+ dir = work.shift
626
+ skip = nil
627
+
628
+ if dir == '.'
629
+ dir = ''
630
+ else
631
+ skip = '.hg'
632
+ end
633
+
634
+
635
+ dirs = Dir.glob("#{dir}/*", File::FNM_DOTMATCH) - ["#{dir}/.", "#{dir}/.."]
636
+ entries = dirs.inject({}) do |h, f|
637
+ h.merge f => [File.ftype(f), File.lstat(f)]
638
+ end
639
+
640
+
641
+ entries.each do |f, arr|
642
+ tf = f[(@root.size+1)..-1]
643
+ kind = arr[0]
644
+ stats = arr[1]
645
+ unless results[tf]
646
+ if kind == 'directory'
647
+ work << f unless ignore tf
648
+ results[tf] = nil if @files[tf] && match.call(tf)
649
+ elsif kind == 'file' || kind == 'link'
650
+ if @files[tf]
651
+ results[tf] = stats if match.call tf
652
+ elsif match.call(tf) && !ignore(tf)
653
+ results[tf] = stats
654
+ end
655
+ elsif @files[tf] && match.call(tf)
656
+ results[tf] = nil
657
+ end
658
+ end
659
+ end
660
+ end
661
+
662
+ # step 3: report unseen items in @files
663
+ visit = @files.keys.select {|f| !results[f] && match.call(f) }.sort
664
+
665
+ # zip it to a hash of {file_name => file_stats}
666
+ hash = visit.inject({}) do |h, f|
667
+ h.merge!(f => File.stat(File.join(@root,f))) rescue h.merge!(f => nil)
668
+ end
669
+
670
+ hash.each do |file, stat|
671
+ unless stat.nil?
672
+ # because filestats can't be gathered if it's, say, a directory
673
+ stat = nil unless ['file', 'link'].include? File.ftype(File.join(@root, file))
674
+ end
675
+ results[file] = stat
676
+ end
677
+
678
+ results.delete ".hg"
679
+ @ignore_all = nil # reset this
680
+ results
681
+ end
682
+
683
+ ##
684
+ # what's the current state of life, man!
685
+ # Splits up all the files into modified, clean,
686
+ # added, deleted, unknown, ignored, or lookup-needed.
687
+ #
688
+ # @return [Hash<Symbol => Array<String>>] a hash of the filestatuses and their files
689
+ def status(ignored, clean, unknown, match = Match.new { true })
690
+ list_ignored, list_clean, list_unknown = ignored, clean, unknown
691
+ lookup, modified, added, unknown, ignored = [], [], [], [], []
692
+ removed, deleted, clean = [], [], []
693
+ delta = 0
694
+
695
+ walk(list_unknown, list_ignored, match).each do |file, st|
696
+ next if file.nil?
697
+
698
+ unless @files[file]
699
+ if list_ignored && ignoring_directory?(file)
700
+ ignored << file
701
+ elsif list_unknown
702
+ unknown << file unless ignore(file)
703
+ end
704
+
705
+ next # on to the next one, don't do the rest
706
+ end
707
+
708
+ # here's where we split up the files
709
+ state, mode, size, time = *@files[file].to_a
710
+ delta += (size - st.size).abs if st && size >= 0 # increase the delta, but don't forget to check that it's not nil
711
+ if !st && [:normal, :modified, :added].include?(state)
712
+ # add it to the deleted folder if it should be here but isn't
713
+ deleted << file
714
+ elsif state == :normal
715
+ if (size >= 0 && (size != st.size || ((mode ^ st.mode) & 0100 and @check_exec))) || size == -2 || @copy_map[file]
716
+ modified << file
717
+ elsif time != st.mtime.to_i # DOH - we have to remember that times are stored as fixnums
718
+ lookup << file
719
+ elsif list_clean
720
+ clean << file
721
+ end
722
+
723
+ elsif state == :merged
724
+ modified << file
725
+ elsif state == :added
726
+ added << file
727
+ elsif state == :removed
728
+ removed << file
729
+ end
730
+ end
731
+
732
+ r = { :modified => modified.sort , # those that have clearly been modified
733
+ :added => added.sort , # those that are marked for adding
734
+ :removed => removed.sort , # those that are marked for removal
735
+ :deleted => deleted.sort , # those that should be here but aren't
736
+ :unknown => unknown.sort , # those that aren't being tracked
737
+ :ignored => ignored.sort , # those that are being deliberately ignored
738
+ :clean => clean.sort , # those that haven't changed
739
+ :lookup => lookup.sort , # those that need to be content-checked to see if they've changed
740
+ :delta => delta # how many bytes have been added or removed from files (not bytes that have been changed)
741
+ }
742
+ end
743
+
744
+
745
+ ##
746
+ # Reads the data in the .hg folder and fills in the vars
747
+ #
748
+ # @return [Amp::DirState] self -- chainable!
749
+ def read!
750
+ @parents, @files, @copy_map = parse('dirstate')
751
+ self # chainable
752
+ end
753
+
754
+ private
755
+ ##
756
+ # Generates the @ignore array
757
+ # The array is full of paths relative to the root, which
758
+ # makes things easier for the proc-generation phase.
759
+ #
760
+ # @return [NilClass]
761
+ def generate_ignore
762
+ @ignore = @config['ui'].map do |k, v|
763
+ @ignore << "#{v}" if k == "ignore"
764
+ end
765
+
766
+ @ignore << ".hgignore"
767
+ @ignore.compact
768
+
769
+ nil
770
+ end
771
+
772
+ ##
773
+ # Perform various checks on the file before upping the content count
774
+ # for all of its parent directories. It checks for:
775
+ # * filenames containing "\n" or "\r" (newlines and carriage returns)
776
+ # * filenames with the same names as directories
777
+ # * clashing filenames
778
+ #
779
+ # It only increments the dirs' file count if the file is untracked or
780
+ # being removed.
781
+ #
782
+ # @param [String] f Should be formatted like ["action", mode, size, mtime]
783
+ # @param [Boolean] check whether to perform any of the checks
784
+ # @return [NilClass]
785
+ def add_path(f, check=false)
786
+ old_state = @files[f] || DirStateEntry.new # it's an array of info, remember
787
+
788
+ if check || old_state.removed?
789
+ raise "Bad Filename" if f.match(/\r|\n/)
790
+ raise "Directory #{f} already exists" if @dirs[f]
791
+
792
+ # make sure we don't have any files with the same name as a directory
793
+ directories_to(f).each do |d|
794
+ break if @dirs[d]
795
+
796
+ if @files[d] && !@files[d].removed?
797
+ raise "File #{d} clashes with #{f}! Fix their names"
798
+ end
799
+ end
800
+ end
801
+
802
+ # only inc the dirs if the file is untracked or being removed.
803
+ if [:untracked, :removed].include? old_state.status
804
+ # inc the number of dirs in each dir
805
+ inc_directories_to f
806
+ end
807
+
808
+ nil
809
+ end
810
+
811
+ ##
812
+ # Conditional wrapper around +dec_directories_to+. It will dec the
813
+ # directories if the file in question (+f+) is either untracked or
814
+ # being removed.
815
+ #
816
+ # @param [String] f Should be formatted like ["action", mode, size, mtime]
817
+ # @return [NilClass]
818
+ def drop_path(f)
819
+ unless [:untracked, :removed].include? f[0]
820
+ dec_directories_to(f)
821
+ end
822
+
823
+ nil
824
+ end
825
+
826
+ ##
827
+ # All directories leading up to this path
828
+ #
829
+ # @example directories_to "/Users/ari/src/monkey.txt" # =>
830
+ # ["/Users/ari/src", "/Users/ari", "/Users"]
831
+ # @param [String] path the path to the file we're examining
832
+ # @return [Array] the directories leading up to this path
833
+ def directories_to(path)
834
+ File.amp_directories_to path
835
+ end
836
+
837
+ ##
838
+ # Increment all directories' dir-count leading up to this path.
839
+ # The dir-count is the path's value in @dirs.
840
+ # This is used when adding a file.
841
+ #
842
+ # @param [String] path the path we're disecting
843
+ # @return [NilClass]
844
+ def inc_directories_to(path)
845
+ p = directories_to(path).first
846
+ @dirs[p] ||= 0
847
+ @dirs[p] += 1
848
+ nil
849
+ end
850
+
851
+ ##
852
+ # Decrement all directories' dir-count leading up to this path.
853
+ # The dir-count is the path's value in @dirs.
854
+ # This is used when removing a file.
855
+ #
856
+ # @param [String] path the path we're disecting
857
+ # @return [NilClass]
858
+ def dec_directories_to(path)
859
+ p = directories_to(path).first
860
+ # if the dir has 0, kill the dir. we don't need it anymore
861
+ if @dirs[p] && @dirs[p].zero?
862
+ @dirs.delete p
863
+ elsif @dirs[p]
864
+ @dirs[p] -= 1 # we only need to inc the latest dir
865
+ end
866
+
867
+ nil
868
+ end
869
+
870
+ ##
871
+ # I wish I knew what this did or when it was called.
872
+ #
873
+ # @todo figure out what this does
874
+ # @param [String] path the path to a file
875
+ # @return [String] All I know is that this returns a string
876
+ def normalize(path)
877
+ fold_path = @folds[path]
878
+ fold_path = path unless fold_path # if fold_path is true, then this line returns nil
879
+ fold_path # so we need an extra line here to make sure it returns a good value
880
+ end
881
+
882
+ ##
883
+ # Are we ignoring the directory?
884
+ #
885
+ # @param [String] dir the directory we're checking, either aboslute or relative
886
+ # @return [Boolean] are we ignoring the dir?
887
+ def ignoring_directory?(dir)
888
+ return true if @ignore_all
889
+ return false if @ignore_all == false
890
+ return false if dir == '.' # base cases
891
+ return true if ignore dir # base cases
892
+
893
+ !!directories_to(dir).any? {|d| ignore d }
894
+ end
895
+ alias_method :ignoring_dir?, :ignoring_directory?
896
+
897
+ ##
898
+ # Parses the dirstate file in .hg
899
+ #
900
+ # @param [String] file path to the file to parse
901
+ # @return [((String, String), Hash<String => (Integer, Integer, Integer)>, Hash<String => String>)]
902
+ # a tuple of (parents, files, copies). Parents is a tuple of the parents,
903
+ # files is a hash of filename => [mode, size, mtime], and copies is a hash of src => dest
904
+ def parse(file)
905
+ # the main data we need to return
906
+ files = {}
907
+ copies = {}
908
+ parents = []
909
+ @opener.open file, "r" do |s|
910
+
911
+ # the parents are the first 40 bytes
912
+ parent = s.read(20) || NULL_ID
913
+ parent_ = s.read(20) || NULL_ID
914
+ parents = [parent, parent_]
915
+
916
+ # 1 character + 4 32-bit ints = 17 bytes
917
+ e_size = 17
918
+
919
+ # this loop is just cycling through and reading every entry
920
+ while !s.eof?
921
+ # read 1 entry
922
+ info = s.read(e_size).unpack FORMAT
923
+
924
+ # byte swap and shizzle
925
+ info = [info[0].to_dirstate_symbol, info[1], info[2].to_signed(32), info[3].to_signed(32), info[4]]
926
+ # ^^^^ we have to sign them because otherwise they'll be hugely wrong
927
+
928
+ # read in the filename
929
+ f = s.read(info[4])
930
+
931
+ # if it has an \0, then we've moved/copied it
932
+ if f.match(/\0/)
933
+ source, dest = f.split "\0"
934
+ copies[source] = dest
935
+ f = source
936
+ end
937
+
938
+ # and put in the info for the file itself
939
+ files[f] = DirStateEntry.new(*info[0..3])
940
+ end
941
+ end
942
+
943
+ [parents, files, copies]
944
+ rescue Errno::ENOENT
945
+ # no file? easy peasy
946
+ [[NULL_ID, NULL_ID], {}, {}]
947
+ end
948
+ end
949
+ end
950
+ end