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,218 @@
1
+ module Amp
2
+ class StandardErrorReporter
3
+ def self.report str
4
+ UI.err str
5
+ end
6
+ end
7
+
8
+ module Mercurial
9
+
10
+ ##
11
+ # Provides a journal interface so when a large number of transactions
12
+ # are occurring, and any one could fail, we can rollback the changes.
13
+ class Journal
14
+ DEFAULT_OPTS = {:reporter => StandardErrorReporter, :after_close => nil, :createmode => nil}
15
+
16
+ attr_accessor :reporter, :journal, :after_close, :opener
17
+
18
+ ##
19
+ # @return [Amp::Mercurial::Journal]
20
+ def self.start(file, opts={})
21
+ opts = DEFAULT_OPTS.merge(opts)
22
+ opts[:journal] = file
23
+ journal = Journal.new opts
24
+
25
+ if block_given?
26
+ begin
27
+ yield journal
28
+ rescue
29
+ journal.delete
30
+ ensure
31
+ journal.close
32
+ end
33
+ end
34
+
35
+ journal
36
+ end
37
+
38
+ ##
39
+ # Initializes the journal to get ready for some transactions.
40
+ #
41
+ # @param [#report] reporter an object that will keep track of any alerts
42
+ # we have to send out. Must respond to #report.
43
+ # @param [String] journal the path to the journal file to use
44
+ # @param [Integer] createmode An octal number that sets the filemode
45
+ # of the journal file we'll be using
46
+ # @param [Proc] after_close A proc to call (with no args) after we
47
+ # close (finish) the transaction.
48
+ def initialize(opts = {}, &after_close)
49
+ opts = DEFAULT_OPTS.merge(opts)
50
+ opts[:journal] ||= ".journal#{rand(10000)}"
51
+ @count = 1
52
+ @entries = []
53
+ @map = {}
54
+ @journal_file = opts[:journal]
55
+ self.reporter = opts[:reporter]
56
+ self.after_close = after_close
57
+ self.opener = opts[:opener]
58
+
59
+ @file = Kernel::open(@journal_file, "w")
60
+
61
+ FileUtils.chmod(createmode & 0666, @journal_file) unless opts[:createmode].nil?
62
+
63
+ UI.status "opening journal"
64
+ end
65
+
66
+ ##
67
+ # Kills the journal - used when shit goes down and we gotta give up
68
+ # on the transactions.
69
+ def delete
70
+ if @journal_file
71
+ abort if @entries.any?
72
+ @file.close
73
+ FileUtils.safe_unlink @journal_file
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Adds an entry to the journal. Since all our files are just being appended
79
+ # to all the time, all we really need is to keep track of how long the file
80
+ # was when we last knew it to be safe. In other words, if the file started
81
+ # off at 20 bytes, then an error happened, we just truncate it to 20 bytes.
82
+ #
83
+ # All params should be contained in the array
84
+ #
85
+ # @param h [Hash] a hash of the args
86
+ # @option h [String] :file the name of the file
87
+ # @option h [Integer] :offset the offset of :file at the last known good revision
88
+ # @option h [String] :data any extra data
89
+ def add_entry(h={})
90
+ return if @map[h[:file]]
91
+ @entries << {:file => h[:file], :offset => h[:offset], :data => h[:data]}
92
+ @map[h[:file]] = @entries.size - 1
93
+
94
+ # tell the journal how to truncate this revision
95
+ @file.write "#{h[:file]}\0#{h[:offset]}\n"
96
+ @file.flush
97
+ end
98
+
99
+ ##
100
+ # Alias for {add_entry}
101
+ alias :<< :add_entry
102
+
103
+ ##
104
+ # Finds the entry for a given file's path
105
+ #
106
+ # @param [String] file the path to the file
107
+ # @return [Hash] A hash with the values :file, :offset, and :data, as
108
+ # they were when they were stored by {add_entry} or {update}
109
+ def find_file(file)
110
+ return @entries[@map[file]] if @map[file]
111
+ nil
112
+ end
113
+
114
+ ##
115
+ # Alias for {find_file}
116
+ alias :find :find_file
117
+
118
+ ##
119
+ # Updates an entry's data, based on the filename. The file must already
120
+ # have been journaled.
121
+ #
122
+ # @param [String] file the file to update
123
+ # @param [Fixnum] offset the new offset to store
124
+ # @param [String] data the new data to store
125
+ def replace(file, offset, data=nil)
126
+ raise IndexError.new("journal lookup failed #{file}") unless @map[file]
127
+ index = @map[file]
128
+ @entries[index] = {:file => file, :offset => offset, :data => data}
129
+ @file.write("#{file}\0#{offset}\n")
130
+ @file.flush
131
+ end
132
+
133
+ ##
134
+ # Alias for {replace}
135
+ alias :update :replace
136
+
137
+ ##
138
+ # No godly idea what this is for
139
+ def nest
140
+ @count += 1
141
+ self
142
+ end
143
+
144
+ ##
145
+ # Is the journal running right now?
146
+ def running?
147
+ @count > 0
148
+ end
149
+
150
+ ##
151
+ # Closes up the journal. Will call the after_close proc passed
152
+ # during instantiation.
153
+ def close
154
+ UI::status "closing journal"
155
+ @count -= 1
156
+ return if @count != 0
157
+ @file.close
158
+ @entries = []
159
+ if @after_close
160
+ @after_close.call
161
+ else
162
+ FileUtils.safe_unlink(@journal_file)
163
+ end
164
+ @journal_file = nil
165
+ end
166
+
167
+ ##
168
+ # Abort, abort! abandon ship! This rolls back any changes we've made
169
+ # during the current journalling session.
170
+ def abort
171
+ UI::status "aborting journal"
172
+ return unless @entries && @entries.any?
173
+ @reporter.report "transaction abort!\n"
174
+ @entries.each do |hash|
175
+ file, offset = hash[:file], hash[:offset]
176
+ begin
177
+ self.opener.open(file, "a") do |fp|
178
+ p "OPENED #{file} truncating to #{offset}"
179
+ fp.truncate offset
180
+ end
181
+ rescue
182
+ @reporter.report "Failed to truncate #{File.join(".hg","store",file)}\n"
183
+ end
184
+ end
185
+ @entries = []
186
+ @reporter.report "rollback completed\n"
187
+ end
188
+
189
+ ##
190
+ # If we crashed during an abort, the journal file is gonna be sitting aorund
191
+ # somewhere. So, we should rollback any changes it left lying around.
192
+ #
193
+ # @param [String] file the journal file to use during the rollback
194
+ def self.rollback(opener, file)
195
+ files = {}
196
+ File.open(file, "r") do |fp|
197
+ fp.each_line do |line|
198
+ file, offset = line.split("\0")
199
+ files[file] = offset.to_i
200
+ end
201
+ end
202
+ files.each do |file_to_truncate, offset|
203
+ if o > 0
204
+ opener.open(file_to_truncate, "a") do |fp|
205
+ fp.truncate o.to_i
206
+ end
207
+ else
208
+ opener.open(file_to_truncate, "a") do |fp|
209
+ fn = fp.path
210
+ end
211
+ FileUtils.safe_unlink fn
212
+ end
213
+ end
214
+ FileUtils.safe_unlink file
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,210 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+
5
+ ##
6
+ # = Lock
7
+ # Manages a given lock file, indicating that the enclosing folder should not
8
+ # be modified. Typically used during destructive operations on a repo (such as
9
+ # a commit or push).
10
+ #
11
+ # We must be compatible with Mercurial's lock format, unfortunately. Doesn't life
12
+ # suck?
13
+ #####
14
+ ##### From Mercurial code, explaining their format:
15
+ #####
16
+ #
17
+ # lock is symlink on platforms that support it, file on others.
18
+ #
19
+ # symlink is used because create of directory entry and contents
20
+ # are atomic even over nfs.
21
+ #
22
+ # old-style lock: symlink to pid
23
+ # new-style lock: symlink to hostname:pid
24
+ class Lock
25
+ @@host = nil
26
+
27
+ ##
28
+ # Initializes the lock to a given file name, and creates the lock, effectively
29
+ # locking the containing directory.
30
+ #
31
+ # @param [String] file the path to the the lock file to create
32
+ # @param [Hash<Symbol => Object>] opts the options to use when creating the lock
33
+ # @option [Integer] options :timeout (-1) the length of time to keep trying to create the lock.
34
+ # defaults to -1 (indefinitely)
35
+ # @option [Proc, #call] options :release_fxn (nil) A proc to run when the
36
+ # lock is released
37
+ # @option [String] options :desc (nil) A description of the lock
38
+ def initialize(file, opts={:timeout => -1})
39
+ @file = file
40
+ @held = false
41
+ @timeout = opts[:timeout]
42
+ @release_fxn = opts[:release_fxn]
43
+ @description = opts[:desc]
44
+ apply_lock
45
+ end
46
+
47
+ ##
48
+ # Applies the lock. Will sleep the thread for +timeout+ time trying to apply the lock before
49
+ # giving up and raising an error.
50
+ def apply_lock
51
+ timeout = @timeout
52
+ while true do
53
+ begin
54
+ # try_lock will raise of there is already a lock.
55
+ try_lock
56
+ return true
57
+ rescue LockHeld => e
58
+ # We'll put up with this exception for @timeout times, then give up.
59
+ if timeout != 0
60
+ sleep(1)
61
+ timeout > 0 && timeout -= 1
62
+ next
63
+ end
64
+ # Timeout's up? Raise an exception.
65
+ raise LockHeld.new(Errno::ETIMEDOUT::Errno, e.filename, @desc, e.locker)
66
+ end
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Attempts to apply the lock. Raises if unsuccessful. Contains the logic for actually naming
72
+ # the lock.
73
+ def try_lock
74
+ if @@host.nil?
75
+ @@host = Socket.gethostname
76
+ end
77
+ lockname = "#{@@host}:#{Process.pid}"
78
+ while !@held
79
+ begin
80
+ make_a_lock(@file, lockname)
81
+ @held = true
82
+ rescue Errno::EEXIST
83
+ locker = test_lock
84
+ unless locker.nil?
85
+ raise LockHeld.new(Errno::EAGAIN::Errno, @file, @desc, locker)
86
+ end
87
+ rescue SystemCallError => e
88
+ raise LockUnavailable.new(e.errno, e.to_s, @file, @desc)
89
+ end
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Creates a lock at the given location, with info about the locking process. Uses
95
+ # a symlink if possible, because even over NFS, creating a symlink is atomic. Nice.
96
+ # Otherwise, it will call make_a_lock_in_file on inferior OS's (cough windows cough)
97
+ # and put the data in there.
98
+ #
99
+ # The symlink is actually a non-working symlink - it points the filename (such as "hglock")
100
+ # to the data, even though the data is not an actual file. So hglock -> "medgar:25043" is
101
+ # a sort-of possible lock this method would create.
102
+ #
103
+ # @param [String] file the filename of the lock
104
+ # @param [String] info the info to store in the lock
105
+ def make_a_lock(file, info)
106
+ begin
107
+ File.symlink(info, file)
108
+ rescue Errno::EEXIST
109
+ raise
110
+ rescue
111
+ make_a_lock_in_file(file, info)
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Creates a lock at the given location, storing the info about the locking process in
117
+ # an actual lock file. These locks are not preferred, because symlinks are atomic even
118
+ # over NFS. Anyway, very simple. Create the file, write in the info, close 'er up.
119
+ # That's 1 line in ruby, folks.
120
+ #
121
+ # @see make_a_lock
122
+ # @param [String] file the filename of the lock
123
+ # @param [String] info the info to store in the lock
124
+ def make_a_lock_in_file(file, info)
125
+ File.open(file, "w+") {|out| out.write info }
126
+ end
127
+
128
+ ##
129
+ # Reads in the data associated with a lock file.
130
+ #
131
+ # @param [String] file the path to the lock file to read
132
+ # @return [String] the data in the lock. In the format "#{locking_host}:#{locking_pid}"
133
+ def read_lock(file)
134
+ begin
135
+ return File.readlink(file)
136
+ rescue Errno::EINVAL, Errno::ENOSYS
137
+ return File.read(file)
138
+ end
139
+ end
140
+
141
+ ##
142
+ # Checks to see if there is a process running with id +pid+.
143
+ #
144
+ # @param [Fixnum] pid the process ID to look up
145
+ # @return [Boolean] is there a process with the given pid?
146
+ def test_pid(pid)
147
+ return true if Platform::OS == :vms
148
+
149
+ begin
150
+ # Doesn't actually kill it
151
+ Process.kill(0, pid)
152
+ true
153
+ rescue Errno::ESRCH::Errno
154
+ true
155
+ rescue
156
+ false
157
+ end
158
+ end
159
+
160
+
161
+ ##
162
+ # Text from mercurial code:
163
+ #
164
+ # return id of locker if lock is valid, else None.
165
+ #
166
+ # If old-style lock, we cannot tell what machine locker is on.
167
+ # with new-style lock, if locker is on this machine, we can
168
+ # see if locker is alive. If locker is on this machine but
169
+ # not alive, we can safely break lock.
170
+ #
171
+ # The lock file is only deleted when None is returned.
172
+ def test_lock
173
+ locker = read_lock(@file)
174
+ host, pid = locker.split(":", 1)
175
+ return locker if pid.nil? || host != @@host
176
+
177
+ pid = pid.to_i
178
+ return locker if pid == 0
179
+
180
+ return locker if test_pid pid
181
+
182
+ # if locker dead, break lock. must do this with another lock
183
+ # held, or can race and break valid lock.
184
+ begin
185
+ the_lock = Lock.new(@file + ".break")
186
+ the_lock.try_lock
187
+ File.unlink(@file)
188
+ the_lock.release
189
+ rescue LockError
190
+ return locker
191
+ end
192
+ end
193
+
194
+ ##
195
+ # Releases the lock, signalling that it is now safe to modify the directory in which
196
+ # the lock is found.
197
+ def release
198
+ if @held
199
+ @held = false
200
+ @release_fxn.call if @release_fxn
201
+
202
+ File.unlink(@file) rescue ""
203
+ end
204
+ end
205
+
206
+
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,228 @@
1
+ module Amp
2
+ module Merges
3
+ module Mercurial
4
+
5
+ ##
6
+ # = MergeState
7
+ # MergeState handles the merge/ directory in the repository, in order
8
+ # to keep track of how well the current merge is progressing. There is
9
+ # a file called merge/state that lists all the files that need merging
10
+ # and a little info about whether it has beeen merged or not.
11
+ #
12
+ # You can add a file to the mergestate, iterate over all of them, quickly
13
+ # look up to see if a file is still dirty, and so on.
14
+ class MergeState
15
+ include Enumerable
16
+
17
+ ##
18
+ # Initializes a new mergestate with the given repo, and reads in all the
19
+ # information from merge/state.
20
+ #
21
+ # @param repo the repository being inspected
22
+ def initialize(repo)
23
+ @repo = repo
24
+ read!
25
+ end
26
+
27
+ ##
28
+ # Resets the merge status, by clearing all merge information and files
29
+ #
30
+ # @param node the node we're working with? seems kinda useless
31
+ def reset(node = nil)
32
+ @state = {}
33
+ @local = node if node
34
+ FileUtils.rm_rf @repo.join("merge")
35
+ end
36
+ alias_method :reset!, :reset
37
+
38
+ ##
39
+ # Returns whether the file is part of a merge or not
40
+ #
41
+ # @return [Boolean] if the dirty file in our state and not nil?
42
+ def include?(dirty_file)
43
+ not @state[dirty_file].nil?
44
+ end
45
+
46
+ ##
47
+ # Accesses the the given file's merge status - can be "u" for unmerged,
48
+ # or other stuff we haven't figured out yet.
49
+ #
50
+ # @param [String] dirty_file the path to the file for merging.
51
+ # @return [String] the status as a letter - so far "u" means unmerged or "r"
52
+ # for resolved.
53
+ def [](dirty_file)
54
+ @state[dirty_file] ? @state[dirty_file][0, 1] : ""
55
+ end
56
+
57
+ ##
58
+ # Adds a file to the mergestate, which creates a separate file
59
+ # in the merge directory with all the information. I don't know
60
+ # what these parameters are for yet.
61
+ def add(fcl, fco, fca, fd, flags)
62
+ hash = Digest::SHA1.new.update(fcl.path).hexdigest
63
+ @repo.open("merge/#{hash}", "w") do |file|
64
+ file.write fcl.data
65
+ end
66
+ @state[fd] = ["u", hash, fcl.path, fca.path, fca.file_node.hexlify,
67
+ fco.path, flags]
68
+ save
69
+ end
70
+
71
+ ##
72
+ # Returns all uncommitted merge files - everything tracked by the merge state.
73
+ #
74
+ # @todo come up with a better method name
75
+ #
76
+ # @return [Array<Array<String, Symbol>>] an array of String-Symbol pairs - the
77
+ # filename is the first entry, the status of the merge is the second.
78
+ def uncommitted_merge_files
79
+ @state.map {|k, _| [k, status(k)] }
80
+ end
81
+
82
+ ##
83
+ # Iterates over all the files that are involved in the current
84
+ # merging transaction.
85
+ #
86
+ # @yield each file, sorted by filename, that needs merging.
87
+ # @yieldparam file the filename that needs (or has been) merged.
88
+ # @yieldparam state all the information about the current merge with
89
+ # this file.
90
+ def each(&block)
91
+ @state.each(&block)
92
+ end
93
+
94
+ ##
95
+ # Marks the given file with a given state, which is 1 letter. "u" means
96
+ # unmerged, "r" means resolved.
97
+ #
98
+ # @param [String] dirty_file the file path for marking
99
+ # @param [String] state the state - "u" for unmerged, "r" for resolved.
100
+ def mark(dirty_file, state)
101
+ @state[dirty_file][0] = state
102
+ save
103
+ end
104
+
105
+ ##
106
+ # Marks the given file as unresolved. Helper method to hide details of
107
+ # how the mergestate works. Silly leaky abstractions...
108
+ #
109
+ # @param [String] filename the file to mark unresolved
110
+ def mark_conflicted(filename)
111
+ mark(filename, "u")
112
+ end
113
+
114
+ ##
115
+ # Marks the given file as resolved. Helper method to hide details of
116
+ # how the mergestate works. Silly leaky abstractions...
117
+ #
118
+ # @param [String] filename the file to mark unresolved
119
+ def mark_resolved(filename)
120
+ mark(filename, "r")
121
+ end
122
+
123
+ ##
124
+ # Returns the status of a given file, or nil otherwise. Used for making this
125
+ # class more friendly to the outside world. It came to us from mercurial as
126
+ # one leaky fucking abstraction. Every class that used it had to know that "u"
127
+ # returned meant unresolved... ugh.
128
+ #
129
+ # @param [String] filename the file to inspect
130
+ # @return [Symbol] a symbol representing the status of the file, either
131
+ # :untracked, :resolved, or :unresolved
132
+ def status(filename)
133
+ return :untracked unless filename
134
+ case self[filename]
135
+ when "r"
136
+ :resolved
137
+ when "u"
138
+ :unresolved
139
+ end
140
+ end
141
+
142
+ ##
143
+ # Is the given file unresolved?
144
+ #
145
+ # @param [String] filename
146
+ def unresolved?(filename)
147
+ status(filename) == :unresolved
148
+ end
149
+
150
+ ##
151
+ # Is the given file resolved?
152
+ #
153
+ # @param [String] filename
154
+ def resolved?(filename)
155
+ status(filename) == :resolved
156
+ end
157
+
158
+ ##
159
+ # Resolves the given file for a merge between 2 changesets.
160
+ #
161
+ # @param dirty_file the path to the file for merging
162
+ # @param working_changeset the current changeset that is the destination
163
+ # of the merge
164
+ # @param other_changeset the newer changeset, which we're merging to
165
+ def resolve(dirty_file, working_changeset, other_changeset)
166
+ return 0 if resolved?(dirty_file)
167
+ state, hash, lfile, afile, anode, ofile, flags = @state[dirty_file]
168
+ r = true
169
+ @repo.open("merge/#{hash}") do |file|
170
+ @repo.working_write(dirty_file, file.read, flags)
171
+ working_file = working_changeset[dirty_file]
172
+ other_file = other_changeset[ofile]
173
+ ancestor_file = @repo.versioned_file(afile, :file_id => anode)
174
+ r = MergeUI.file_merge(@repo, @local, lfile, working_file, other_file, ancestor_file)
175
+ end
176
+
177
+ mark_resolved(dirty_file) if r.nil? || r == false
178
+ return r
179
+ end
180
+
181
+ ##
182
+ # Public access to writing the file.
183
+ def save
184
+ write!
185
+ end
186
+ alias_method :save!, :save
187
+
188
+ private
189
+
190
+ ##
191
+ # Reads in the merge state and sets up all our instance variables.
192
+ #
193
+ def read!
194
+ @state = {}
195
+ ignore_missing_files do
196
+ local_node = nil
197
+ @repo.open("merge/state") do |file|
198
+ get_node = true
199
+ file.each_line do |line|
200
+ if get_node
201
+ local_node = line.chomp
202
+ get_node = false
203
+ else
204
+ parts = line.chomp.split("\0")
205
+ @state[parts[0]] = parts[1..-1]
206
+ end
207
+ end
208
+ @local = local_node.unhexlify
209
+ end
210
+ end
211
+ end
212
+
213
+ ##
214
+ # Saves the merge state to disk.
215
+ #
216
+ def write!
217
+ @repo.open("merge/state","w") do |file|
218
+ file.write @local.hexlify + "\n"
219
+ @state.each do |key, val|
220
+ file.write "#{([key] + val).join("\0")}\n"
221
+ end
222
+ end
223
+ end
224
+
225
+ end
226
+ end
227
+ end
228
+ end