amp 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (232) hide show
  1. data/.gitignore +12 -0
  2. data/.hgignore +3 -0
  3. data/AUTHORS +1 -1
  4. data/Manifest.txt +99 -38
  5. data/README.md +3 -3
  6. data/Rakefile +53 -18
  7. data/SCHEDULE.markdown +5 -1
  8. data/TODO.markdown +120 -149
  9. data/ampfile.rb +3 -1
  10. data/bin/amp +4 -1
  11. data/ext/amp/bz2/extconf.rb +1 -1
  12. data/ext/amp/mercurial_patch/extconf.rb +1 -1
  13. data/ext/amp/mercurial_patch/mpatch.c +4 -3
  14. data/ext/amp/priority_queue/extconf.rb +1 -1
  15. data/ext/amp/support/extconf.rb +1 -1
  16. data/ext/amp/support/support.c +1 -1
  17. data/lib/amp.rb +125 -67
  18. data/lib/amp/commands/command.rb +12 -10
  19. data/lib/amp/commands/command_support.rb +8 -1
  20. data/lib/amp/commands/commands/help.rb +2 -20
  21. data/lib/amp/commands/commands/init.rb +14 -2
  22. data/lib/amp/commands/commands/templates.rb +6 -4
  23. data/lib/amp/commands/commands/version.rb +15 -1
  24. data/lib/amp/commands/commands/workflow.rb +3 -3
  25. data/lib/amp/commands/commands/workflows/git/add.rb +3 -3
  26. data/lib/amp/commands/commands/workflows/git/copy.rb +1 -1
  27. data/lib/amp/commands/commands/workflows/git/rm.rb +4 -2
  28. data/lib/amp/commands/commands/workflows/hg/add.rb +1 -1
  29. data/lib/amp/commands/commands/workflows/hg/addremove.rb +2 -2
  30. data/lib/amp/commands/commands/workflows/hg/annotate.rb +8 -2
  31. data/lib/amp/commands/commands/workflows/hg/bisect.rb +253 -0
  32. data/lib/amp/commands/commands/workflows/hg/branch.rb +1 -1
  33. data/lib/amp/commands/commands/workflows/hg/branches.rb +3 -3
  34. data/lib/amp/commands/commands/workflows/hg/bundle.rb +3 -3
  35. data/lib/amp/commands/commands/workflows/hg/clone.rb +4 -5
  36. data/lib/amp/commands/commands/workflows/hg/commit.rb +37 -1
  37. data/lib/amp/commands/commands/workflows/hg/copy.rb +2 -1
  38. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +1 -1
  39. data/lib/amp/commands/commands/workflows/hg/diff.rb +3 -8
  40. data/lib/amp/commands/commands/workflows/hg/forget.rb +5 -4
  41. data/lib/amp/commands/commands/workflows/hg/identify.rb +6 -6
  42. data/lib/amp/commands/commands/workflows/hg/import.rb +1 -1
  43. data/lib/amp/commands/commands/workflows/hg/incoming.rb +2 -2
  44. data/lib/amp/commands/commands/workflows/hg/log.rb +5 -4
  45. data/lib/amp/commands/commands/workflows/hg/merge.rb +1 -1
  46. data/lib/amp/commands/commands/workflows/hg/move.rb +5 -3
  47. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +1 -1
  48. data/lib/amp/commands/commands/workflows/hg/push.rb +6 -7
  49. data/lib/amp/commands/commands/workflows/hg/remove.rb +2 -2
  50. data/lib/amp/commands/commands/workflows/hg/resolve.rb +6 -23
  51. data/lib/amp/commands/commands/workflows/hg/root.rb +1 -2
  52. data/lib/amp/commands/commands/workflows/hg/status.rb +21 -12
  53. data/lib/amp/commands/commands/workflows/hg/tag.rb +2 -2
  54. data/lib/amp/commands/commands/workflows/hg/untrack.rb +12 -0
  55. data/lib/amp/commands/commands/workflows/hg/verify.rb +13 -3
  56. data/lib/amp/commands/commands/workflows/hg/what_changed.rb +18 -0
  57. data/lib/amp/commands/dispatch.rb +12 -13
  58. data/lib/amp/dependencies/amp_support.rb +1 -1
  59. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +1 -0
  60. data/lib/amp/dependencies/maruku.rb +136 -0
  61. data/lib/amp/dependencies/maruku/attributes.rb +227 -0
  62. data/lib/amp/dependencies/maruku/defaults.rb +71 -0
  63. data/lib/amp/dependencies/maruku/errors_management.rb +92 -0
  64. data/lib/amp/dependencies/maruku/helpers.rb +260 -0
  65. data/lib/amp/dependencies/maruku/input/charsource.rb +326 -0
  66. data/lib/amp/dependencies/maruku/input/extensions.rb +69 -0
  67. data/lib/amp/dependencies/maruku/input/html_helper.rb +189 -0
  68. data/lib/amp/dependencies/maruku/input/linesource.rb +111 -0
  69. data/lib/amp/dependencies/maruku/input/parse_block.rb +615 -0
  70. data/lib/amp/dependencies/maruku/input/parse_doc.rb +234 -0
  71. data/lib/amp/dependencies/maruku/input/parse_span_better.rb +746 -0
  72. data/lib/amp/dependencies/maruku/input/rubypants.rb +225 -0
  73. data/lib/amp/dependencies/maruku/input/type_detection.rb +147 -0
  74. data/lib/amp/dependencies/maruku/input_textile2/t2_parser.rb +163 -0
  75. data/lib/amp/dependencies/maruku/maruku.rb +33 -0
  76. data/lib/amp/dependencies/maruku/output/to_ansi.rb +223 -0
  77. data/lib/amp/dependencies/maruku/output/to_html.rb +991 -0
  78. data/lib/amp/dependencies/maruku/output/to_markdown.rb +164 -0
  79. data/lib/amp/dependencies/maruku/output/to_s.rb +56 -0
  80. data/lib/amp/dependencies/maruku/string_utils.rb +191 -0
  81. data/lib/amp/dependencies/maruku/structures.rb +167 -0
  82. data/lib/amp/dependencies/maruku/structures_inspect.rb +87 -0
  83. data/lib/amp/dependencies/maruku/structures_iterators.rb +61 -0
  84. data/lib/amp/dependencies/maruku/textile2.rb +1 -0
  85. data/lib/amp/dependencies/maruku/toc.rb +199 -0
  86. data/lib/amp/dependencies/maruku/usage/example1.rb +33 -0
  87. data/lib/amp/dependencies/maruku/version.rb +40 -0
  88. data/lib/amp/dependencies/priority_queue.rb +2 -1
  89. data/lib/amp/dependencies/python_config.rb +2 -1
  90. data/lib/amp/graphs/ancestor.rb +2 -1
  91. data/lib/amp/graphs/copies.rb +236 -233
  92. data/lib/amp/help/entries/__default__.erb +31 -0
  93. data/lib/amp/help/entries/commands.erb +6 -0
  94. data/lib/amp/help/entries/mdtest.md +35 -0
  95. data/lib/amp/help/entries/silly +3 -0
  96. data/lib/amp/help/help.rb +288 -0
  97. data/lib/amp/profiling_hacks.rb +5 -3
  98. data/lib/amp/repository/abstract/abstract_changeset.rb +97 -0
  99. data/lib/amp/repository/abstract/abstract_local_repo.rb +181 -0
  100. data/lib/amp/repository/abstract/abstract_staging_area.rb +180 -0
  101. data/lib/amp/repository/abstract/abstract_versioned_file.rb +100 -0
  102. data/lib/amp/repository/abstract/common_methods/changeset.rb +75 -0
  103. data/lib/amp/repository/abstract/common_methods/local_repo.rb +277 -0
  104. data/lib/amp/repository/abstract/common_methods/staging_area.rb +233 -0
  105. data/lib/amp/repository/abstract/common_methods/versioned_file.rb +71 -0
  106. data/lib/amp/repository/generic_repo_picker.rb +78 -0
  107. data/lib/amp/repository/git/repo_format/changeset.rb +336 -0
  108. data/lib/amp/repository/git/repo_format/staging_area.rb +192 -0
  109. data/lib/amp/repository/git/repo_format/versioned_file.rb +119 -0
  110. data/lib/amp/repository/git/repositories/local_repository.rb +164 -0
  111. data/lib/amp/repository/git/repository.rb +41 -0
  112. data/lib/amp/repository/mercurial/encoding/mercurial_diff.rb +382 -0
  113. data/lib/amp/repository/mercurial/encoding/mercurial_patch.rb +1 -0
  114. data/lib/amp/repository/mercurial/encoding/patch.rb +294 -0
  115. data/lib/amp/repository/mercurial/encoding/pure_ruby/ruby_mercurial_patch.rb +124 -0
  116. data/lib/amp/repository/mercurial/merging/merge_ui.rb +327 -0
  117. data/lib/amp/repository/mercurial/merging/simple_merge.rb +452 -0
  118. data/lib/amp/repository/mercurial/repo_format/branch_manager.rb +266 -0
  119. data/lib/amp/repository/mercurial/repo_format/changeset.rb +768 -0
  120. data/lib/amp/repository/mercurial/repo_format/dir_state.rb +716 -0
  121. data/lib/amp/repository/mercurial/repo_format/journal.rb +218 -0
  122. data/lib/amp/repository/mercurial/repo_format/lock.rb +210 -0
  123. data/lib/amp/repository/mercurial/repo_format/merge_state.rb +228 -0
  124. data/lib/amp/repository/mercurial/repo_format/staging_area.rb +367 -0
  125. data/lib/amp/repository/mercurial/repo_format/store.rb +487 -0
  126. data/lib/amp/repository/mercurial/repo_format/tag_manager.rb +322 -0
  127. data/lib/amp/repository/mercurial/repo_format/updatable.rb +543 -0
  128. data/lib/amp/repository/mercurial/repo_format/updater.rb +848 -0
  129. data/lib/amp/repository/mercurial/repo_format/verification.rb +433 -0
  130. data/lib/amp/repository/mercurial/repositories/bundle_repository.rb +216 -0
  131. data/lib/amp/repository/mercurial/repositories/http_repository.rb +386 -0
  132. data/lib/amp/repository/mercurial/repositories/local_repository.rb +2034 -0
  133. data/lib/amp/repository/mercurial/repository.rb +119 -0
  134. data/lib/amp/repository/mercurial/revlogs/bundle_revlogs.rb +249 -0
  135. data/lib/amp/repository/mercurial/revlogs/changegroup.rb +217 -0
  136. data/lib/amp/repository/mercurial/revlogs/changelog.rb +339 -0
  137. data/lib/amp/repository/mercurial/revlogs/file_log.rb +152 -0
  138. data/lib/amp/repository/mercurial/revlogs/index.rb +500 -0
  139. data/lib/amp/repository/mercurial/revlogs/manifest.rb +201 -0
  140. data/lib/amp/repository/mercurial/revlogs/node.rb +20 -0
  141. data/lib/amp/repository/mercurial/revlogs/revlog.rb +1026 -0
  142. data/lib/amp/repository/mercurial/revlogs/revlog_support.rb +129 -0
  143. data/lib/amp/repository/mercurial/revlogs/versioned_file.rb +597 -0
  144. data/lib/amp/repository/repository.rb +11 -88
  145. data/lib/amp/server/extension/amp_extension.rb +3 -3
  146. data/lib/amp/server/fancy_http_server.rb +1 -1
  147. data/lib/amp/server/fancy_views/_browser.haml +1 -1
  148. data/lib/amp/server/fancy_views/_diff_file.haml +1 -8
  149. data/lib/amp/server/fancy_views/changeset.haml +2 -2
  150. data/lib/amp/server/fancy_views/file.haml +1 -1
  151. data/lib/amp/server/fancy_views/file_diff.haml +1 -1
  152. data/lib/amp/support/amp_ui.rb +13 -29
  153. data/lib/amp/support/generator.rb +1 -1
  154. data/lib/amp/support/loaders.rb +1 -2
  155. data/lib/amp/support/logger.rb +10 -16
  156. data/lib/amp/support/match.rb +18 -4
  157. data/lib/amp/support/mercurial/ignore.rb +151 -0
  158. data/lib/amp/support/openers.rb +8 -3
  159. data/lib/amp/support/support.rb +91 -46
  160. data/lib/amp/templates/{blank.commit.erb → mercurial/blank.commit.erb} +0 -0
  161. data/lib/amp/templates/{blank.log.erb → mercurial/blank.log.erb} +0 -0
  162. data/lib/amp/templates/{default.commit.erb → mercurial/default.commit.erb} +0 -0
  163. data/lib/amp/templates/{default.log.erb → mercurial/default.log.erb} +0 -0
  164. data/lib/amp/templates/template.rb +18 -18
  165. data/man/amp.1 +51 -0
  166. data/site/src/about/commands.haml +1 -1
  167. data/site/src/css/amp.css +1 -1
  168. data/site/src/index.haml +3 -3
  169. data/tasks/man.rake +39 -0
  170. data/tasks/stats.rake +1 -10
  171. data/tasks/yard.rake +1 -50
  172. data/test/dirstate_tests/test_dir_state.rb +10 -8
  173. data/test/functional_tests/annotate.out +31 -0
  174. data/test/functional_tests/test_functional.rb +155 -63
  175. data/test/localrepo_tests/ampfile.rb +12 -0
  176. data/test/localrepo_tests/test_local_repo.rb +56 -57
  177. data/test/manifest_tests/test_manifest.rb +3 -5
  178. data/test/merge_tests/test_merge.rb +3 -3
  179. data/test/revlog_tests/test_revlog.rb +14 -6
  180. data/test/store_tests/test_fncache_store.rb +19 -19
  181. data/test/test_19_compatibility.rb +46 -0
  182. data/test/test_base85.rb +2 -2
  183. data/test/test_bdiff.rb +2 -2
  184. data/test/test_changegroup.rb +59 -0
  185. data/test/test_commands.rb +2 -2
  186. data/test/test_difflib.rb +2 -2
  187. data/test/test_generator.rb +34 -0
  188. data/test/test_ignore.rb +203 -0
  189. data/test/test_journal.rb +18 -13
  190. data/test/test_match.rb +2 -2
  191. data/test/test_mdiff.rb +3 -3
  192. data/test/test_mpatch.rb +3 -3
  193. data/test/test_multi_io.rb +40 -0
  194. data/test/test_support.rb +18 -2
  195. data/test/test_templates.rb +38 -0
  196. data/test/test_ui.rb +79 -0
  197. data/test/testutilities.rb +56 -0
  198. metadata +168 -49
  199. data/ext/amp/bz2/mkmf.log +0 -38
  200. data/lib/amp/encoding/mercurial_diff.rb +0 -378
  201. data/lib/amp/encoding/mercurial_patch.rb +0 -1
  202. data/lib/amp/encoding/patch.rb +0 -292
  203. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +0 -123
  204. data/lib/amp/merges/merge_state.rb +0 -164
  205. data/lib/amp/merges/merge_ui.rb +0 -322
  206. data/lib/amp/merges/simple_merge.rb +0 -450
  207. data/lib/amp/repository/branch_manager.rb +0 -234
  208. data/lib/amp/repository/dir_state.rb +0 -950
  209. data/lib/amp/repository/journal.rb +0 -203
  210. data/lib/amp/repository/lock.rb +0 -207
  211. data/lib/amp/repository/repositories/bundle_repository.rb +0 -214
  212. data/lib/amp/repository/repositories/http_repository.rb +0 -377
  213. data/lib/amp/repository/repositories/local_repository.rb +0 -2661
  214. data/lib/amp/repository/store.rb +0 -485
  215. data/lib/amp/repository/tag_manager.rb +0 -319
  216. data/lib/amp/repository/updatable.rb +0 -532
  217. data/lib/amp/repository/verification.rb +0 -431
  218. data/lib/amp/repository/versioned_file.rb +0 -475
  219. data/lib/amp/revlogs/bundle_revlogs.rb +0 -246
  220. data/lib/amp/revlogs/changegroup.rb +0 -217
  221. data/lib/amp/revlogs/changelog.rb +0 -338
  222. data/lib/amp/revlogs/changeset.rb +0 -521
  223. data/lib/amp/revlogs/file_log.rb +0 -165
  224. data/lib/amp/revlogs/index.rb +0 -493
  225. data/lib/amp/revlogs/manifest.rb +0 -195
  226. data/lib/amp/revlogs/node.rb +0 -18
  227. data/lib/amp/revlogs/revlog.rb +0 -1045
  228. data/lib/amp/revlogs/revlog_support.rb +0 -126
  229. data/lib/amp/support/ignore.rb +0 -144
  230. data/site/Rakefile +0 -38
  231. data/test/test_amp.rb +0 -9
  232. data/test/test_helper.rb +0 -15
@@ -0,0 +1,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