amp 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. data/test/test_helper.rb +0 -15
@@ -0,0 +1,716 @@
1
+ module Amp
2
+ module Repositories
3
+ module Mercurial
4
+
5
+ ##
6
+ # An entry in the dirstate. Similar to IndexEntry for revlogs. Simple struct, that's
7
+ # all.
8
+ class DirStateEntry < Struct.new(:status, :mode, :size, :mtime)
9
+
10
+ ##
11
+ # shortcuts!
12
+ def removed?; self.status == :removed; end
13
+ def added?; self.status == :added; end
14
+ def untracked?; self.status == :untracked; end
15
+ def modified?; self.status == :modified; end
16
+ def merged?; self.status == :merged; end
17
+ def normal?; self.status == :normal; end
18
+ def forgotten?; self.status == :forgotten; end
19
+
20
+ ##
21
+ # Do I represent a dirty object?
22
+ #
23
+ # @return [Boolean] does this array represent a dirty object in a DirState?
24
+ def dirty?
25
+ self[-2] == -2 && self[-1] == -1 && self.normal?
26
+ end
27
+
28
+ ##
29
+ # Do I possibly represent a dirty object?
30
+ #
31
+ # @return [Boolean] does this array possibly represent a dirty object in a DirState?
32
+ def maybe_dirty?
33
+ self[-2] == -1 && self[-1] == -1 && self.normal?
34
+ end
35
+ end
36
+
37
+ ##
38
+ # = DirState
39
+ # This class handles parsing and manipulating the "dirstate" file, which is stored
40
+ # in the .hg folder. This file handles which files are marked for addition, removal,
41
+ # copies, and so on. The structure of each entry is below.
42
+ #
43
+ #
44
+ # class DirStateEntry < BitStruct
45
+ # default_options :endian => :network
46
+ #
47
+ # char :status , 8, "the state of the file"
48
+ # signed :mode , 32, "mode"
49
+ # signed :size , 32, "size"
50
+ # signed :mtime , 32, "mtime"
51
+ # signed :fname_size , 32, "filename size"
52
+ #
53
+ # end
54
+ class DirState
55
+ include Amp::Mercurial::Ignore
56
+ include Amp::Mercurial::RevlogSupport::Node
57
+
58
+ UNKNOWN = DirStateEntry.new(:untracked, 0, 0, 0)
59
+ FORMAT = "cNNNN"
60
+
61
+ class FileNotInRootError < StandardError; end
62
+ class AbsolutePathNeededError < StandardError; end
63
+
64
+ # The parents of the current state. If there's been an uncommitted merge,
65
+ # it will be two. Otherwise it will just be one parent and +NULL_ID+
66
+ attr_reader :parents
67
+
68
+ # The number of directories in each base ["dir" => #_of_dirs]
69
+ attr_reader :dirs
70
+
71
+ # The files mapped to their stats (state, mode, size, mtime)
72
+ # [state, mode, size, mtime]
73
+ attr_reader :files
74
+
75
+ # A map of files to be copied, because we want to preserve their history
76
+ # "dest" => "source"
77
+ attr_reader :copy_map
78
+
79
+ # I still don't know what this does
80
+ attr_reader :folds
81
+
82
+ # The conglomerate config object of global configs and the repo
83
+ # specific config.
84
+ attr_reader :config
85
+
86
+ # The root of the repository
87
+ attr_reader :root
88
+
89
+ # The opener to access files. The only files that will be touched lie
90
+ # in the .hg/ directory, so the default MUST be +:open_hg+.
91
+ attr_reader :opener
92
+
93
+ ##
94
+ # Creates a DirState object. This is used to represent, in memory (and
95
+ # occasionally on file) how the repository is being changed.
96
+ # It's really simple, and it is really the basis for _using_ the repo
97
+ # (contrary to how Revlog is the basis for _saving_ the repo).
98
+ #
99
+ # @param [String] root the absolute path to the root of the repository
100
+ # @param [Amp::AmpConfig] config the config file of hgrc
101
+ # @param [Amp::Opener] opener the opener to open files with
102
+ def initialize(root, config, opener)
103
+ unless root[0, 1] == "/"
104
+ raise AbsolutePathNeededError, "#{root} is not an absolute path!"
105
+ end
106
+
107
+ # root must be an aboslute path with no ending slash
108
+ @root = root[-1, 1] == "/" ? root[0..-2] : root # the root of the repo
109
+ @config = config # the config file where we get defaults
110
+ @opener = opener # opener to retrieve files (default: open_hg)
111
+ @dirty = false # has something changed, and do we need to write?
112
+ @dirty_parents = false
113
+ @parents = [NULL_ID, NULL_ID] # the parent revisions
114
+ @dirs = {} # number of directories in each base ["dir" => #_of_dirs]
115
+ @files = {} # the files mapped to their statistics
116
+ @copy_map = {} # src => dest
117
+ @ignore = [] # dirs and files to ignore
118
+ @folds = []
119
+ generate_ignore
120
+ end
121
+
122
+ ##
123
+ # Retrieve a file's status from +@files+. If it's not there
124
+ # then return :untracked
125
+ #
126
+ # @param [String] key the path of the file
127
+ # @return [Symbol] status of the file, either :removed, :added, :untracked,
128
+ # :merged, :normal, :forgotten, or :untracked
129
+ def [](key)
130
+ lookup = @files[key]
131
+ lookup || DirStateEntry.new(:untracked)
132
+ end
133
+
134
+ ##
135
+ # Determine if +path+ is a link or an executable.
136
+ #
137
+ # @param [String] path the path to the file
138
+ # @return [String] either 'l' for a link and 'x' for an executable. Returns
139
+ # '' if neither
140
+ def flags(path)
141
+ return 'l' if File.ftype(path) == 'link'
142
+ return 'x' if File.executable? path
143
+ ''
144
+ end
145
+
146
+ ##
147
+ # just a lil' reader to find if the repo is dirty or not
148
+ # by dirty i mean "no longer in sync with the cache"
149
+ #
150
+ # @return [Boolean] is the dirstate no longer in sync with the cache located
151
+ # at .hg/branch.cache
152
+ def dirty?
153
+ @dirty
154
+ end
155
+
156
+ ##
157
+ # The directories and path matches that we're ignoringzorz. It will call
158
+ # the ignorer generated by .hgignore.
159
+ #
160
+ # @param [String] file the path to the file that will be checked by
161
+ # the .hgignore file
162
+ # @return [Boolean] whether we're ignoring the path or not
163
+ def ignore(file)
164
+ @ignore_matches ||= parse_ignore @root, @ignore
165
+ @ignore_matches.call file
166
+ end
167
+
168
+ ##
169
+ # Gets the current branch.
170
+ #
171
+ # @return [String] the current branch in the working directory
172
+ def branch
173
+ text = @opener.read('branch').strip
174
+ @branch ||= text.empty? ? "default" : text
175
+ rescue
176
+ @branch = "default"
177
+ end
178
+
179
+ ##
180
+ # Set the branch to +branch+.
181
+ #
182
+ # @param [#to_s] brnch the branch to switch to
183
+ # @return [String] +brnch+.to_s
184
+ def branch=(brnch)
185
+ @branch = brnch.to_s
186
+
187
+ @opener.open 'branch', 'w' do |f|
188
+ f.puts brnch.to_s
189
+ end
190
+ @branch
191
+ end
192
+
193
+ ##
194
+ # Set the parents to +p+
195
+ #
196
+ # @param [Array<String>] p the parents as binary strings
197
+ # @return [Array<String>] the parents, as will be used by the dirstate
198
+ def parents=(p)
199
+ @parents = if p.is_a? Array
200
+ p.size == 1 ? p + [NULL_ID] : p[0..1]
201
+ else
202
+ [p, NULL_ID]
203
+ end
204
+
205
+ @dirty_parents = true
206
+ @dirty = true
207
+ @parents # return this
208
+ end
209
+ alias_method :parent, :parents
210
+
211
+ ##
212
+ # Set the file as "to be added".
213
+ #
214
+ # @param [String] file the path of the file to add
215
+ # @return [Boolean] a success marker
216
+ def add(*files)
217
+ files.each do |file|
218
+ state = self[file]
219
+ if state.added? || state.modified? || state.normal?
220
+ # fail if it's being tracked
221
+ UI.warn "#{file} already tracked!"
222
+ elsif state.removed?
223
+ # check back on it if it's being removed
224
+ normal_lookup file
225
+ else
226
+ # else add it
227
+ add_path file, true
228
+
229
+ @dirty = true
230
+ @files[file] = DirStateEntry.new(:added, 0, -1, -1)
231
+ @copy_map.delete file
232
+ end
233
+ end
234
+ true # success
235
+ end
236
+
237
+ ##
238
+ # Set the file as "normal", meaning no changes. This is the same
239
+ # as dirstate.normal in dirstate.py, for those referencing both.
240
+ #
241
+ # @param [String] file the path of the file to clean
242
+ # @return [Boolean] a success marker
243
+ def normal(file)
244
+ @dirty = true
245
+ add_path file, true
246
+
247
+ f = File.lstat "#{@root}/#{file}"
248
+ @files[file] = DirStateEntry.new(:normal, f.mode, f.size, f.mtime.to_i)
249
+ @copy_map.delete file
250
+ true # success
251
+ end
252
+ alias_method :clean, :normal
253
+
254
+ ##
255
+ # Set the file as normal, but possibly dirty. It's like when you
256
+ # meet a cool girl, and she seems really innocent and it's a chance
257
+ # for you to maybe change yourself and make a new friend, but then
258
+ # she *might* actually be a total slut. Better milk that grapevine
259
+ # to find out the truth. Oddly specific, huh.
260
+ #
261
+ # THUS IS THE HISTORY OF THIS METHOD!
262
+ #
263
+ # And then one day you go to the movies with some other girl, and the
264
+ # original crazy slutty girl is the cashier next to you. Unsure of
265
+ # what to do, you don't do anything. Next thing you know, she's trying
266
+ # to get your attention to say hey. WTF? Anyone know what's up with this
267
+ # girl?
268
+ #
269
+ # After milking that grapevine, you find out that she's not a great person.
270
+ # There's nothing interesting there and you should just move on.
271
+ #
272
+ # *sigh* girls.
273
+ #
274
+ # @param [String] file the path of the file to mark
275
+ # @return [Boolean] a success marker
276
+ def maybe_dirty(file)
277
+ if @files[file] && @parents.last != NULL_ID
278
+ # if there's a merge happening and the file was either modified
279
+ # or dirty before being removed, restore that state.
280
+ # I'm quoting the python with that one.
281
+ # I guess what it's saying is that if a file is being removed
282
+ # by a merge, but it was altered somehow beforehand on the local
283
+ # repo, then play it safe and bring back the dead. Divine intervention
284
+ # on the side of the local repo.
285
+
286
+ # info here is a standard array of info
287
+ # [action, mode, size, mtime]
288
+ info = @files[file]
289
+
290
+ if info.removed? and [-1, -2].member? info.size
291
+ source = @copy_map[file]
292
+
293
+ # do the appropriate action
294
+ case info.size
295
+ when -1 # either merge it
296
+ merge file
297
+ when -2 # or mark it as dirty
298
+ dirty file
299
+ end
300
+
301
+ copy source => file if source
302
+ return
303
+ end
304
+
305
+ # next step... the base case!
306
+ return true if info.modified? || info.maybe_dirty? and info.size == -2
307
+ end
308
+
309
+ @dirty = true # make the repo dirty
310
+ add_path file # add the file
311
+
312
+ @files[file] = DirStateEntry.new(:normal, 0, -1, -1) # give it info
313
+ @copy_map.delete file # we're not copying it since we're adding it
314
+ true # success
315
+ end
316
+
317
+ ##
318
+ # Checks whether the dirstate is tracking the given file.
319
+ #
320
+ # @param f the file to check for
321
+ # @return [Boolean] whether or not the file is being tracked.
322
+ def include?(path)
323
+ not @files[path].nil?
324
+ end
325
+ alias_method :tracking?, :include?
326
+
327
+ ##
328
+ # Mark the file as "dirty"
329
+ #
330
+ # @param [String] file the path of the file to mark
331
+ # @return [Boolean] a success marker
332
+ def dirty(file)
333
+ @dirty = true
334
+ add_path file
335
+
336
+ @files[file] = DirStateEntry.new(:normal, 0, -2, -1)
337
+ @copy_map.delete file
338
+ true # success
339
+ end
340
+
341
+ ##
342
+ # Set the file as "to be removed"
343
+ #
344
+ # @param [String] file the path of the file to remove
345
+ # @return [Boolean] a success marker
346
+ def remove(file)
347
+ if self[file].added?
348
+ # Is it already added? if so, forgettaboutit
349
+ forget file
350
+ true # success!
351
+ elsif !tracking?(file)
352
+ # Are we not even tracking this file? dumbass
353
+ UI.warn("#{file} not being tracked!")
354
+ false # no success
355
+ else
356
+ # Woooo we can delete it
357
+ @dirty = true
358
+ drop_path file
359
+
360
+ size = 0
361
+ if @parents.last.null? && (info = @files[file])
362
+ if info.merged?
363
+ size = -1
364
+ elsif info.normal? && info.size == -2
365
+ size = -2
366
+ end
367
+ end
368
+ @files[file] = DirStateEntry.new(:removed, 0, size, 0)
369
+ @copy_map.delete file if size.zero?
370
+ true
371
+ end
372
+ end
373
+
374
+ ##
375
+ # Prepare the file to be merged
376
+ #
377
+ # @param [String] file the path of the file to merge
378
+ # @return [Boolean] a success marker
379
+ def merge(file)
380
+ @dirty = true
381
+ add_path file
382
+
383
+ stats = File.lstat "#{@root}/#{file}"
384
+ add_path file
385
+ @files[file] = DirStateEntry.new(:merged, stats.mode, stats.size, stats.mtime.to_i)
386
+ @copy_map.delete file
387
+ true # success
388
+ end
389
+
390
+ ##
391
+ # Forget the file
392
+ #
393
+ # @param [String] file the path of the file to forget
394
+ # @return [Boolean] a success marker
395
+ def forget(file)
396
+ @dirty = true
397
+ drop_path file
398
+ @files.delete file
399
+ true # success
400
+ end
401
+
402
+ ##
403
+ # Returns a list of all the files tracked by the dirstate.
404
+ #
405
+ # @return [Array<String>] all the files being tracked, unsorted
406
+ def all_files
407
+ @files.keys
408
+ end
409
+
410
+ ##
411
+ # Invalidates the dirstate, making it completely unusable until it is
412
+ # re-read. Should only be used in error situations.
413
+ def invalidate!
414
+ %w(@files @copy_map @folds @branch @parents @dirs @ignore).each do |ivar|
415
+ instance_variable_set(ivar, nil)
416
+ end
417
+ @dirty = false
418
+ end
419
+
420
+ ##
421
+ # Refresh the directory's state, making everything empty.
422
+ # Called by #rebuild.
423
+ #
424
+ # This is not the same as #initialize, so we can't just run
425
+ # `send :initialize` and call it a day :-(
426
+ #
427
+ # @return [Boolean] a success marker
428
+ def clear
429
+ @files = {}
430
+ @dirs = {}
431
+ @copy_map = {}
432
+ @parents = [NULL_ID, NULL_ID]
433
+ @dirty = true
434
+
435
+ true # success
436
+ end
437
+
438
+ ##
439
+ # Rebuild the directory's state.
440
+ #
441
+ # @param [String] parent the binary format of the parent
442
+ # @param [ManifestEntry] files the files in a specific revision
443
+ # @return [Boolean] a success marker
444
+ def rebuild(parent, files)
445
+ clear
446
+
447
+ # alter each file according to its flags
448
+ files.each do |f|
449
+ mode = files.flags(f).include?('x') ? 0777 : 0666
450
+ @files[f] = DirStateEntry.new(:normal, mode, -1, 0)
451
+ end
452
+
453
+ @parents = [parent, NULL_ID]
454
+ @dirty_parents = true
455
+ true # success
456
+ end
457
+
458
+ ##
459
+ # Save the data to .hg/dirstate.
460
+ # Uses mode: "w", so it overwrites everything
461
+ #
462
+ # @todo watch memory usage - +si+ could grow unrestrictedly which would
463
+ # bog down the entire program
464
+ # @return [Boolean] a success marker
465
+ def write
466
+ return true unless @dirty
467
+ begin
468
+ @opener.open "dirstate", 'w' do |state|
469
+ gran = @config['dirstate']['granularity'] || 1 # self._ui.config('dirstate', 'granularity', 1)
470
+
471
+ limit = 2147483647 # sorry for the literal use...
472
+ limit = state.mtime - gran if gran > 0
473
+
474
+ si = StringIO.new "", (ruby_19? ? "w+:ASCII-8BIT" : "w+")
475
+ si.write @parents.join
476
+
477
+ @files.each do |file, info|
478
+ file = file.dup # so we don't corrupt vars
479
+ info = info.dup.to_a # UNLIKE PYTHON
480
+ info[0] = info[0].to_hg_int
481
+
482
+ # I should probably do mah physics hw. nah, i'll do it
483
+ # tomorrow during my break
484
+ # good news - i did pretty well on my physics test by using
485
+ # brian ford's name instead of my own.
486
+ file = "#{file}\0#{@copy_map[file]}" if @copy_map[file]
487
+ info = [info[0], 0, (-1).to_signed_32, (-1).to_signed_32] if info[3].to_i > limit.to_i and info[0] == :normal
488
+ info << file.size # the final element to make it pass, which is the length of the filename
489
+ info = info.pack FORMAT # pack them their lunch
490
+ si.write info # and send them off
491
+ si.write file # to school
492
+ end
493
+
494
+ state.write si.string
495
+ @dirty = false
496
+ @dirty_parents = false
497
+
498
+ true # success
499
+ end
500
+ rescue IOError
501
+ false
502
+ end
503
+ end
504
+
505
+ ##
506
+ # Copies the files in h (represented as "dest" => "source").
507
+ #
508
+ # @param [Hash<String => String>] h the keys are sources and the values
509
+ # are dests
510
+ # @return [Boolean] a success marker
511
+ def copy(h={})
512
+ h.each do |source, dest|
513
+ next if source == dest
514
+ return true unless source
515
+
516
+ @dirty = true
517
+
518
+ if @copy_map[dest]
519
+ then @copy_map.delete dest
520
+ else @copy_map[dest] = source
521
+ end
522
+ end
523
+
524
+ true # success
525
+ end
526
+
527
+ ##
528
+ # Reads the data in the .hg folder and fills in the vars
529
+ #
530
+ # @return [Amp::DirState] self -- chainable!
531
+ def read!
532
+ @parents, @files, @copy_map = parse('dirstate')
533
+ self # chainable
534
+ end
535
+
536
+ ##
537
+ # Are we ignoring the directory?
538
+ #
539
+ # @param [String] dir the directory we're checking, either aboslute or relative
540
+ # @return [Boolean] are we ignoring the dir?
541
+ def ignoring_directory?(dir)
542
+ return false if dir == '.' # base cases
543
+ return true if ignore dir # base cases
544
+ !!directories_to(dir).any? {|d| ignore d }
545
+ end
546
+ alias_method :ignoring_dir?, :ignoring_directory?
547
+
548
+ private
549
+ ##
550
+ # Generates the @ignore array
551
+ # The array is full of paths relative to the root, which
552
+ # makes things easier for the proc-generation phase.
553
+ #
554
+ # @return [NilClass]
555
+ def generate_ignore
556
+ @ignore = @config['ui'].select {|k, v| k == "ignore" }
557
+
558
+ @ignore << ".hgignore"
559
+ @ignore.compact
560
+
561
+ nil
562
+ end
563
+
564
+ ##
565
+ # Perform various checks on the file before upping the content count
566
+ # for all of its parent directories. It checks for:
567
+ # * filenames containing "\n" or "\r" (newlines and carriage returns)
568
+ # * filenames with the same names as directories
569
+ # * clashing filenames
570
+ #
571
+ # It only increments the dirs' file count if the file is untracked or
572
+ # being removed.
573
+ #
574
+ # @param [String] f Should be formatted like ["action", mode, size, mtime]
575
+ # @param [Boolean] check whether to perform any of the checks
576
+ # @return [NilClass]
577
+ def add_path(f, check=false)
578
+ old_state = @files[f] || DirStateEntry.new # it's an array of info, remember
579
+
580
+ if check || old_state.removed?
581
+ raise "Bad Filename" if f.match(/\r|\n/)
582
+ raise "Directory #{f} already exists" if @dirs[f]
583
+
584
+ # make sure we don't have any files with the same name as a directory
585
+ directories_to(f).each do |d|
586
+ break if @dirs[d]
587
+
588
+ if @files[d] && !@files[d].removed?
589
+ raise "File #{d} clashes with #{f}! Fix their names"
590
+ end
591
+ end
592
+ end
593
+
594
+ # only inc the dirs if the file is untracked or being removed.
595
+ if [:untracked, :removed].include? old_state.status
596
+ # inc the number of dirs in each dir
597
+ inc_directories_to f
598
+ end
599
+
600
+ nil
601
+ end
602
+
603
+ ##
604
+ # Conditional wrapper around +dec_directories_to+. It will dec the
605
+ # directories if the file in question (+f+) is either untracked or
606
+ # being removed.
607
+ #
608
+ # @param [String] f Should be formatted like ["action", mode, size, mtime]
609
+ # @return [NilClass]
610
+ def drop_path(f)
611
+ unless [:untracked, :removed].include? f[0]
612
+ dec_directories_to(f)
613
+ end
614
+
615
+ nil
616
+ end
617
+
618
+ ##
619
+ # All directories leading up to this path
620
+ #
621
+ # @example directories_to "/Users/ari/src/monkey.txt" # =>
622
+ # ["/Users/ari/src", "/Users/ari", "/Users"]
623
+ # @param [String] path the path to the file we're examining
624
+ # @return [Array] the directories leading up to this path
625
+ def directories_to(path)
626
+ File.amp_directories_to path
627
+ end
628
+
629
+ ##
630
+ # Increment all directories' dir-count leading up to this path.
631
+ # The dir-count is the path's value in @dirs.
632
+ # This is used when adding a file.
633
+ #
634
+ # @param [String] path the path we're disecting
635
+ # @return [NilClass]
636
+ def inc_directories_to(path)
637
+ p = directories_to(path).first
638
+ @dirs[p] ||= 0
639
+ @dirs[p] += 1
640
+ nil
641
+ end
642
+
643
+ ##
644
+ # Decrement all directories' dir-count leading up to this path.
645
+ # The dir-count is the path's value in @dirs.
646
+ # This is used when removing a file.
647
+ #
648
+ # @param [String] path the path we're disecting
649
+ # @return [NilClass]
650
+ def dec_directories_to(path)
651
+ p = directories_to(path).first
652
+ # if the dir has 0, kill the dir. we don't need it anymore
653
+ if @dirs[p] && @dirs[p].zero?
654
+ @dirs.delete p
655
+ elsif @dirs[p]
656
+ @dirs[p] -= 1 # we only need to inc the latest dir
657
+ end
658
+
659
+ nil
660
+ end
661
+
662
+ ##
663
+ # Parses the dirstate file in .hg
664
+ #
665
+ # @param [String] file path to the file to parse
666
+ # @return [((String, String), Hash<String => (Integer, Integer, Integer)>, Hash<String => String>)]
667
+ # a tuple of (parents, files, copies). Parents is a tuple of the parents,
668
+ # files is a hash of filename => [mode, size, mtime], and copies is a hash of src => dest
669
+ def parse(file)
670
+ # the main data we need to return
671
+ files = {}
672
+ copies = {}
673
+ parents = []
674
+ @opener.open file, "r" do |s|
675
+
676
+ # the parents are the first 40 bytes
677
+ parent = s.read(20) || NULL_ID
678
+ parent_ = s.read(20) || NULL_ID
679
+ parents = [parent, parent_]
680
+
681
+ # 1 character + 4 32-bit ints = 17 bytes
682
+ e_size = 17
683
+
684
+ # this loop is just cycling through and reading every entry
685
+ while !s.eof?
686
+ # read 1 entry
687
+ info = s.read(e_size).unpack FORMAT
688
+
689
+ # byte swap and shizzle
690
+ info = [info[0].to_dirstate_symbol, info[1], info[2].to_signed_32, info[3].to_signed_32, info[4]]
691
+ # ^^^^ we have to sign them because otherwise they'll be hugely wrong
692
+
693
+ # read in the filename
694
+ f = s.read(info[4])
695
+
696
+ # if it has an \0, then we've moved/copied it
697
+ if f.match(/\0/)
698
+ source, dest = f.split "\0"
699
+ copies[source] = dest
700
+ f = source
701
+ end
702
+
703
+ # and put in the info for the file itself
704
+ files[f] = DirStateEntry.new(*info[0..3])
705
+ end
706
+ end
707
+
708
+ [parents, files, copies]
709
+ rescue Errno::ENOENT
710
+ # no file? easy peasy
711
+ [[NULL_ID, NULL_ID], {}, {}]
712
+ end
713
+ end
714
+ end
715
+ end
716
+ end