amp 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/.hgignore +3 -0
- data/AUTHORS +1 -1
- data/Manifest.txt +99 -38
- data/README.md +3 -3
- data/Rakefile +53 -18
- data/SCHEDULE.markdown +5 -1
- data/TODO.markdown +120 -149
- data/ampfile.rb +3 -1
- data/bin/amp +4 -1
- data/ext/amp/bz2/extconf.rb +1 -1
- data/ext/amp/mercurial_patch/extconf.rb +1 -1
- data/ext/amp/mercurial_patch/mpatch.c +4 -3
- data/ext/amp/priority_queue/extconf.rb +1 -1
- data/ext/amp/support/extconf.rb +1 -1
- data/ext/amp/support/support.c +1 -1
- data/lib/amp.rb +125 -67
- data/lib/amp/commands/command.rb +12 -10
- data/lib/amp/commands/command_support.rb +8 -1
- data/lib/amp/commands/commands/help.rb +2 -20
- data/lib/amp/commands/commands/init.rb +14 -2
- data/lib/amp/commands/commands/templates.rb +6 -4
- data/lib/amp/commands/commands/version.rb +15 -1
- data/lib/amp/commands/commands/workflow.rb +3 -3
- data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
- data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
- data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
- data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
- data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
- data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
- data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
- data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
- data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
- data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
- data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
- data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
- data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
- data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
- data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
- data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
- data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
- data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
- data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
- data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
- data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
- data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
- data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
- data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
- data/lib/amp/commands/dispatch.rb +12 -13
- data/lib/amp/dependencies/amp_support.rb +1 -1
- data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
- data/lib/amp/dependencies/maruku.rb +136 -0
- data/lib/amp/dependencies/maruku/attributes.rb +227 -0
- data/lib/amp/dependencies/maruku/defaults.rb +71 -0
- data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
- data/lib/amp/dependencies/maruku/helpers.rb +260 -0
- data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
- data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
- data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
- data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
- data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
- data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
- data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
- data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
- data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
- data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
- data/lib/amp/dependencies/maruku/maruku.rb +33 -0
- data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
- data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
- data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
- data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
- data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
- data/lib/amp/dependencies/maruku/structures.rb +167 -0
- data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
- data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
- data/lib/amp/dependencies/maruku/textile2.rb +1 -0
- data/lib/amp/dependencies/maruku/toc.rb +199 -0
- data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
- data/lib/amp/dependencies/maruku/version.rb +40 -0
- data/lib/amp/dependencies/priority_queue.rb +2 -1
- data/lib/amp/dependencies/python_config.rb +2 -1
- data/lib/amp/graphs/ancestor.rb +2 -1
- data/lib/amp/graphs/copies.rb +236 -233
- data/lib/amp/help/entries/__default__.erb +31 -0
- data/lib/amp/help/entries/commands.erb +6 -0
- data/lib/amp/help/entries/mdtest.md +35 -0
- data/lib/amp/help/entries/silly +3 -0
- data/lib/amp/help/help.rb +288 -0
- data/lib/amp/profiling_hacks.rb +5 -3
- data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
- data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
- data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
- data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
- data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
- data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
- data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
- data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
- data/lib/amp/repository/generic_repo_picker.rb +78 -0
- data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
- data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
- data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
- data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
- data/lib/amp/repository/git/repository.rb +41 -0
- data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
- data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
- data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
- data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
- data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
- data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
- data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
- data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
- data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
- data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
- data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
- data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
- data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
- data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
- data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
- data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
- data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
- data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
- data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
- data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
- data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
- data/lib/amp/repository/mercurial/repository.rb +119 -0
- data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
- data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
- data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
- data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
- data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
- data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
- data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
- data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
- data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
- data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
- data/lib/amp/repository/repository.rb +11 -88
- data/lib/amp/server/extension/amp_extension.rb +3 -3
- data/lib/amp/server/fancy_http_server.rb +1 -1
- data/lib/amp/server/fancy_views/_browser.haml +1 -1
- data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
- data/lib/amp/server/fancy_views/changeset.haml +2 -2
- data/lib/amp/server/fancy_views/file.haml +1 -1
- data/lib/amp/server/fancy_views/file_diff.haml +1 -1
- data/lib/amp/support/amp_ui.rb +13 -29
- data/lib/amp/support/generator.rb +1 -1
- data/lib/amp/support/loaders.rb +1 -2
- data/lib/amp/support/logger.rb +10 -16
- data/lib/amp/support/match.rb +18 -4
- data/lib/amp/support/mercurial/ignore.rb +151 -0
- data/lib/amp/support/openers.rb +8 -3
- data/lib/amp/support/support.rb +91 -46
- data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
- data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
- data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
- data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
- data/lib/amp/templates/template.rb +18 -18
- data/man/amp.1 +51 -0
- data/site/src/about/commands.haml +1 -1
- data/site/src/css/amp.css +1 -1
- data/site/src/index.haml +3 -3
- data/tasks/man.rake +39 -0
- data/tasks/stats.rake +1 -10
- data/tasks/yard.rake +1 -50
- data/test/dirstate_tests/test_dir_state.rb +10 -8
- data/test/functional_tests/annotate.out +31 -0
- data/test/functional_tests/test_functional.rb +155 -63
- data/test/localrepo_tests/ampfile.rb +12 -0
- data/test/localrepo_tests/test_local_repo.rb +56 -57
- data/test/manifest_tests/test_manifest.rb +3 -5
- data/test/merge_tests/test_merge.rb +3 -3
- data/test/revlog_tests/test_revlog.rb +14 -6
- data/test/store_tests/test_fncache_store.rb +19 -19
- data/test/test_19_compatibility.rb +46 -0
- data/test/test_base85.rb +2 -2
- data/test/test_bdiff.rb +2 -2
- data/test/test_changegroup.rb +59 -0
- data/test/test_commands.rb +2 -2
- data/test/test_difflib.rb +2 -2
- data/test/test_generator.rb +34 -0
- data/test/test_ignore.rb +203 -0
- data/test/test_journal.rb +18 -13
- data/test/test_match.rb +2 -2
- data/test/test_mdiff.rb +3 -3
- data/test/test_mpatch.rb +3 -3
- data/test/test_multi_io.rb +40 -0
- data/test/test_support.rb +18 -2
- data/test/test_templates.rb +38 -0
- data/test/test_ui.rb +79 -0
- data/test/testutilities.rb +56 -0
- metadata +168 -49
- data/ext/amp/bz2/mkmf.log +0 -38
- data/lib/amp/encoding/mercurial_diff.rb +0 -378
- data/lib/amp/encoding/mercurial_patch.rb +0 -1
- data/lib/amp/encoding/patch.rb +0 -292
- data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
- data/lib/amp/merges/merge_state.rb +0 -164
- data/lib/amp/merges/merge_ui.rb +0 -322
- data/lib/amp/merges/simple_merge.rb +0 -450
- data/lib/amp/repository/branch_manager.rb +0 -234
- data/lib/amp/repository/dir_state.rb +0 -950
- data/lib/amp/repository/journal.rb +0 -203
- data/lib/amp/repository/lock.rb +0 -207
- data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
- data/lib/amp/repository/repositories/http_repository.rb +0 -377
- data/lib/amp/repository/repositories/local_repository.rb +0 -2661
- data/lib/amp/repository/store.rb +0 -485
- data/lib/amp/repository/tag_manager.rb +0 -319
- data/lib/amp/repository/updatable.rb +0 -532
- data/lib/amp/repository/verification.rb +0 -431
- data/lib/amp/repository/versioned_file.rb +0 -475
- data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
- data/lib/amp/revlogs/changegroup.rb +0 -217
- data/lib/amp/revlogs/changelog.rb +0 -338
- data/lib/amp/revlogs/changeset.rb +0 -521
- data/lib/amp/revlogs/file_log.rb +0 -165
- data/lib/amp/revlogs/index.rb +0 -493
- data/lib/amp/revlogs/manifest.rb +0 -195
- data/lib/amp/revlogs/node.rb +0 -18
- data/lib/amp/revlogs/revlog.rb +0 -1045
- data/lib/amp/revlogs/revlog_support.rb +0 -126
- data/lib/amp/support/ignore.rb +0 -144
- data/site/Rakefile +0 -38
- data/test/test_amp.rb +0 -9
- 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
|