amp 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 +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