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
@@ -1,950 +0,0 @@
1
- module Amp
2
- module Repositories
3
-
4
- ##
5
- # An entry in the dirstate. Similar to IndexEntry for revlogs. Simple struct, that's
6
- # all.
7
- class DirStateEntry < Struct.new(:status, :mode, :size, :mtime)
8
-
9
- ##
10
- # shortcuts!
11
- def removed?; self.status == :removed; end
12
- def added?; self.status == :added; end
13
- def untracked?; self.status == :untracked; end
14
- def modified?; self.status == :modified; end
15
- def merged?; self.status == :merged; end
16
- def normal?; self.status == :normal; end
17
- def forgotten?; self.status == :forgotten; end
18
-
19
- ##
20
- # Do I represent a dirty object?
21
- #
22
- # @return [Boolean] does this array represent a dirty object in a DirState?
23
- def dirty?
24
- self[-2] == -2 && self[-1] == -1 && self.normal?
25
- end
26
-
27
- ##
28
- # Do I possibly represent a dirty object?
29
- #
30
- # @return [Boolean] does this array possibly represent a dirty object in a DirState?
31
- def maybe_dirty?
32
- self[-2] == -1 && self[-1] == -1 && self.normal?
33
- end
34
- end
35
-
36
- ##
37
- # = DirState
38
- # This class handles parsing and manipulating the "dirstate" file, which is stored
39
- # in the .hg folder. This file handles which files are marked for addition, removal,
40
- # copies, and so on. The structure of each entry is below.
41
- #
42
- #
43
- # class DirStateEntry < BitStruct
44
- # default_options :endian => :network
45
- #
46
- # char :status , 8, "the state of the file"
47
- # signed :mode , 32, "mode"
48
- # signed :size , 32, "size"
49
- # signed :mtime , 32, "mtime"
50
- # signed :fname_size , 32, "filename size"
51
- #
52
- # end
53
- class DirState
54
- include Ignore
55
- include RevlogSupport::Node
56
-
57
- UNKNOWN = DirStateEntry.new(:untracked, 0, 0, 0)
58
- FORMAT = "cNNNN"
59
-
60
- class FileNotInRootError < StandardError; end
61
- class AbsolutePathNeededError < StandardError; end
62
-
63
- # The parents of the current state. If there's been an uncommitted merge,
64
- # it will be two. Otherwise it will just be one parent and +NULL_ID+
65
- attr_reader :parents
66
-
67
- # The number of directories in each base ["dir" => #_of_dirs]
68
- attr_reader :dirs
69
-
70
- # The files mapped to their stats (state, mode, size, mtime)
71
- # [state, mode, size, mtime]
72
- attr_reader :files
73
-
74
- # A map of files to be copied, because we want to preserve their history
75
- # "source" => "dest"
76
- attr_reader :copy_map
77
-
78
- # I still don't know what this does
79
- attr_reader :folds
80
-
81
- # The conglomerate config object of global configs and the repo
82
- # specific config.
83
- attr_reader :config
84
-
85
- # The root of the repository
86
- attr_reader :root
87
-
88
- # The opener to access files. The only files that will be touched lie
89
- # in the .hg/ directory, so the default MUST be +:open_hg+.
90
- attr_reader :opener
91
-
92
- ##
93
- # Creates a DirState object. This is used to represent, in memory (and
94
- # occasionally on file) how the repository is being changed.
95
- # It's really simple, and it is really the basis for _using_ the repo
96
- # (contrary to how Revlog is the basis for _saving_ the repo).
97
- #
98
- # @param [String] root the absolute path to the root of the repository
99
- # @param [Amp::AmpConfig] config the config file of hgrc
100
- # @param [Amp::Opener] opener the opener to open files with
101
- def initialize(root, config, opener)
102
- unless root[0, 1] == "/"
103
- raise AbsolutePathNeededError, "#{root} is not an absolute path!"
104
- end
105
-
106
- # root must be an aboslute path with no ending slash
107
- @root = root[-1, 1] == "/" ? root[0..-2] : root # the root of the repo
108
- @config = config # the config file where we get defaults
109
- @opener = opener # opener to retrieve files (default: open_hg)
110
- @dirty = false # has something changed, and do we need to write?
111
- @dirty_parents = false
112
- @parents = [NULL_ID, NULL_ID] # the parent revisions
113
- @dirs = {} # number of directories in each base ["dir" => #_of_dirs]
114
- @files = {} # the files mapped to their statistics
115
- @copy_map = {} # src => dest
116
- @ignore = [] # dirs and files to ignore
117
- @folds = []
118
- @check_exec = nil
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, but only if @ignore_all is nil (really
159
- # only if @ignore_all isn't a Boolean value, but we set it to nil)
160
- #
161
- # @param [String] file the path to the file that will be checked by
162
- # the .hgignore file
163
- # @return [Boolean] whether we're ignoring the path or not
164
- def ignore(file)
165
- return true if @ignore_all == true
166
- return false if @ignore_all == false
167
- @ignore_matches ||= parse_ignore @root, @ignore
168
- @ignore_matches.call file
169
- end
170
-
171
- ##
172
- # Gets the current branch.
173
- #
174
- # @return [String] the current branch in the working directory
175
- def branch
176
- text = @opener.read('branch').strip
177
- @branch ||= text.empty? ? "default" : text
178
- rescue
179
- @branch = "default"
180
- end
181
-
182
- ##
183
- # Set the branch to +branch+.
184
- #
185
- # @param [#to_s] brnch the branch to switch to
186
- # @return [String] +brnch+.to_s
187
- def branch=(brnch)
188
- @branch = brnch.to_s
189
-
190
- @opener.open 'branch', 'w' do |f|
191
- f.puts brnch.to_s
192
- end
193
- @branch
194
- end
195
-
196
- ##
197
- # Set the parents to +p+
198
- #
199
- # @param [Array<String>] p the parents as binary strings
200
- # @return [Array<String>] the parents, as will be used by the dirstate
201
- def parents=(p)
202
- @parents = if p.is_a? Array
203
- p.size == 1 ? p + [NULL_ID] : p[0..1]
204
- else
205
- [p, NULL_ID]
206
- end
207
-
208
- @dirty_parents = true
209
- @dirty = true
210
- @parents # return this
211
- end
212
- alias_method :parent, :parents
213
-
214
- ##
215
- # Set the file as "to be added".
216
- #
217
- # @param [String] file the path of the file to add
218
- # @return [Boolean] a success marker
219
- def add(file)
220
- add_path file, true
221
-
222
- @dirty = true
223
- @files[file] = DirStateEntry.new(:added, 0, -1, -1)
224
- @copy_map.delete file
225
- true # success
226
- end
227
-
228
- ##
229
- # Set the file as "normal", meaning no changes. This is the same
230
- # as dirstate.normal in dirstate.py, for those referencing both.
231
- #
232
- # @param [String] file the path of the file to clean
233
- # @return [Boolean] a success marker
234
- def normal(file)
235
- @dirty = true
236
- add_path file, true
237
-
238
- f = File.lstat "#{@root}/#{file}"
239
- @files[file] = DirStateEntry.new(:normal, f.mode, f.size, f.mtime.to_i)
240
- @copy_map.delete file
241
- true # success
242
- end
243
- alias_method :clean, :normal
244
-
245
- ##
246
- # Set the file as normal, but possibly dirty. It's like when you
247
- # meet a cool girl, and she seems really innocent and it's a chance
248
- # for you to maybe change yourself and make a new friend, but then
249
- # she *might* actually be a total slut. Better milk that grapevine
250
- # to find out the truth. Oddly specific, huh.
251
- #
252
- # THUS IS THE HISTORY OF THIS METHOD!
253
- #
254
- # And then one day you go to the movies with some other girl, and the
255
- # original crazy slutty girl is the cashier next to you. Unsure of
256
- # what to do, you don't do anything. Next thing you know, she's trying
257
- # to get your attention to say hey. WTF? Anyone know what's up with this
258
- # girl?
259
- #
260
- # After milking that grapevine, you find out that she's not a great person.
261
- # There's nothing interesting there and you should just move on.
262
- #
263
- # *sigh* girls.
264
- #
265
- # @param [String] file the path of the file to mark
266
- # @return [Boolean] a success marker
267
- def maybe_dirty(file)
268
- if @files[file] && @parents.last != NULL_ID
269
- # if there's a merge happening and the file was either modified
270
- # or dirty before being removed, restore that state.
271
- # I'm quoting the python with that one.
272
- # I guess what it's saying is that if a file is being removed
273
- # by a merge, but it was altered somehow beforehand on the local
274
- # repo, then play it safe and bring back the dead. Divine intervention
275
- # on the side of the local repo.
276
-
277
- # info here is a standard array of info
278
- # [action, mode, size, mtime]
279
- info = @files[file]
280
-
281
- if info.removed? and [-1, -2].member? info.size
282
- source = @copy_map[file]
283
-
284
- # do the appropriate action
285
- case info.size
286
- when -1 # either merge it
287
- merge file
288
- when -2 # or mark it as dirty
289
- dirty file
290
- end
291
-
292
- copy source => file if source
293
- return
294
- end
295
-
296
- # next step... the base case!
297
- return true if info.modified? || info.maybe_dirty? and info.size == -2
298
- end
299
-
300
- @dirty = true # make the repo dirty
301
- add_path file # add the file
302
-
303
- @files[file] = DirStateEntry.new(:normal, 0, -1, -1) # give it info
304
- @copy_map.delete file # we're not copying it since we're adding it
305
- true # success
306
- end
307
-
308
- ##
309
- # Checks whether the dirstate is tracking the given file.
310
- #
311
- # @param f the file to check for
312
- # @return [Boolean] whether or not the file is being tracked.
313
- def include?(path)
314
- not @files[path].nil?
315
- end
316
- alias_method :tracking?, :include?
317
-
318
- ##
319
- # Mark the file as "dirty"
320
- #
321
- # @param [String] file the path of the file to mark
322
- # @return [Boolean] a success marker
323
- def dirty(file)
324
- @dirty = true
325
- add_path file
326
-
327
- @files[file] = DirStateEntry.new(:normal, 0, -2, -1)
328
- @copy_map.delete file
329
- true # success
330
- end
331
-
332
- ##
333
- # Set the file as "to be removed"
334
- #
335
- # @param [String] file the path of the file to remove
336
- # @return [Boolean] a success marker
337
- def remove(file)
338
- @dirty = true
339
- drop_path file
340
-
341
- size = 0
342
- if @parents.last.null? && (info = @files[file])
343
- if info.merged?
344
- size = -1
345
- elsif info.normal? && info.size == -2
346
- size = -2
347
- end
348
- end
349
- @files[file] = DirStateEntry.new(:removed, 0, size, 0)
350
- @copy_map.delete file if size.zero?
351
- true # success
352
- end
353
-
354
- ##
355
- # Prepare the file to be merged
356
- #
357
- # @param [String] file the path of the file to merge
358
- # @return [Boolean] a success marker
359
- def merge(file)
360
- @dirty = true
361
- add_path file
362
-
363
- stats = File.lstat "#{@root}/#{file}"
364
- add_path file
365
- @files[file] = DirStateEntry.new(:merged, stats.mode, stats.size, stats.mtime.to_i)
366
- @copy_map.delete file
367
- true # success
368
- end
369
-
370
- ##
371
- # Forget the file, erase it from the repo
372
- #
373
- # @param [String] file the path of the file to forget
374
- # @return [Boolean] a success marker
375
- def forget(file)
376
- @dirty = true
377
- drop_path file
378
- @files.delete file
379
- true # success
380
- end
381
-
382
- ##
383
- # Invalidates the dirstate, making it completely unusable until it is
384
- # re-read. Should only be used in error situations.
385
- def invalidate!
386
- %w(@files @copy_map @folds @branch @parents @dirs @ignore).each do |ivar|
387
- instance_variable_set(ivar, nil)
388
- end
389
- @dirty = false
390
- end
391
-
392
- ##
393
- # Refresh the directory's state, making everything empty.
394
- # Called by #rebuild.
395
- #
396
- # This is not the same as #initialize, so we can't just run
397
- # `send :initialize` and call it a day :-(
398
- #
399
- # @return [Boolean] a success marker
400
- def clear
401
- @files = {}
402
- @dirs = {}
403
- @copy_map = {}
404
- @parents = [NULL_ID, NULL_ID]
405
- @dirty = true
406
-
407
- true # success
408
- end
409
-
410
- ##
411
- # Rebuild the directory's state. Needs Manifest, as that's
412
- # what the files really are.
413
- #
414
- # @param [String] parent the binary format of the parent
415
- # @param [ManifestEntry] files the files in a specific revision
416
- # @return [Boolean] a success marker
417
- def rebuild(parent, files)
418
- clear
419
-
420
- # alter each file according to its flags
421
- files.each do |f|
422
- mode = files.flags(f).include?('x') ? 0777 : 0666
423
- @files[f] = DirStateEntry.new(:normal, mode, -1, 0)
424
- end
425
-
426
- @parents = [parent, NULL_ID]
427
- @dirty_parents = true
428
- true # success
429
- end
430
-
431
- ##
432
- # Save the data to .hg/dirstate.
433
- # Uses mode: "w", so it overwrites everything
434
- #
435
- # @todo watch memory usage - +si+ could grow unrestrictedly which would
436
- # bog down the entire program
437
- # @return [Boolean] a success marker
438
- def write
439
- return true unless @dirty
440
- begin
441
- @opener.open "dirstate", 'w' do |state|
442
- gran = @config['dirstate']['granularity'] || 1 # self._ui.config('dirstate', 'granularity', 1)
443
-
444
- limit = 2147483647 # sorry for the literal use...
445
- limit = state.mtime - gran if gran > 0
446
-
447
- si = StringIO.new "", (ruby_19? ? "w+:ASCII-8BIT" : "w+")
448
- si.write @parents.join
449
-
450
- @files.each do |file, info|
451
- file = file.dup # so we don't corrupt vars
452
- info = info.dup.to_a # UNLIKE PYTHON
453
- info[0] = info[0].to_hg_int
454
-
455
- # I should probably do mah physics hw. nah, i'll do it
456
- # tomorrow during my break
457
- # good news - i did pretty well on my physics test by using
458
- # brian ford's name instead of my own.
459
- file = "#{file}\0#{@copy_map[file]}" if @copy_map[file]
460
- info = [info[0], 0, (-1).to_signed(32), (-1).to_signed(32)] if info[3].to_i > limit.to_i and info[0] == :normal
461
- info << file.size # the final element to make it pass, which is the length of the filename
462
- info = info.pack FORMAT # pack them their lunch
463
- si.write info # and send them off
464
- si.write file # to school
465
- end
466
-
467
- state.write si.string
468
- @dirty = false
469
- @dirty_parents = false
470
-
471
- true # success
472
- end
473
- rescue IOError
474
- false
475
- end
476
- end
477
-
478
- ##
479
- # Copies the files in h (represented as "source" => "dest").
480
- #
481
- # @param [Hash<String => String>] h the keys are sources and the values
482
- # are dests
483
- # @return [Boolean] a success marker
484
- def copy(h={})
485
- h.each do |source, dest|
486
- next if source == dest
487
- return true unless source
488
-
489
- @dirty = true
490
-
491
- if @copy_map[dest]
492
- then @copy_map.delete dest
493
- else @copy_map[dest] = source
494
- end
495
- end
496
-
497
- true # success
498
- end
499
-
500
- ##
501
- # The current directory from where the command is being called, with
502
- # the path shortened if it's within the repo.
503
- #
504
- # @return [String] effectively Dir.pwd
505
- def cwd
506
- path = Dir.pwd
507
- return '' if path == @root
508
-
509
- # return a more local path if possible...
510
- return path[@root.length..-1] if path.start_with? @root
511
- path # else we're outside the repo
512
- end
513
- alias_method :pwd, :cwd
514
-
515
- ##
516
- # Returns the relative path from +src+ to +dest+.
517
- #
518
- # @param [String] src This is a directory! If this is relative,
519
- # it is assumed to be relative to the root.
520
- # @param [String] dest This MUST be within root! It also is a file.
521
- # @return [String] the relative path
522
- def path_to(src, dest)
523
- # first, make both paths absolute, for ease of use.
524
- # @root is guarenteed to be absolute, so we're leethax here
525
- src = File.join @root, src
526
- dest = File.join @root, dest
527
-
528
- # lil' bit of error checking...
529
- [src, dest].map do |f|
530
- unless File.exist? f # does both files and directories...
531
- raise FileNotInRootError, "#{f} is not in the root, #{@root}"
532
- end
533
- end
534
-
535
- # now we find the differences
536
- # these both are now arrays!!!
537
- src = src.split '/'
538
- dest = dest.split '/'
539
-
540
- while src.first == dest.first
541
- src.shift and dest.shift
542
- end
543
-
544
- # now, src and dest are just where they differ
545
- path = ['..'] * src.size # we want to go back this many directories
546
- path += dest
547
- path.join '/' # tadah!
548
- end
549
-
550
- ##
551
- # Walk recursively through the directory tree, finding all
552
- # files matched by the regexp in match.
553
- #
554
- # Step 1: find all explicit files
555
- # Step 2: visit subdirectories
556
- # Step 3: report unseen items in the @files hash
557
- #
558
- # @param [Boolean] unknown
559
- # @param [Boolean] ignored
560
- # @return [Hash<String => [NilClass, File::Stat]>] nil for directories and
561
- # stuff, File::Stat for files and links
562
- def walk(unknown, ignored, match)
563
- files = match.files
564
-
565
- bad_type = proc do |file|
566
- UI::warn "#{file}: unsupported file type (type is #{File.ftype file})"
567
- end
568
-
569
- if ignored
570
- @ignore_all = false
571
- elsif not unknown
572
- @ignore_all = true
573
- end
574
-
575
- work = [@root]
576
-
577
- files = match.files ? match.files.uniq : [] # because [].uniq! is a major fuckup
578
-
579
- # why do we overwrite the entire array if it includes the current dir?
580
- # we even kill posisbly good things
581
- files = [''] if files.empty? || files.include?('.') # strange thing to do
582
- results = {'.hg' => true}
583
-
584
- # Step 1: find all explicit files
585
- files.sort.each do |file|
586
- next if results[file] || file == ""
587
-
588
- begin
589
- stats = File.lstat File.join(@root, file)
590
- kind = File.ftype File.join(@root, file)
591
-
592
- # we'll take it! but only if it's a directory, which means we have
593
- # more work to do...
594
- if kind == 'directory'
595
- # add it to the list of dirs we have to search in
596
- work << File.join(@root, file) unless ignoring_directory? file
597
- elsif kind == 'file' || kind == 'link'
598
- # ARGH WE FOUND ZE BOOTY
599
- results[file] = stats
600
- else
601
- # user you are a fuckup in life please exit the world
602
- bad_type[file]
603
- results[file] = nil if @files[file]
604
- end
605
- rescue => e
606
- keep = false
607
- prefix = file + '/'
608
-
609
- @files.each do |f, _|
610
- if f == file || f.start_with?(prefix)
611
- keep = true
612
- break
613
- end
614
- end
615
-
616
- unless keep
617
- bad_type[file]
618
- results[file] = nil if (@files[file] || !ignore(file)) && match.call(file)
619
- end
620
- end
621
- end
622
-
623
- # step 2: visit subdirectories in `work`
624
- until work.empty?
625
- dir = work.shift
626
- skip = nil
627
-
628
- if dir == '.'
629
- dir = ''
630
- else
631
- skip = '.hg'
632
- end
633
-
634
-
635
- dirs = Dir.glob("#{dir}/*", File::FNM_DOTMATCH) - ["#{dir}/.", "#{dir}/.."]
636
- entries = dirs.inject({}) do |h, f|
637
- h.merge f => [File.ftype(f), File.lstat(f)]
638
- end
639
-
640
-
641
- entries.each do |f, arr|
642
- tf = f[(@root.size+1)..-1]
643
- kind = arr[0]
644
- stats = arr[1]
645
- unless results[tf]
646
- if kind == 'directory'
647
- work << f unless ignore tf
648
- results[tf] = nil if @files[tf] && match.call(tf)
649
- elsif kind == 'file' || kind == 'link'
650
- if @files[tf]
651
- results[tf] = stats if match.call tf
652
- elsif match.call(tf) && !ignore(tf)
653
- results[tf] = stats
654
- end
655
- elsif @files[tf] && match.call(tf)
656
- results[tf] = nil
657
- end
658
- end
659
- end
660
- end
661
-
662
- # step 3: report unseen items in @files
663
- visit = @files.keys.select {|f| !results[f] && match.call(f) }.sort
664
-
665
- # zip it to a hash of {file_name => file_stats}
666
- hash = visit.inject({}) do |h, f|
667
- h.merge!(f => File.stat(File.join(@root,f))) rescue h.merge!(f => nil)
668
- end
669
-
670
- hash.each do |file, stat|
671
- unless stat.nil?
672
- # because filestats can't be gathered if it's, say, a directory
673
- stat = nil unless ['file', 'link'].include? File.ftype(File.join(@root, file))
674
- end
675
- results[file] = stat
676
- end
677
-
678
- results.delete ".hg"
679
- @ignore_all = nil # reset this
680
- results
681
- end
682
-
683
- ##
684
- # what's the current state of life, man!
685
- # Splits up all the files into modified, clean,
686
- # added, deleted, unknown, ignored, or lookup-needed.
687
- #
688
- # @return [Hash<Symbol => Array<String>>] a hash of the filestatuses and their files
689
- def status(ignored, clean, unknown, match = Match.new { true })
690
- list_ignored, list_clean, list_unknown = ignored, clean, unknown
691
- lookup, modified, added, unknown, ignored = [], [], [], [], []
692
- removed, deleted, clean = [], [], []
693
- delta = 0
694
-
695
- walk(list_unknown, list_ignored, match).each do |file, st|
696
- next if file.nil?
697
-
698
- unless @files[file]
699
- if list_ignored && ignoring_directory?(file)
700
- ignored << file
701
- elsif list_unknown
702
- unknown << file unless ignore(file)
703
- end
704
-
705
- next # on to the next one, don't do the rest
706
- end
707
-
708
- # here's where we split up the files
709
- state, mode, size, time = *@files[file].to_a
710
- delta += (size - st.size).abs if st && size >= 0 # increase the delta, but don't forget to check that it's not nil
711
- if !st && [:normal, :modified, :added].include?(state)
712
- # add it to the deleted folder if it should be here but isn't
713
- deleted << file
714
- elsif state == :normal
715
- if (size >= 0 && (size != st.size || ((mode ^ st.mode) & 0100 and @check_exec))) || size == -2 || @copy_map[file]
716
- modified << file
717
- elsif time != st.mtime.to_i # DOH - we have to remember that times are stored as fixnums
718
- lookup << file
719
- elsif list_clean
720
- clean << file
721
- end
722
-
723
- elsif state == :merged
724
- modified << file
725
- elsif state == :added
726
- added << file
727
- elsif state == :removed
728
- removed << file
729
- end
730
- end
731
-
732
- r = { :modified => modified.sort , # those that have clearly been modified
733
- :added => added.sort , # those that are marked for adding
734
- :removed => removed.sort , # those that are marked for removal
735
- :deleted => deleted.sort , # those that should be here but aren't
736
- :unknown => unknown.sort , # those that aren't being tracked
737
- :ignored => ignored.sort , # those that are being deliberately ignored
738
- :clean => clean.sort , # those that haven't changed
739
- :lookup => lookup.sort , # those that need to be content-checked to see if they've changed
740
- :delta => delta # how many bytes have been added or removed from files (not bytes that have been changed)
741
- }
742
- end
743
-
744
-
745
- ##
746
- # Reads the data in the .hg folder and fills in the vars
747
- #
748
- # @return [Amp::DirState] self -- chainable!
749
- def read!
750
- @parents, @files, @copy_map = parse('dirstate')
751
- self # chainable
752
- end
753
-
754
- private
755
- ##
756
- # Generates the @ignore array
757
- # The array is full of paths relative to the root, which
758
- # makes things easier for the proc-generation phase.
759
- #
760
- # @return [NilClass]
761
- def generate_ignore
762
- @ignore = @config['ui'].map do |k, v|
763
- @ignore << "#{v}" if k == "ignore"
764
- end
765
-
766
- @ignore << ".hgignore"
767
- @ignore.compact
768
-
769
- nil
770
- end
771
-
772
- ##
773
- # Perform various checks on the file before upping the content count
774
- # for all of its parent directories. It checks for:
775
- # * filenames containing "\n" or "\r" (newlines and carriage returns)
776
- # * filenames with the same names as directories
777
- # * clashing filenames
778
- #
779
- # It only increments the dirs' file count if the file is untracked or
780
- # being removed.
781
- #
782
- # @param [String] f Should be formatted like ["action", mode, size, mtime]
783
- # @param [Boolean] check whether to perform any of the checks
784
- # @return [NilClass]
785
- def add_path(f, check=false)
786
- old_state = @files[f] || DirStateEntry.new # it's an array of info, remember
787
-
788
- if check || old_state.removed?
789
- raise "Bad Filename" if f.match(/\r|\n/)
790
- raise "Directory #{f} already exists" if @dirs[f]
791
-
792
- # make sure we don't have any files with the same name as a directory
793
- directories_to(f).each do |d|
794
- break if @dirs[d]
795
-
796
- if @files[d] && !@files[d].removed?
797
- raise "File #{d} clashes with #{f}! Fix their names"
798
- end
799
- end
800
- end
801
-
802
- # only inc the dirs if the file is untracked or being removed.
803
- if [:untracked, :removed].include? old_state.status
804
- # inc the number of dirs in each dir
805
- inc_directories_to f
806
- end
807
-
808
- nil
809
- end
810
-
811
- ##
812
- # Conditional wrapper around +dec_directories_to+. It will dec the
813
- # directories if the file in question (+f+) is either untracked or
814
- # being removed.
815
- #
816
- # @param [String] f Should be formatted like ["action", mode, size, mtime]
817
- # @return [NilClass]
818
- def drop_path(f)
819
- unless [:untracked, :removed].include? f[0]
820
- dec_directories_to(f)
821
- end
822
-
823
- nil
824
- end
825
-
826
- ##
827
- # All directories leading up to this path
828
- #
829
- # @example directories_to "/Users/ari/src/monkey.txt" # =>
830
- # ["/Users/ari/src", "/Users/ari", "/Users"]
831
- # @param [String] path the path to the file we're examining
832
- # @return [Array] the directories leading up to this path
833
- def directories_to(path)
834
- File.amp_directories_to path
835
- end
836
-
837
- ##
838
- # Increment all directories' dir-count leading up to this path.
839
- # The dir-count is the path's value in @dirs.
840
- # This is used when adding a file.
841
- #
842
- # @param [String] path the path we're disecting
843
- # @return [NilClass]
844
- def inc_directories_to(path)
845
- p = directories_to(path).first
846
- @dirs[p] ||= 0
847
- @dirs[p] += 1
848
- nil
849
- end
850
-
851
- ##
852
- # Decrement all directories' dir-count leading up to this path.
853
- # The dir-count is the path's value in @dirs.
854
- # This is used when removing a file.
855
- #
856
- # @param [String] path the path we're disecting
857
- # @return [NilClass]
858
- def dec_directories_to(path)
859
- p = directories_to(path).first
860
- # if the dir has 0, kill the dir. we don't need it anymore
861
- if @dirs[p] && @dirs[p].zero?
862
- @dirs.delete p
863
- elsif @dirs[p]
864
- @dirs[p] -= 1 # we only need to inc the latest dir
865
- end
866
-
867
- nil
868
- end
869
-
870
- ##
871
- # I wish I knew what this did or when it was called.
872
- #
873
- # @todo figure out what this does
874
- # @param [String] path the path to a file
875
- # @return [String] All I know is that this returns a string
876
- def normalize(path)
877
- fold_path = @folds[path]
878
- fold_path = path unless fold_path # if fold_path is true, then this line returns nil
879
- fold_path # so we need an extra line here to make sure it returns a good value
880
- end
881
-
882
- ##
883
- # Are we ignoring the directory?
884
- #
885
- # @param [String] dir the directory we're checking, either aboslute or relative
886
- # @return [Boolean] are we ignoring the dir?
887
- def ignoring_directory?(dir)
888
- return true if @ignore_all
889
- return false if @ignore_all == false
890
- return false if dir == '.' # base cases
891
- return true if ignore dir # base cases
892
-
893
- !!directories_to(dir).any? {|d| ignore d }
894
- end
895
- alias_method :ignoring_dir?, :ignoring_directory?
896
-
897
- ##
898
- # Parses the dirstate file in .hg
899
- #
900
- # @param [String] file path to the file to parse
901
- # @return [((String, String), Hash<String => (Integer, Integer, Integer)>, Hash<String => String>)]
902
- # a tuple of (parents, files, copies). Parents is a tuple of the parents,
903
- # files is a hash of filename => [mode, size, mtime], and copies is a hash of src => dest
904
- def parse(file)
905
- # the main data we need to return
906
- files = {}
907
- copies = {}
908
- parents = []
909
- @opener.open file, "r" do |s|
910
-
911
- # the parents are the first 40 bytes
912
- parent = s.read(20) || NULL_ID
913
- parent_ = s.read(20) || NULL_ID
914
- parents = [parent, parent_]
915
-
916
- # 1 character + 4 32-bit ints = 17 bytes
917
- e_size = 17
918
-
919
- # this loop is just cycling through and reading every entry
920
- while !s.eof?
921
- # read 1 entry
922
- info = s.read(e_size).unpack FORMAT
923
-
924
- # byte swap and shizzle
925
- info = [info[0].to_dirstate_symbol, info[1], info[2].to_signed(32), info[3].to_signed(32), info[4]]
926
- # ^^^^ we have to sign them because otherwise they'll be hugely wrong
927
-
928
- # read in the filename
929
- f = s.read(info[4])
930
-
931
- # if it has an \0, then we've moved/copied it
932
- if f.match(/\0/)
933
- source, dest = f.split "\0"
934
- copies[source] = dest
935
- f = source
936
- end
937
-
938
- # and put in the info for the file itself
939
- files[f] = DirStateEntry.new(*info[0..3])
940
- end
941
- end
942
-
943
- [parents, files, copies]
944
- rescue Errno::ENOENT
945
- # no file? easy peasy
946
- [[NULL_ID, NULL_ID], {}, {}]
947
- end
948
- end
949
- end
950
- end