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.
- 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
|