amp 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +129 -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 +385 -0
@@ -0,0 +1,319 @@
1
+ module Amp
2
+ module Repositories
3
+ ##
4
+ # = TagManager
5
+ # This module handles all tag-related (but not branch tag) functionality
6
+ # of the repository.
7
+ module TagManager
8
+ include Amp::RevlogSupport::Node
9
+
10
+ TAG_FORBIDDEN_LETTERS = ":\r\n"
11
+ ##
12
+ # Returns a list of all the tags as a hash, mapping each tag to the tip-most
13
+ # changeset it applies to.
14
+ #
15
+ # @return [Hash] a hash, sorted by revision index (i.e. its order in the commit
16
+ # history), with the keys:
17
+ # :revision => the revision index of the changeset,
18
+ # :tag => the name of the tag,
19
+ # :node => the node-id of the changeset
20
+ def tag_list
21
+ list = []
22
+ tags.each do |tag, node|
23
+ begin
24
+ r = changelog.revision(node)
25
+ rescue
26
+ r = -2
27
+ end
28
+ list << {:revision => r, :tag => tag, :node => node}
29
+ end
30
+ list.sort {|i1, i2| i1[:revision] <=> i2[:revision] }
31
+ end
32
+
33
+ ##
34
+ # Returns the tag-type of the given tag. This could be "local", which means it is
35
+ # not shared among repositories.
36
+ #
37
+ # @param [String] tag_name the name of the tag to lookup, such as "tip"
38
+ # @return [String] the type of the requested tag, such as "local".
39
+ def tag_type(tag_name)
40
+ tags #load the tags
41
+ @tags_type_cache[tag_name]
42
+ end
43
+
44
+ ##
45
+ # Returns the tags for a given revision (by ID).
46
+ #
47
+ # @param [String] node the node-ID, in binary form.
48
+ # @return [Array<String>] a list of tags for the given node.
49
+ def tags_for_node(node)
50
+ return (@tags_for_node_cache[node] || []) if @tags_for_node_cache
51
+ @tags_for_node_cache = {}
52
+ tags.each do |tag, tag_node|
53
+ @tags_for_node_cache[tag_node] ||= [] # make sure there's an array
54
+ @tags_for_node_cache[tag_node] << tag # add the tag to it
55
+ end
56
+ @tags_for_node_cache[node] || []
57
+ end
58
+
59
+ ##
60
+ # Invalidates the tag cache. Removes all ivars relating to tags.
61
+ def invalidate_tag_cache!
62
+ @tags_for_node_cache = nil
63
+ @tags_cache = nil
64
+ @tags_type_cache = nil
65
+ end
66
+
67
+ ##
68
+ # Loads all of the tags from the tag-cache file stored as .hgtags. Returns
69
+ # a hash, mapping tag names to node-IDs.
70
+ #
71
+ # @return [Hash] a hash mapping tags to node-IDs.
72
+ def tags
73
+ return @tags_cache if @tags_cache
74
+
75
+ global_tags, tag_types = {}, {}
76
+
77
+ file = nil
78
+ # For each current .hgtags file in our history (including multi-heads), read in
79
+ # the tags
80
+ hg_tags_nodes.each do |rev, node, file_node|
81
+ # get the file
82
+ f = (f && f.file(file_node)) || self.versioned_file(".hgtags", :file_id => file_node.file_node)
83
+ # read the tags, as global, because they're versioned.
84
+ read_tags(f.data.split("\n"), f, "global", global_tags, tag_types)
85
+ end
86
+
87
+ # Now do locally stored tags, that aren't committed/versioned
88
+ begin
89
+ # get the local file, stored in .hg/
90
+ data = @hg_opener.read("localtags")
91
+ # Read the tags as local, because they are not versioned
92
+ read_tags(data.split_newlines,"local_tags","local",global_tags, tag_types)
93
+ rescue Errno::ENOENT
94
+ # do nothing. most people don't have this file.
95
+ end
96
+ # Save our tags for use later. Use ivars.
97
+ @tags_cache = {}
98
+ @tags_type_cache = {}
99
+ # Go through the global tags to store them in the cache
100
+ global_tags.each do |k, nh|
101
+ # the node ID is the first part of the stored data
102
+ n = nh.first
103
+
104
+ # update the cache
105
+ @tags_cache[k] = n unless n == NULL_ID
106
+ @tags_type_cache[k] = tag_types[k]
107
+ end
108
+
109
+ # tip = special tag
110
+ @tags_cache["tip"] = self.changelog.tip
111
+
112
+ # return our tags
113
+ @tags_cache
114
+ end
115
+
116
+ ##
117
+ # Adds the given tag to a given changeset, and commits to preserve it.
118
+ #
119
+ # @param [String, Array] names a list of tags (or just 1 tag) to apply to
120
+ # the changeset
121
+ # @param [String, Integer] node the node to apply the tag to
122
+ # @param [Hash] opts the opts for tagging
123
+ # @option [String] opts message ("added tag _tag_ to changeset _node_")
124
+ # the commit message to use.
125
+ # @option [Boolean] opts local (false) is the tag a local one? I.E., will it be
126
+ # shared across repos?
127
+ # @option [String] opts user ($USER) the username to apply for the commit
128
+ # @option [Time] opts time (Time.now) what should the commit-time be marked as?
129
+ # @option [String] opts parent (nil) The parent revision of the one we
130
+ # are tagging. or something.
131
+ # @option [Hash] opts extra ({}) the extra data to apply for the commit.
132
+ def apply_tag(names, node, opts={})
133
+ use_dirstate = opts[:parent].nil?
134
+ all_letters = names.kind_of?(Array) ? names.join : names
135
+ (TAG_FORBIDDEN_LETTERS.size-1).downto 0 do |i|
136
+ if all_letters.include? TAG_FORBIDDEN_LETTERS[i, 1]
137
+ raise abort("#{TAG_FORBIDDEN_LETTERS[i,1]} not allowed in a tag name!")
138
+ end
139
+ end
140
+
141
+ prev_tags = ""
142
+ # If it's a local tag, we just write the file and bounce. mad easy yo.
143
+ if opts[:local]
144
+ @hg_opener.open("localtags","r+") do |fp|
145
+ prev_tags = fp.read
146
+ write_tags(fp,names, nil, prev_tags)
147
+ end
148
+ return
149
+ end
150
+
151
+ # ok, it's a global tag. now we have to handle versioning and shit.
152
+ if use_dirstate
153
+ prev_tags = working_read(".hgtags") rescue ""
154
+ file = @file_opener.open(".hgtags","a")
155
+ else
156
+ prev_tags = versioned_file(".hgtags", :change_id => parent).data
157
+ file = @file_opener.open(".hgtags","w")
158
+
159
+ file.write prev_tags if prev_tags && prev_tags.any?
160
+ end
161
+
162
+ write_tags(file, node, names, prev_tags)
163
+ file.close
164
+ if use_dirstate && dirstate[".hgtags"].status == :untracked
165
+ self.add([".hgtags"])
166
+ end
167
+
168
+ tag_node = commit :files => [".hgtags"],
169
+ :message => opts[:message],
170
+ :user => opts[:user],
171
+ :date => opts[:date],
172
+ :p1 => opts[:parent],
173
+ :extra => opts[:extra]
174
+
175
+ tag_node
176
+ end
177
+
178
+ private
179
+
180
+ ##
181
+ # Goes through all the heads in the repository, getting the state of the .hgtags
182
+ # file at each head. We then return a list, with each entry mapping revision index
183
+ # and node ID to the .hgtags file at that head. If two different heads have the same
184
+ # .hgtags file, only 1 is returned with it.
185
+ #
186
+ # @return [[Fixnum, String, VersionedFile]] each head with a different .hgtags file
187
+ # at that point. That way we have the most recent copy of .hgtags, even if the file
188
+ # differs on different heads.
189
+ def hg_tags_nodes
190
+ heads = self.heads.reverse
191
+ last = {}
192
+ return_list = []
193
+ heads.each do |node|
194
+ changeset = self[node]
195
+ rev = changeset.revision
196
+ begin
197
+ file_node = changeset.get_file(".hgtags")
198
+ rescue
199
+ next
200
+ end
201
+ return_list << [rev, node, file_node]
202
+ return_list[last[file_node]] = nil if last[file_node] # replace old head
203
+
204
+ last[file_node] = return_list.size - 1
205
+ end
206
+ return return_list.reject {|item| item.nil?}
207
+ end
208
+
209
+ ##
210
+ # Writes the tags to the given stream. This method must be aware of previously
211
+ # written tags. Also, any new tags must state what the node to use for writing is.
212
+ #
213
+ # @param [IO, #write] file the output stream to write to. Could be a file, or any IO.
214
+ # @param [String] node a binary node ID for any newly-added tags
215
+ # @param [Array] names A list of all the tag names to write
216
+ # @param [Hash] prev_tags the previously written string (or something)
217
+ def write_tags(file, node, names, prev_tags)
218
+ file.seek(0, IO::SEEK_END)
219
+ if prev_tags && prev_tags.any? && prev_tags[-1,1] != "\n"
220
+ file.write "\n"
221
+ end
222
+ names.each do |name|
223
+ if @tags_type_cache && @tags_type_cache[name]
224
+ old = @tags_cache[name] || NULL_ID
225
+ file.write("#{old.hexlify} #{name}\n")
226
+ end
227
+ file.write("#{node.hexlify} #{name}\n")
228
+ end
229
+ end
230
+
231
+ ##
232
+ # Reads in an .hgtags file and parses it, while respecting global tags.
233
+ # This is where things get kinda messy, because otherwise we'd just be parsing
234
+ # a simple text file. Anyway, global_tags are tags like "tip" -> the current tip
235
+ # - they're programmatically assigned tags.
236
+ #
237
+ # @param [Array<String>] lines the file, split into lines
238
+ # @param [String] fn the file that we are parsing, only for debug purposes
239
+ # @param [String] tag_type what kind of tag are we looking at? usually "local"
240
+ # or "global" or nothing. For example, a local-only tag isn't committed - these
241
+ # need to be treated differently.
242
+ # @param [Hash] global_tags maps nodes to global tags, such as "tip".
243
+ # @param [Hash] tag_types maps nodes to what type of tag they are
244
+ # @return [Hash] the list of tags we have read in.
245
+ #
246
+ # @todo encodings, handle local encodings
247
+ def read_tags(lines, fn, tag_type, global_tags, tag_types)
248
+ # This is our tag list we'll be building.
249
+ file_tags = {}
250
+
251
+ # Each line is of the format:
252
+ # [char * 40, node ID] [tag]
253
+ # 0123456789012345678901234567890123456789 crazymerge
254
+ lines.each_with_index do |line, count|
255
+ # skip if we have no text to parse
256
+ next if line.nil? || line.empty?
257
+
258
+ # split once, so we could theoretically have spaces in tag names
259
+ s = line.split(" ", 2)
260
+ # make sure we parsed the tag entry alright
261
+ if s.size != 2
262
+ UI::warn "Can't parse entry, filename #{fn} line #{count}"
263
+ next
264
+ end
265
+
266
+ # Node comes first, tag comes second
267
+ node, tag = s
268
+ tag.strip! #TODO: encodings, handle local encodings
269
+
270
+ # Convert to binary so we can look it up in our repo
271
+ bin_node = node.unhexlify
272
+
273
+ # Is it in our repo? if not, skip to the next tag.
274
+ unless self.changelog.node_map[bin_node]
275
+ UI::warn "Tag #{key} refers to unknown node"
276
+ next
277
+ end
278
+
279
+ # heads is a list of the nodes that have this same tag
280
+ heads = []
281
+ # have we already seen this tag?
282
+ if file_tags[tag]
283
+ # pull out the old data
284
+ n, heads = file_tags[tag]
285
+ # add our new node to the list for this tag
286
+ heads << n
287
+ end
288
+ # update our tag list
289
+ file_tags[tag] = [bin_node, heads]
290
+ end
291
+
292
+ # For each tag that we have...
293
+ file_tags.each do |k, nh|
294
+ # Is this a reserved, global tag? Or, just one that's been used already?
295
+ # like "tip"? if not, we're ok
296
+ unless global_tags[k]
297
+ # update global_tags with our new tag
298
+ global_tags[k] = nh
299
+ # set the tag_types hash as well
300
+ tag_types[k] = tag_type
301
+ next
302
+ end
303
+ # we prefer the global tag if:
304
+ # it supercedes us OR
305
+ # mutual supercedes and it has a higher rank
306
+ # otherwise we win because we're tip-most
307
+ an, ah = nh
308
+ bn, bh = global_tags[k]
309
+ if [bn != an, bh[an], (!ah[bn] || bh.size > ah.size)].all?
310
+ an = bn
311
+ end
312
+ ah += bh.select {|n| !ah[n]}
313
+ global_tags[k] = an, ah
314
+ tag_types[k] = tag_type
315
+ end
316
+ end
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,532 @@
1
+ module Amp
2
+ module Repositories
3
+
4
+ ##
5
+ # This module contains all the code that makes a repository able to
6
+ # update its working directory.
7
+ module Updatable
8
+ include Amp::RevlogSupport::Node
9
+
10
+ ##
11
+ # Updates the repository to the given node. One of the major operations on a repository.
12
+ # This will update the working directory to the given node.
13
+ #
14
+ # @todo add lock
15
+ # @param [String, Integer] node the revision to which we are updating the repository. Can
16
+ # be either nil, a node ID, or an integer. If it is nil, it will update
17
+ # to the latest revision.
18
+ # @param [Boolean] branch_merge whether to merge between branches
19
+ # @param [Boolean] force whether to force branch merging or file overwriting
20
+ # @param [Proc, #call] partial a function to filter file lists (dirstate not updated if this
21
+ # is passed)
22
+ # @return [Array<Integer>] a set of statistics about the update. In the form:
23
+ # [updated, merged, removed, unresolved] where each entry is the # of files in that category.
24
+ def update(node=nil, branch_merge=false, force=false, partial=nil)
25
+ # debugger
26
+ self.status(:node_1 => self.dirstate.parents.first, :node_2 => nil)
27
+ working_changeset = self[nil]
28
+ # tip of current branch
29
+ node ||= branch_tags[working_changeset.branch]
30
+ node = self.lookup("tip") if node.nil? && working_changeset.branch == "default"
31
+ if node.nil?
32
+ raise abort("branch #{working_changeset.branch} not found")
33
+ end
34
+
35
+ overwrite = force && !branch_merge
36
+ parent_list = working_changeset.parents
37
+ parent1, parent2 = parent_list.first, self[node]
38
+ parent_ancestor = parent1.ancestor(parent2)
39
+
40
+ fp1, fp2, xp1, xp2 = parent1.node, parent2.node, parent1.to_s, parent2.to_s
41
+ fast_forward = false
42
+
43
+ ## In this section, we make sure that we can actually do an update.
44
+ ## No use starting an udpate if we can't finish!
45
+
46
+ if !overwrite && parent_list.size > 1
47
+ raise abort("outstanding uncommitted merges")
48
+ end
49
+
50
+ if branch_merge
51
+ if parent_ancestor == parent2
52
+ raise abort("can't merge with ancestor")
53
+ elsif parent_ancestor == parent1
54
+ if parent1.branch != parent2.branch
55
+ fast_forward = true
56
+ else
57
+ raise abort("nothing to merge (use 'hg update' or check"+
58
+ " 'hg heads')")
59
+ end
60
+ end
61
+ if !force && (working_changeset.files.any? || working_changeset.deleted.any?)
62
+ raise abort("oustanding uncommitted changes")
63
+ end
64
+ elsif !overwrite
65
+ if parent_ancestor == parent1 || parent_ancestor == parent2
66
+ # do nothing
67
+ elsif parent1.branch == parent2.branch
68
+ if working_changeset.files.any? || working_changeset.deleted.any?
69
+ raise abort("crosses branches (use 'hg merge' or "+
70
+ "'hg update -C' to discard changes)")
71
+ end
72
+ raise abort("crosses branches (use 'hg merge' or 'hg update -C')")
73
+ elsif working_changeset.files.any? || working_changeset.deleted.any?
74
+ raise abort("crosses named branches (use 'hg update -C'"+
75
+ " to discard changes)")
76
+ else
77
+ overwrite = true
78
+ end
79
+ end
80
+
81
+ ## Alright, now let's figure out exactly what we have to do to make this update.
82
+ ## Shall we?
83
+
84
+ actions = []
85
+ check_unknown(working_changeset, parent2) if force
86
+ check_collision(parent2) if false # case-sensitive file-system? seriously?
87
+
88
+ actions += forget_removed(working_changeset, parent2, branch_merge)
89
+ actions += manifest_merge(working_changeset, parent2, parent_ancestor,
90
+ overwrite, partial)
91
+
92
+ ## Apply phase - apply the changes we just generated.
93
+ unless branch_merge # just jump to the new revision
94
+ fp1, fp2, xp1, xp2 = fp2, NULL_ID, xp2, ''
95
+ end
96
+ unless partial
97
+ run_hook :preupdate, :throw => true, :parent1 => xp1, :parent2 => xp2
98
+ end
99
+
100
+ stats = apply_updates(actions, working_changeset, parent2)
101
+
102
+ unless partial
103
+ record_updates(actions, branch_merge)
104
+ dirstate.parents = [fp1, fp2]
105
+ if !branch_merge && !fast_forward
106
+ dirstate.branch = parent2.branch
107
+ end
108
+ run_hook :update, :parent1 => xp1, :parent2 => xp2, :error => stats[3]
109
+ dirstate.write
110
+ end
111
+
112
+ return stats
113
+
114
+ end
115
+
116
+ ##
117
+ # Merge two heads
118
+ def merge(node, force=false)
119
+ update node, true, force, false
120
+ end
121
+
122
+ ##
123
+ # Updates the repository to the given node, clobbering (removing) changes
124
+ # along the way. This has the effect of turning the working directory into
125
+ # a pristine copy of the requested changeset. Really just a nice way of
126
+ # skipping some arguments for the caller.
127
+ #
128
+ # @param [String] node the requested node
129
+ def clean(node)
130
+ update node, false, true, nil
131
+ end
132
+
133
+ private
134
+
135
+ ##
136
+ # This method will make sure that there are no differences between
137
+ # untracked files in the working directory, and tracked files in
138
+ # the new changeset.
139
+ #
140
+ # @param [WorkingDirectoryChangeset] working_changeset the current working directory
141
+ # @param [Changeset] target_changeset the destination changeset (that we're updating to)
142
+ # @raise [AbortError] if an untracked file in the working directory is different from
143
+ # a tracked file in the target changeset, this abort error will be raised.
144
+ def check_unknown(working_changeset, target_changeset)
145
+ working_changeset.unknown.each do |file|
146
+ if target_changeset[file] && target_changeset[file].cmp(working_changeset[file].data())
147
+ raise abort("Untracked file in the working directory differs from "+
148
+ "a tracked file in the requested revision: #{file}")
149
+ end
150
+ end
151
+ end
152
+
153
+ ##
154
+ # This method will check if the target changeset will cause name collisions
155
+ # when filenames are changed to all lower-case. This is important because
156
+ # in the store, the file-logs are all changed to lower-case.
157
+ #
158
+ # @param [Changeset] target_changeset the destination changeset (that we're updating to)
159
+ # @raise [AbortError] If two files have the same lower-case name, in 1 changeset,
160
+ # this error will be thrown.
161
+ def check_collision(target_changeset)
162
+ folded_names = {}
163
+ target_changeset.each do |file|
164
+ folded = file.downcase
165
+ if folded_names[folded]
166
+ raise abort("Case-folding name collision between #{folded_names[folded]} and #{file}.")
167
+ end
168
+ folded_names[folded] = file
169
+ end
170
+ end
171
+
172
+ ##
173
+ # Forget removed files (docs ripped from mercurial)
174
+ #
175
+ # If we're jumping between revisions (as opposed to merging), and if
176
+ # neither the working directory nor the target rev has the file,
177
+ # then we need to remove it from the dirstate, to prevent the
178
+ # dirstate from listing the file when it is no longer in the
179
+ # manifest.
180
+ #
181
+ # If we're merging, and the other revision has removed a file
182
+ # that is not present in the working directory, we need to mark it
183
+ # as removed.
184
+ #
185
+ # @param [WorkingDirectoryChangeset] working_changeset the current working directory
186
+ # @param [Changeset] target_changeset the destination changeset (that we're updating to)
187
+ # @param [Boolean] branch_merge whether or not to delete working files
188
+ # @return [[String, Symbol]] a list of actions that should be taken to complete
189
+ # a successful transition from working_changeset to target_changeset.
190
+ def forget_removed(working_changeset, target_changeset, branch_merge)
191
+ action_list = []
192
+ action = branch_merge ? :remove : :forget
193
+ working_changeset.deleted.each do |file|
194
+ action_list << [file, action] unless target_changeset[file]
195
+ end
196
+
197
+ unless branch_merge
198
+ working_changeset.removed.each do |file|
199
+ action_list << [file, :forget] unless target_changeset[file]
200
+ end
201
+ end
202
+
203
+ action_list
204
+ end
205
+
206
+ ##
207
+ # Merge the local working changeset (local), and the target changeset (remote),
208
+ # using the common ancestor (ancestor). Generates a merge action list to update
209
+ # the manifest.
210
+ #
211
+ # @param [Changeset] local The working-directory changeset we're merging from
212
+ # @param [Changeset] remote The target changeset we need to merge to
213
+ # @param [Changeset] ancestor A common ancestor between the 2 parents
214
+ # @param [Boolean] overwrite Can we delete working files?
215
+ # @param [Proc] partial a function to filter file lists
216
+ # @return [[String, Symbol]] A list of actions that should be taken to complete
217
+ # a successful transition from local to remote.
218
+ def manifest_merge(local, remote, ancestor, overwrite, partial)
219
+ UI::status("resolving manifests")
220
+ UI::debug(" overwrite #{overwrite} partial #{partial}")
221
+ UI::debug(" ancestor #{ancestor} local #{local} remote #{remote}")
222
+
223
+ local_manifest = local.manifest
224
+ remote_manifest = remote.manifest
225
+ ancestor_manifest = ancestor.manifest
226
+ backwards = (ancestor == remote)
227
+ action_list = []
228
+ copy, copied, diverge = {}, {}, {}
229
+
230
+ flag_merge = lambda do |file_local, file_remote, file_ancestor|
231
+ file_remote = file_ancestor = file_local unless file_remote
232
+
233
+ a = ancestor_manifest.flags[file_ancestor]
234
+ m = local_manifest.flags[file_local]
235
+ n = remote_manifest.flags[file_remote]
236
+
237
+ return m if m == n # flags are identical, we're fine
238
+
239
+ if m.any? && n.any?
240
+ unless a.any? # i'm so confused, ask the user what the flag should be!
241
+ r = UI.ask("conflicting flags for #{file_local} (n)one, e(x)ec, or "+
242
+ "sym(l)ink?")
243
+ return (r != "n") ? r : ''
244
+ end
245
+ return n if m == a # changed from m to n
246
+ return m # changed from n to m
247
+ end
248
+
249
+ return m if m.any? && m != a # changed from a to m
250
+ return n if n.any? && n != a # changed from a to n
251
+ return '' #no more flag
252
+ end
253
+
254
+ act = lambda do |message, move, file, *args|
255
+ UI::debug(" #{file}: #{message} -> #{move}")
256
+ action_list << [file, move] + args
257
+ end
258
+
259
+ if ancestor && !(backwards || overwrite)
260
+ if @config["merge", "followcopies", Boolean, true]
261
+ dirs = @config["merge", "followdirs", Boolean, false] # don't track directory renames
262
+ copy, diverge = Amp::Graphs::CopyCalculator.find_copies(self, local, remote, ancestor, dirs)
263
+ end
264
+ copied = Hash.with_keys(copy.values)
265
+ diverge.each do |of, fl|
266
+ act["divergent renames", :divergent_rename, of, fl]
267
+ end
268
+ end
269
+
270
+ # Compare manifests
271
+ local_manifest.each do |file, node|
272
+ next if partial && !partial[file]
273
+
274
+ if remote_manifest[file]
275
+ rflags = (overwrite || backwards) ? remote_manifest.flags[file] : flag_merge[file,nil,nil]
276
+ # Are files different?
277
+ if node != remote_manifest[file]
278
+ anc_node = ancestor_manifest[file] || NULL_ID
279
+
280
+ if overwrite # Can we kill the file?
281
+ act["clobbering", :get, file, rflags]
282
+ elsif backwards # Or are we going back in time and cleaning?
283
+ if !(node[20..-1]) || !(remote[file].cmp(local[file].data))
284
+ act["reverting", :get, file, rflags]
285
+ end
286
+ elsif node != anc_node && remote_manifest[file] != anc_node
287
+ # are both nodes different from the ancestor?
288
+ act["versions differ", :merge, file, file, file, rflags, false]
289
+ elsif remote_manifest[file] != anc_node
290
+ # is remote's version newer?
291
+ act["remote is newer", :get, file, rflags]
292
+ elsif local_manifest.flags[file] != rflags
293
+ # local is newer, not overwrite, check mode bits (wtf does this mean)
294
+ act["update permissions", :exec, file, rflags]
295
+ end
296
+ elsif local_manifest.flags[file] != rflags
297
+ act["update permissions", :exec, file, rflags]
298
+ end
299
+ elsif copied[file]
300
+ next
301
+ elsif copy[file]
302
+ file2 = copy[file]
303
+ if !remote_manifest[file2] #directory rename
304
+ act["remote renamed directory to #{file2}", :d, file, nil, file2, local_manifest.flags[file]]
305
+ elsif local_manifest[file2] # case 2 A,B/B/B
306
+ act["local copied to #{file2}", :merge, file, file2, file,
307
+ flag_merge[file, file2, file2], false]
308
+ else # case 4,21 A/B/B
309
+ act["local moved to #{file2}", :merge, file, file2, file,
310
+ flag_merge[file, file2, file2], false]
311
+ end
312
+ elsif ancestor_manifest[file]
313
+ if node != ancestor_manifest[file] && !overwrite
314
+ if UI.ask("local changed #{file} which remote deleted\n" +
315
+ "use (c)hanged version or (d)elete?") == "d"
316
+ act["prompt delete", :remove, file]
317
+ else
318
+ act["prompt keep", :add, file]
319
+ end
320
+ else
321
+ act["other deleted", :remove, file]
322
+ end
323
+ else
324
+ if (overwrite && node[20..-1] != "u") || (backwards && node[20..-1].empty?)
325
+ act["remote deleted", :remove, file]
326
+ end
327
+ end
328
+ end
329
+
330
+ remote_manifest.each do |file, node|
331
+ next if partial && !(partial[file])
332
+ next if local_manifest[file]
333
+ next if copied[file]
334
+ if copy[file]
335
+ file2 = copy[file]
336
+ if !(local_manifest[file2])
337
+ act["local renamed directory to #{file2}", :directory, nil, file,
338
+ file2, remote_manifest.flags[file]]
339
+ elsif remote_manifest[file2]
340
+ act["remote copied to #{file}", :merge, file2, file, file,
341
+ flag_merge[file2, file, file2], false]
342
+ else
343
+ act["remote moved to #{file}", :merge, file2, file, file,
344
+ flag_merge[file2, file, file2], true]
345
+ end
346
+ elsif ancestor_manifest[file]
347
+ if overwrite || backwards
348
+ act["recreating", :get, file, remote_manifest.flags[file]]
349
+ elsif node != ancestor_manifest[file]
350
+ if UI.ask("remote changed #{file} which local deleted\n" +
351
+ "use (c)hanged version or leave (d)eleted?") == "c"
352
+ act["prompt recreating", :get, file, remote_manifest.flags[file]]
353
+ end
354
+ end
355
+ else
356
+ act["remote created", :get, file, remote_manifest.flags[file]]
357
+ end
358
+ end
359
+
360
+ action_list
361
+ end
362
+
363
+ def action_cmp(action1, action2)
364
+ move1 = action1[1] # action out of the tuple
365
+ move2 = action2[1] # action out of the tuple
366
+
367
+ return action1 <=> action2 if move1 == move2
368
+ return -1 if move1 == :remove
369
+ return 1 if move2 == :remove
370
+ return action1 <=> action2
371
+ end
372
+
373
+ ##
374
+ # Apply the merge action list to the working directory, in order to migrate from
375
+ # working_changeset to target_changeset.
376
+ #
377
+ # @todo add path auditor
378
+ # @param [Array<Array>] actions list of actions to take to migrate from {working_changeset} to
379
+ # {target_changeset}.
380
+ # @param [WorkingDirectoryChangeset] working_changeset the current changeset in the repository
381
+ # @param [Changeset] target_changeset the changeset we are updating the working directory to.
382
+ # @return [Hash] Statistics about the update. Keys are:
383
+ # :updated => files that were changed
384
+ # :merged => files that were merged
385
+ # :removed => files that were removed
386
+ # :unresolved => files that had conflicts when merging that we couldn't fix
387
+ def apply_updates(actions, working_changeset, target_changeset)
388
+ updated, merged, removed, unresolved = [], [], [], []
389
+ merge_state.reset(working_changeset.parents.first.node)
390
+
391
+ moves = []
392
+ actions.sort! {|a1, a2| action_cmp a1, a2 }
393
+
394
+ # prescan for merges in the list of actions.
395
+ actions.each do |a|
396
+ file, action = a[0], a[1]
397
+ if action == :merge # ah ha! a merge.
398
+ file2, filename_dest, flags, move = a[2..-1] # grab some info about it
399
+ UI.debug("preserving #{file} for resolve of #{filename_dest}")
400
+ vf_local = working_changeset[file] # look up our changesets we'll need later
401
+ vf_other = target_changeset[file2]
402
+ vf_base = vf_local.ancestor(vf_other) || versioned_file(file, :file_id => NULL_REV)
403
+ merge_state.add(vf_local, vf_other, vf_base, filename_dest, flags) # track this merge!
404
+
405
+ moves << file if file != filename_dest && move
406
+ end
407
+ end
408
+
409
+ # If we're moving any files, we can remove renamed ones now
410
+ moves.each do |file|
411
+ if File.amp_lexist?(working_join(file))
412
+ UI.debug("removing #{file}")
413
+ File.unlink(working_join(file))
414
+ end
415
+ end
416
+
417
+ # TODO: add path auditor
418
+
419
+ actions.each do |action|
420
+ file, choice = action[0], action[1]
421
+ next if file && file[0,1] == "/"
422
+ case choice
423
+ when :remove
424
+ UI.note "removing #{file}"
425
+ File.unlink(working_join(file))
426
+ removed << file
427
+ when :merge
428
+ file2, file_dest, flags, move = action[2..-1]
429
+ result = merge_state.resolve(file_dest, working_changeset, target_changeset)
430
+
431
+ unresolved << file if result
432
+ updated << file if result.nil?
433
+ merged << file if result == false
434
+
435
+ File.amp_set_executable(working_join(file_dest), flags && flags.include?('x'))
436
+ if (file != file_dest && move && File.amp_lexist?(working_join(file)))
437
+ UI.debug("removing #{file}")
438
+ File.unlink(working_join(file))
439
+ end
440
+ when :get
441
+ flags = action[2]
442
+ UI.note("getting #{file}")
443
+ data = target_changeset.get_file(file).data
444
+ working_write(file, data, flags)
445
+ updated << file
446
+ when :directory
447
+ file2, file_dest, flags = action[2..-1]
448
+ if file && file.any?
449
+ UI.note("moving #{file} to #{file_dest}")
450
+ File.move(file, file_dest)
451
+ end
452
+ if file2 && file2.any?
453
+ UI.note("getting #{file2} to #{file_dest}")
454
+ data = target_changeset.get_file(file2).data
455
+ working_write(file_dest, data, flags)
456
+ end
457
+ updated << file
458
+ when :divergent_rename
459
+ filelist = action[2]
460
+ UI.warn("detected divergent renames of #{f} to:")
461
+ filelist.each {|fn| UI.warn fn }
462
+ when :exec
463
+ flags = action[2]
464
+ File.amp_set_executable(working_join(file), flags.include?('x'))
465
+ end
466
+
467
+ end
468
+
469
+ hash = {:updated => updated ,
470
+ :merged => merged ,
471
+ :removed => removed ,
472
+ :unresolved => unresolved }
473
+
474
+ class << hash
475
+ def success?; self[:unresolved].empty?; end
476
+ end
477
+
478
+ hash
479
+ end
480
+
481
+ ##
482
+ # Records all the updates being made while merging to the new working directory.
483
+ # It records them by writing to the dirstate.
484
+ #
485
+ # @param [Array<Array>] actions a list of actions to take
486
+ # @param [Boolean] branch_merge is this a branch merge?
487
+ def record_updates(actions, branch_merge)
488
+ actions.each do |action|
489
+ file, choice = action[0], action[1]
490
+ case choice
491
+ when :remove
492
+ branch_merge and dirstate.remove(file) or dirstate.forget(file)
493
+ when :add
494
+ dirstate.add file unless branch_merge
495
+ when :forget
496
+ dirstate.forget file
497
+ when :get
498
+ branch_merge and dirstate.dirty(file) or dirstate.normal(file)
499
+ when :merge
500
+ file2, file_dest, flag, move = action[2..-1]
501
+ if branch_merge
502
+ dirstate.merge(file_dest)
503
+ if file != file2 #copy/rename
504
+ dirstate.remove file if move
505
+ dirstate.copy(file, file_dest) if file != file_dest
506
+ dirstate.copy(file2, file_dest) if file == file_dest
507
+ end
508
+ else
509
+ dirstate.maybe_dirty(file_dest)
510
+ dirstate.forget(file) if move
511
+ end
512
+ when :directory
513
+ file2, file_dest, flag = action[2..-1]
514
+ next if !file2 && !(dirstate.include?(file))
515
+
516
+ if branch_merge
517
+ dirstate.add file_dest
518
+ if file && file.any?
519
+ dirstate.remove file
520
+ dirstate.copy file, file_dest
521
+ end
522
+ dirstate.copy file2, file_dest if file2 && file2.any?
523
+ else
524
+ dirstate.normal file_dest
525
+ dirstate.forget file if file && file.any?
526
+ end
527
+ end
528
+ end
529
+ end
530
+ end
531
+ end
532
+ end