amp 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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