amp 0.5.2 → 0.5.3

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 (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. data/test/test_helper.rb +0 -15
@@ -0,0 +1,322 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+
5
+ ##
6
+ # = TagManager
7
+ # This module handles all tag-related (but not branch tag) functionality
8
+ # of the repository.
9
+ module TagManager
10
+ include Amp::Mercurial::RevlogSupport::Node
11
+
12
+ TAG_FORBIDDEN_LETTERS = ":\r\n"
13
+ ##
14
+ # Returns a list of all the tags as a hash, mapping each tag to the tip-most
15
+ # changeset it applies to.
16
+ #
17
+ # @return [Hash] a hash, sorted by revision index (i.e. its order in the commit
18
+ # history), with the keys:
19
+ # :revision => the revision index of the changeset,
20
+ # :tag => the name of the tag,
21
+ # :node => the node-id of the changeset
22
+ def tag_list
23
+ list = []
24
+ tags.each do |tag, node|
25
+ begin
26
+ r = changelog.revision(node)
27
+ rescue
28
+ r = -2
29
+ end
30
+ list << {:revision => r, :tag => tag, :node => node}
31
+ end
32
+ list.sort {|i1, i2| i1[:revision] <=> i2[:revision] }
33
+ end
34
+
35
+ ##
36
+ # Returns the tag-type of the given tag. This could be "local", which means it is
37
+ # not shared among repositories.
38
+ #
39
+ # @param [String] tag_name the name of the tag to lookup, such as "tip"
40
+ # @return [String] the type of the requested tag, such as "local".
41
+ def tag_type(tag_name)
42
+ tags #load the tags
43
+ @tags_type_cache[tag_name]
44
+ end
45
+
46
+ ##
47
+ # Returns the tags for a given revision (by ID).
48
+ #
49
+ # @param [String] node the node-ID, in binary form.
50
+ # @return [Array<String>] a list of tags for the given node.
51
+ def tags_for_node(node)
52
+ return (@tags_for_node_cache[node] || []) if (@tags_for_node_cache ||= nil)
53
+ @tags_for_node_cache = {}
54
+ tags.each do |tag, tag_node|
55
+ @tags_for_node_cache[tag_node] ||= [] # make sure there's an array
56
+ @tags_for_node_cache[tag_node] << tag # add the tag to it
57
+ end
58
+ @tags_for_node_cache[node] || []
59
+ end
60
+
61
+ ##
62
+ # Invalidates the tag cache. Removes all ivars relating to tags.
63
+ def invalidate_tag_cache!
64
+ @tags_for_node_cache = nil
65
+ @tags_cache = nil
66
+ @tags_type_cache = nil
67
+ end
68
+
69
+ ##
70
+ # Loads all of the tags from the tag-cache file stored as .hgtags. Returns
71
+ # a hash, mapping tag names to node-IDs.
72
+ #
73
+ # @return [Hash] a hash mapping tags to node-IDs.
74
+ def tags
75
+ return @tags_cache if (@tags_cache ||= nil)
76
+
77
+ global_tags, tag_types = {}, {}
78
+
79
+ f = nil
80
+ # For each current .hgtags file in our history (including multi-heads), read in
81
+ # the tags
82
+ hg_tags_nodes.each do |rev, node, file_node|
83
+ # get the file
84
+ f = (f && f.file(file_node.file_node)) || self.versioned_file(".hgtags", :file_id => file_node.file_node)
85
+ # read the tags, as global, because they're versioned.
86
+ read_tags(f.data.split("\n"), f, "global", global_tags, tag_types)
87
+ end
88
+
89
+ # Now do locally stored tags, that aren't committed/versioned
90
+ begin
91
+ # get the local file, stored in .hg/
92
+ data = @hg_opener.read("localtags")
93
+ # Read the tags as local, because they are not versioned
94
+ read_tags(data.split_newlines,"local_tags","local",global_tags, tag_types)
95
+ rescue Errno::ENOENT
96
+ # do nothing. most people don't have this file.
97
+ end
98
+ # Save our tags for use later. Use ivars.
99
+ @tags_cache = {}
100
+ @tags_type_cache = {}
101
+ # Go through the global tags to store them in the cache
102
+ global_tags.each do |k, nh|
103
+ # the node ID is the first part of the stored data
104
+ n = nh.first
105
+
106
+ # update the cache
107
+ @tags_cache[k] = n unless n == NULL_ID
108
+ @tags_type_cache[k] = tag_types[k]
109
+ end
110
+
111
+ # tip = special tag
112
+ @tags_cache["tip"] = self.changelog.tip
113
+
114
+ # return our tags
115
+ @tags_cache
116
+ end
117
+
118
+ ##
119
+ # Adds the given tag to a given changeset, and commits to preserve it.
120
+ #
121
+ # @param [String, Array] names a list of tags (or just 1 tag) to apply to
122
+ # the changeset
123
+ # @param [String, Integer] node the node to apply the tag to
124
+ # @param [Hash] opts the opts for tagging
125
+ # @option [String] opts message ("added tag _tag_ to changeset _node_")
126
+ # the commit message to use.
127
+ # @option [Boolean] opts local (false) is the tag a local one? I.E., will it be
128
+ # shared across repos?
129
+ # @option [String] opts user ($USER) the username to apply for the commit
130
+ # @option [Time] opts time (Time.now) what should the commit-time be marked as?
131
+ # @option [String] opts parent (nil) The parent revision of the one we
132
+ # are tagging. or something.
133
+ # @option [Hash] opts extra ({}) the extra data to apply for the commit.
134
+ def apply_tag(names, node, opts={})
135
+ use_dirstate = opts[:parent].nil?
136
+ all_letters = names.kind_of?(Array) ? names.join : names
137
+ (TAG_FORBIDDEN_LETTERS.size-1).downto 0 do |i|
138
+ if all_letters.include? TAG_FORBIDDEN_LETTERS[i, 1]
139
+ raise abort("#{TAG_FORBIDDEN_LETTERS[i,1]} not allowed in a tag name!")
140
+ end
141
+ end
142
+
143
+ prev_tags = ""
144
+ # If it's a local tag, we just write the file and bounce. mad easy yo.
145
+ if opts[:local]
146
+ @hg_opener.open("localtags","r+") do |fp|
147
+ prev_tags = fp.read
148
+ write_tags(fp,names, nil, prev_tags)
149
+ end
150
+ return
151
+ end
152
+
153
+ # ok, it's a global tag. now we have to handle versioning and shit.
154
+ if use_dirstate
155
+ prev_tags = working_read(".hgtags") rescue ""
156
+ file = @file_opener.open(".hgtags","a")
157
+ else
158
+ prev_tags = versioned_file(".hgtags", :change_id => parent).data
159
+ file = @file_opener.open(".hgtags","w")
160
+
161
+ file.write prev_tags if prev_tags && prev_tags.any?
162
+ end
163
+
164
+ write_tags(file, node, names, prev_tags)
165
+ file.close
166
+ if use_dirstate && dirstate[".hgtags"].status == :untracked
167
+ staging_area.add(".hgtags")
168
+ end
169
+
170
+ tag_node = commit :modified => [".hgtags"],
171
+ :message => opts[:message],
172
+ :user => opts[:user],
173
+ :date => opts[:date],
174
+ :parents => [opts[:parent]],
175
+ :extra => opts[:extra]
176
+
177
+ tag_node
178
+ end
179
+
180
+ private
181
+
182
+ ##
183
+ # Goes through all the heads in the repository, getting the state of the .hgtags
184
+ # file at each head. We then return a list, with each entry mapping revision index
185
+ # and node ID to the .hgtags file at that head. If two different heads have the same
186
+ # .hgtags file, only 1 is returned with it.
187
+ #
188
+ # @return [(Fixnum, String, VersionedFile)] each head with a different .hgtags file
189
+ # at that point. That way we have the most recent copy of .hgtags, even if the file
190
+ # differs on different heads.
191
+ def hg_tags_nodes
192
+ heads = self.heads.reverse
193
+ last = {}
194
+ return_list = []
195
+ heads.each do |node|
196
+ changeset = self[node]
197
+ rev = changeset.revision
198
+ begin
199
+ file_node = changeset.get_file(".hgtags")
200
+ rescue
201
+ next
202
+ end
203
+ return_list << [rev, node, file_node]
204
+ return_list[last[file_node]] = nil if last[file_node] # replace old head
205
+
206
+ last[file_node] = return_list.size - 1
207
+ end
208
+ return return_list.reject {|item| item.nil?}
209
+ end
210
+
211
+ ##
212
+ # Writes the tags to the given stream. This method must be aware of previously
213
+ # written tags. Also, any new tags must state what the node to use for writing is.
214
+ #
215
+ # @param [IO, #write] file the output stream to write to. Could be a file, or any IO.
216
+ # @param [String] node a binary node ID for any newly-added tags
217
+ # @param [Array] names A list of all the tag names to write
218
+ # @param [Hash] prev_tags the previously written string (or something)
219
+ def write_tags(file, node, names, prev_tags)
220
+ file.seek(0, IO::SEEK_END)
221
+ if prev_tags && prev_tags.any? && prev_tags[-1,1] != "\n"
222
+ file.write "\n"
223
+ end
224
+ names.each do |name|
225
+ if @tags_type_cache && @tags_type_cache[name]
226
+ old = @tags_cache[name] || NULL_ID
227
+ file.write("#{old.hexlify} #{name}\n")
228
+ end
229
+ file.write("#{node.hexlify} #{name}\n")
230
+ end
231
+ end
232
+
233
+ ##
234
+ # Reads in an .hgtags file and parses it, while respecting global tags.
235
+ # This is where things get kinda messy, because otherwise we'd just be parsing
236
+ # a simple text file. Anyway, global_tags are tags like "tip" -> the current tip
237
+ # - they're programmatically assigned tags.
238
+ #
239
+ # @param [Array<String>] lines the file, split into lines
240
+ # @param [String] fn the file that we are parsing, only for debug purposes
241
+ # @param [String] tag_type what kind of tag are we looking at? usually "local"
242
+ # or "global" or nothing. For example, a local-only tag isn't committed - these
243
+ # need to be treated differently.
244
+ # @param [Hash] global_tags maps nodes to global tags, such as "tip".
245
+ # @param [Hash] tag_types maps nodes to what type of tag they are
246
+ # @return [Hash] the list of tags we have read in.
247
+ #
248
+ # @todo encodings, handle local encodings
249
+ def read_tags(lines, fn, tag_type, global_tags, tag_types)
250
+ # This is our tag list we'll be building.
251
+ file_tags = {}
252
+
253
+ # Each line is of the format:
254
+ # [char * 40, node ID] [tag]
255
+ # 0123456789012345678901234567890123456789 crazymerge
256
+ lines.each_with_index do |line, count|
257
+ # skip if we have no text to parse
258
+ next if line.nil? || line.empty?
259
+
260
+ # split once, so we could theoretically have spaces in tag names
261
+ s = line.split(" ", 2)
262
+ # make sure we parsed the tag entry alright
263
+ if s.size != 2
264
+ UI::warn "Can't parse entry, filename #{fn} line #{count}"
265
+ next
266
+ end
267
+
268
+ # Node comes first, tag comes second
269
+ node, tag = s
270
+ tag.strip! #TODO: encodings, handle local encodings
271
+
272
+ # Convert to binary so we can look it up in our repo
273
+ bin_node = node.unhexlify
274
+
275
+ # Is it in our repo? if not, skip to the next tag.
276
+ unless self.changelog.node_map[bin_node]
277
+ UI::warn "Tag #{key} refers to unknown node"
278
+ next
279
+ end
280
+
281
+ # heads is a list of the nodes that have this same tag
282
+ heads = []
283
+ # have we already seen this tag?
284
+ if file_tags[tag]
285
+ # pull out the old data
286
+ n, heads = file_tags[tag]
287
+ # add our new node to the list for this tag
288
+ heads << n
289
+ end
290
+ # update our tag list
291
+ file_tags[tag] = [bin_node, heads]
292
+ end
293
+
294
+ # For each tag that we have...
295
+ file_tags.each do |k, nh|
296
+ # Is this a reserved, global tag? Or, just one that's been used already?
297
+ # like "tip"? if not, we're ok
298
+ unless global_tags[k]
299
+ # update global_tags with our new tag
300
+ global_tags[k] = nh
301
+ # set the tag_types hash as well
302
+ tag_types[k] = tag_type
303
+ next
304
+ end
305
+ # we prefer the global tag if:
306
+ # it supercedes us OR
307
+ # mutual supercedes and it has a higher rank
308
+ # otherwise we win because we're tip-most
309
+ an, ah = nh
310
+ bn, bh = global_tags[k]
311
+ if [bn != an, bh.include?(an), (!ah.include?(bn) || bh.size > ah.size)].all?
312
+ an = bn
313
+ end
314
+ ah += bh.select {|n| !ah.include?(n) }
315
+ global_tags[k] = an, ah
316
+ tag_types[k] = tag_type
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,543 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+
5
+ ##
6
+ # This module contains all the code that makes a repository able to
7
+ # update its working directory.
8
+ module Updatable
9
+ include Amp::Mercurial::RevlogSupport::Node
10
+
11
+ ##
12
+ # Updates the repository to the given node. One of the major operations on a repository.
13
+ # This will update the working directory to the given node.
14
+ #
15
+ # @todo add lock
16
+ # @param [String, Integer] node the revision to which we are updating the repository. Can
17
+ # be either nil, a node ID, or an integer. If it is nil, it will update
18
+ # to the latest revision.
19
+ # @param [Boolean] branch_merge whether to merge between branches
20
+ # @param [Boolean] force whether to force branch merging or file overwriting
21
+ # @param [Proc, #call] partial a function to filter file lists (dirstate not updated if this
22
+ # is passed)
23
+ # @return [Array<Integer>] a set of statistics about the update. In the form:
24
+ # [updated, merged, removed, unresolved] where each entry is the # of files in that category.
25
+ def update(node=nil, branch_merge=false, force=false, partial=nil)
26
+ # debugger
27
+ self.status(:node_1 => self.dirstate.parents.first, :node_2 => nil)
28
+ working_changeset = self[nil]
29
+ # tip of current branch
30
+ node ||= branch_tags[working_changeset.branch]
31
+ node = self.lookup("tip") if node.nil? && working_changeset.branch == "default"
32
+ if node.nil?
33
+ raise abort("branch #{working_changeset.branch} not found")
34
+ end
35
+
36
+ overwrite = force && !branch_merge
37
+ parent_list = working_changeset.parents
38
+ parent1, parent2 = parent_list.first, self[node]
39
+ parent_ancestor = parent1.ancestor(parent2)
40
+
41
+ fp1, fp2, xp1, xp2 = parent1.node, parent2.node, parent1.to_s, parent2.to_s
42
+ fast_forward = false
43
+
44
+ ## In this section, we make sure that we can actually do an update.
45
+ ## No use starting an udpate if we can't finish!
46
+
47
+ if !overwrite && parent_list.size > 1
48
+ raise abort("outstanding uncommitted merges")
49
+ end
50
+
51
+ if branch_merge
52
+ if parent_ancestor == parent2
53
+ raise abort("can't merge with ancestor")
54
+ elsif parent_ancestor == parent1
55
+ if parent1.branch != parent2.branch
56
+ fast_forward = true
57
+ else
58
+ raise abort("nothing to merge (use 'hg update' or check"+
59
+ " 'hg heads')")
60
+ end
61
+ end
62
+ if !force && (working_changeset.changed_files.any? || working_changeset.deleted.any?)
63
+ raise abort("oustanding uncommitted changes")
64
+ end
65
+ elsif !overwrite
66
+ if parent_ancestor == parent1 || parent_ancestor == parent2
67
+ # do nothing
68
+ elsif parent1.branch == parent2.branch
69
+ if working_changeset.changed_files.any? || working_changeset.deleted.any?
70
+ raise abort("crosses branches (use 'hg merge' or "+
71
+ "'hg update -C' to discard changes)")
72
+ end
73
+ raise abort("crosses branches (use 'hg merge' or 'hg update -C')")
74
+ elsif working_changeset.changed_files.any? || working_changeset.deleted.any?
75
+ raise abort("crosses named branches (use 'hg update -C'"+
76
+ " to discard changes)")
77
+ else
78
+ overwrite = true
79
+ end
80
+ end
81
+
82
+ ## Alright, now let's figure out exactly what we have to do to make this update.
83
+ ## Shall we?
84
+
85
+ actions = []
86
+ check_unknown(working_changeset, parent2) if force
87
+ check_collision(parent2) if false # case-sensitive file-system? seriously?
88
+
89
+ actions += forget_removed(working_changeset, parent2, branch_merge)
90
+ actions += manifest_merge(working_changeset, parent2, parent_ancestor,
91
+ overwrite, partial)
92
+ #p [working_changeset, parent2, parent_ancestor] # KILLME
93
+ ## Apply phase - apply the changes we just generated.
94
+ unless branch_merge # just jump to the new revision
95
+ fp1, fp2, xp1, xp2 = fp2, NULL_ID, xp2, ''
96
+ end
97
+ unless partial
98
+ run_hook :preupdate, :throw => true, :parent1 => xp1, :parent2 => xp2
99
+ end
100
+
101
+ stats = apply_updates(actions, working_changeset, parent2)
102
+
103
+ unless partial
104
+ record_updates(actions, branch_merge)
105
+ dirstate.parents = [fp1, fp2]
106
+ if !branch_merge && !fast_forward
107
+ dirstate.branch = parent2.branch
108
+ end
109
+ run_hook :update, :parent1 => xp1, :parent2 => xp2, :error => stats[3]
110
+ dirstate.write
111
+ end
112
+
113
+ return stats
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.all_files.include?(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} #{target_changeset[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
+
220
+ UI::status("resolving manifests")
221
+ UI::debug(" overwrite #{overwrite} partial #{partial}")
222
+ UI::debug(" ancestor #{ancestor} local #{local} remote #{remote}")
223
+
224
+ local_manifest = local.manifest_entry
225
+ remote_manifest = remote.manifest_entry
226
+ ancestor_manifest = ancestor.manifest_entry
227
+ backwards = (ancestor == remote)
228
+ action_list = []
229
+ copy, copied, diverge = {}, {}, {}
230
+
231
+ flag_merge = lambda do |file_local, file_remote, file_ancestor|
232
+ file_remote = file_ancestor = file_local unless file_remote
233
+
234
+ a = ancestor_manifest.flags[file_ancestor]
235
+ m = local_manifest.flags[file_local]
236
+ n = remote_manifest.flags[file_remote]
237
+
238
+ return m if m == n # flags are identical, we're fine
239
+
240
+ if m.any? && n.any?
241
+ unless a.any? # i'm so confused, ask the user what the flag should be!
242
+ r = UI.ask("conflicting flags for #{file_local} (n)one, e(x)ec, or "+
243
+ "sym(l)ink?")
244
+ return (r != "n") ? r : ''
245
+ end
246
+ return m == a ? n : m
247
+ end
248
+
249
+ return m if m && m.any? && m != a # changed from a to m
250
+ return n if n && 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::Mercurial::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
+ else # case 2 A,B/B/B, # case 4,21 A/B/B
306
+ act["local copied to #{file2}", :merge, file, file2, file, # or local moved to
307
+ flag_merge[file, file2, file2], false]
308
+ end
309
+ elsif ancestor_manifest[file]
310
+ if node != ancestor_manifest[file] && !overwrite
311
+ if UI.ask("local changed #{file} which remote deleted\n" +
312
+ "use (c)hanged version or (d)elete?") == "d"
313
+ act["prompt delete", :remove, file]
314
+ else
315
+ act["prompt keep", :add, file]
316
+ end
317
+ else
318
+ act["other deleted", :remove, file]
319
+ end
320
+ else
321
+ if (overwrite && node[20..-1] != "u") || (backwards && node[20..-1].empty?)
322
+ act["remote deleted", :remove, file]
323
+ end
324
+ end
325
+ end
326
+
327
+ remote_manifest.each do |file, node|
328
+ next if partial && !(partial[file])
329
+ next if local_manifest[file]
330
+ next if copied[file]
331
+ if copy[file]
332
+ file2 = copy[file]
333
+ if !(local_manifest[file2])
334
+ act["local renamed directory to #{file2}", :directory, nil, file,
335
+ file2, remote_manifest.flags[file]]
336
+ elsif remote_manifest[file2]
337
+ act["remote copied to #{file}", :merge, file2, file, file,
338
+ flag_merge[file2, file, file2], false]
339
+ else
340
+ act["remote moved to #{file}", :merge, file2, file, file,
341
+ flag_merge[file2, file, file2], true]
342
+ end
343
+ elsif ancestor_manifest[file]
344
+ if overwrite || backwards
345
+ act["recreating", :get, file, remote_manifest.flags[file]]
346
+ elsif node != ancestor_manifest[file]
347
+ if UI.ask("remote changed #{file} which local deleted\n" +
348
+ "use (c)hanged version or leave (d)eleted?") == "c"
349
+ act["prompt recreating", :get, file, remote_manifest.flags[file]]
350
+ end
351
+ end
352
+ else
353
+ act["remote created", :get, file, remote_manifest.flags[file]]
354
+ end
355
+ end
356
+
357
+ action_list
358
+ end
359
+
360
+ ##
361
+ # Compare two actions in the update action list
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
+ moves = scan_for_merges actions, working_changeset, target_changeset
395
+
396
+ # If we're moving any files, we can remove renamed ones now
397
+ moves.each do |file|
398
+ if File.amp_lexist?(working_join(file))
399
+ UI.debug("removing #{file}")
400
+ File.unlink(working_join(file))
401
+ end
402
+ end
403
+
404
+ # TODO: add path auditor
405
+
406
+ actions.each do |action|
407
+ file, choice = action[0], action[1]
408
+ next if file && file[0,1] == "/"
409
+ case choice
410
+ when :remove
411
+ UI.note "removing #{file}"
412
+ File.unlink(working_join(file))
413
+ removed << file
414
+ when :merge
415
+ file2, file_dest, flags, move = action[2..-1]
416
+ result = merge_state.resolve(file_dest, working_changeset, target_changeset)
417
+
418
+ unresolved << file if result
419
+ updated << file if result.nil?
420
+ merged << file if result == false
421
+
422
+ File.amp_set_executable(working_join(file_dest), flags && flags.include?('x'))
423
+ if (file != file_dest && move && File.amp_lexist?(working_join(file)))
424
+ UI.debug("removing #{file}")
425
+ File.unlink(working_join(file))
426
+ end
427
+ when :get
428
+ flags = action[2]
429
+ UI.note("getting #{file}")
430
+ data = target_changeset.get_file(file).data
431
+ working_write(file, data, flags)
432
+ updated << file
433
+ when :directory
434
+ file2, file_dest, flags = action[2..-1]
435
+ if file && file.any?
436
+ UI.note("moving #{file} to #{file_dest}")
437
+ File.move(file, file_dest)
438
+ end
439
+ if file2 && file2.any?
440
+ UI.note("getting #{file2} to #{file_dest}")
441
+ data = target_changeset.get_file(file2).data
442
+ working_write(file_dest, data, flags)
443
+ end
444
+ updated << file
445
+ when :divergent_rename
446
+ filelist = action[2]
447
+ UI.warn("detected divergent renames of #{f} to:")
448
+ filelist.each {|fn| UI.warn fn }
449
+ when :exec
450
+ flags = action[2]
451
+ File.amp_set_executable(working_join(file), flags.include?('x'))
452
+ end
453
+
454
+ end
455
+
456
+ hash = {:updated => updated ,
457
+ :merged => merged ,
458
+ :removed => removed ,
459
+ :unresolved => unresolved }
460
+
461
+ class << hash
462
+ def success?; self[:unresolved].empty?; end
463
+ end
464
+
465
+ hash
466
+ end
467
+
468
+ ##
469
+ # Add merges in the action list to the merge state.
470
+ #
471
+ # @param [Array<Object>] actions
472
+ def scan_for_merges(actions, working_changeset, target_changeset)
473
+ moves = []
474
+ # prescan for merges in the list of actions.
475
+ actions.select {|act| act[1] == :merge}.each do |a|
476
+ # destructure the list
477
+ file, action, file2, filename_dest, flags, move = a
478
+ UI.debug("preserving #{file} for resolve of #{filename_dest}")
479
+ # look up our changeset for the merge state entry
480
+ vf_local = working_changeset[file]
481
+ vf_other = target_changeset[file2]
482
+ vf_base = vf_local.ancestor(vf_other) || versioned_file(file, :file_id => NULL_REV)
483
+ # track this merge!
484
+ merge_state.add(vf_local, vf_other, vf_base, filename_dest, flags)
485
+
486
+ moves << file if file != filename_dest && move
487
+ end
488
+ moves
489
+ end
490
+
491
+ ##
492
+ # Records all the updates being made while merging to the new working directory.
493
+ # It records them by writing to the dirstate.
494
+ #
495
+ # @param [Array<Array>] actions a list of actions to take
496
+ # @param [Boolean] branch_merge is this a branch merge?
497
+ def record_updates(actions, branch_merge)
498
+ actions.each do |action|
499
+ file, choice = action[0], action[1]
500
+ case choice
501
+ when :remove
502
+ branch_merge and dirstate.remove(file) or dirstate.forget(file)
503
+ when :add
504
+ dirstate.add file unless branch_merge
505
+ when :forget
506
+ dirstate.forget file
507
+ when :get
508
+ branch_merge and dirstate.dirty(file) or dirstate.normal(file)
509
+ when :merge
510
+ file2, file_dest, flag, move = action[2..-1]
511
+ if branch_merge
512
+ dirstate.merge(file_dest)
513
+ if file != file2 #copy/rename
514
+ dirstate.remove file if move
515
+ dirstate.copy(file, file_dest) if file != file_dest
516
+ dirstate.copy(file2, file_dest) if file == file_dest
517
+ end
518
+ else
519
+ dirstate.maybe_dirty(file_dest)
520
+ dirstate.forget(file) if move
521
+ end
522
+ when :directory
523
+ file2, file_dest, flag = action[2..-1]
524
+ next if !file2 && !(dirstate.include?(file))
525
+
526
+ if branch_merge
527
+ dirstate.add file_dest
528
+ if file && file.any?
529
+ dirstate.remove file
530
+ dirstate.copy file, file_dest
531
+ end
532
+ dirstate.copy file2, file_dest if file2 && file2.any?
533
+ else
534
+ dirstate.normal file_dest
535
+ dirstate.forget file if file && file.any?
536
+ end
537
+ end
538
+ end
539
+ end
540
+ end
541
+ end
542
+ end
543
+ end