amp-core 0.1.0

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 (46) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +17 -0
  6. data/Rakefile +67 -0
  7. data/VERSION +1 -0
  8. data/features/amp-core.feature +9 -0
  9. data/features/step_definitions/amp-core_steps.rb +0 -0
  10. data/features/support/env.rb +4 -0
  11. data/lib/amp-core.rb +53 -0
  12. data/lib/amp-core/command_ext/repository_loading.rb +31 -0
  13. data/lib/amp-core/repository/abstract/abstract_changeset.rb +113 -0
  14. data/lib/amp-core/repository/abstract/abstract_local_repo.rb +208 -0
  15. data/lib/amp-core/repository/abstract/abstract_staging_area.rb +202 -0
  16. data/lib/amp-core/repository/abstract/abstract_versioned_file.rb +116 -0
  17. data/lib/amp-core/repository/abstract/common_methods/changeset.rb +185 -0
  18. data/lib/amp-core/repository/abstract/common_methods/local_repo.rb +293 -0
  19. data/lib/amp-core/repository/abstract/common_methods/staging_area.rb +248 -0
  20. data/lib/amp-core/repository/abstract/common_methods/versioned_file.rb +87 -0
  21. data/lib/amp-core/repository/generic_repo_picker.rb +94 -0
  22. data/lib/amp-core/repository/repository.rb +41 -0
  23. data/lib/amp-core/support/encoding_utils.rb +46 -0
  24. data/lib/amp-core/support/platform_utils.rb +92 -0
  25. data/lib/amp-core/support/rooted_opener.rb +143 -0
  26. data/lib/amp-core/support/string_utils.rb +86 -0
  27. data/lib/amp-core/templates/git/blank.log.erb +18 -0
  28. data/lib/amp-core/templates/git/default.log.erb +18 -0
  29. data/lib/amp-core/templates/mercurial/blank.commit.erb +23 -0
  30. data/lib/amp-core/templates/mercurial/blank.log.erb +18 -0
  31. data/lib/amp-core/templates/mercurial/default.commit.erb +23 -0
  32. data/lib/amp-core/templates/mercurial/default.log.erb +26 -0
  33. data/lib/amp-core/templates/template.rb +202 -0
  34. data/spec/amp-core_spec.rb +11 -0
  35. data/spec/command_ext_specs/repository_loading_spec.rb +64 -0
  36. data/spec/command_ext_specs/spec_helper.rb +1 -0
  37. data/spec/repository_specs/repository_spec.rb +41 -0
  38. data/spec/repository_specs/spec_helper.rb +1 -0
  39. data/spec/spec.opts +1 -0
  40. data/spec/spec_helper.rb +14 -0
  41. data/spec/support_specs/encoding_utils_spec.rb +69 -0
  42. data/spec/support_specs/platform_utils_spec.rb +33 -0
  43. data/spec/support_specs/spec_helper.rb +1 -0
  44. data/spec/support_specs/string_utils_spec.rb +44 -0
  45. data/test/test_templates.rb +81 -0
  46. metadata +157 -0
@@ -0,0 +1,202 @@
1
+ ##################################################################
2
+ # Licensing Information #
3
+ # #
4
+ # The following code is licensed, as standalone code, under #
5
+ # the Ruby License, unless otherwise directed within the code. #
6
+ # #
7
+ # For information on the license of this code when distributed #
8
+ # with and used in conjunction with the other modules in the #
9
+ # Amp project, please see the root-level LICENSE file. #
10
+ # #
11
+ # © Michael J. Edgar and Ari Brown, 2009-2010 #
12
+ # #
13
+ ##################################################################
14
+
15
+ module Amp
16
+ module Core
17
+ module Repositories
18
+ class AbstractStagingArea
19
+ include CommonStagingAreaMethods
20
+
21
+ ##
22
+ # Marks a file to be added to the repository upon the next commit.
23
+ #
24
+ # @param [[String]] filenames a list of files to add in the next commit
25
+ # @return [Boolean] true for success, false for failure
26
+ def add(*filenames)
27
+ raise NotImplementedError.new("add() must be implemented by subclasses of AbstractStagingArea.")
28
+ end
29
+
30
+ ##
31
+ # Marks a file to be removed from the repository upon the next commit. Last argument
32
+ # can be a hash, which can take an :unlink key, specifying whether the files should actually
33
+ # be removed or not.
34
+ #
35
+ # @param [[String]] filenames a list of files to remove in the next commit
36
+ # @return [Boolean] true for success, false for failure
37
+ def remove(*filenames)
38
+ raise NotImplementedError.new("remove() must be implemented by subclasses of AbstractStagingArea.")
39
+ end
40
+
41
+ ##
42
+ # Set +file+ as normal and clean. Un-removes any files marked as removed, and
43
+ # un-adds any files marked as added.
44
+ #
45
+ # @param [Array<String>] files the name of the files to mark as normal
46
+ # @return [Boolean] success marker
47
+ def normal(*files)
48
+ raise NotImplementedError.new("normal() must be implemented by subclasses of AbstractStagingArea.")
49
+ end
50
+
51
+ ##
52
+ # Mark the files as untracked.
53
+ #
54
+ # @param [Array<String>] files the name of the files to mark as untracked
55
+ # @return [Boolean] success marker
56
+ def forget(*files)
57
+ raise NotImplementedError.new("forget() must be implemented by subclasses of AbstractStagingArea.")
58
+ end
59
+
60
+ ##
61
+ # Marks a file to be copied from the +from+ location to the +to+ location
62
+ # in the next commit, while retaining history.
63
+ #
64
+ # @param [String] from the source of the file copy
65
+ # @param [String] to the destination of the file copy
66
+ # @return [Boolean] true for success, false for failure
67
+ def copy(from, to)
68
+ raise NotImplementedError.new("copy() must be implemented by subclasses of AbstractStagingArea.")
69
+ end
70
+
71
+ ##
72
+ # Marks a file to be moved from the +from+ location to the +to+ location
73
+ # in the next commit, while retaining history.
74
+ #
75
+ # @param [String] from the source of the file move
76
+ # @param [String] to the destination of the file move
77
+ # @return [Boolean] true for success, false for failure
78
+ def move(from, to)
79
+ raise NotImplementedError.new("move() must be implemented by subclasses of AbstractStagingArea.")
80
+ end
81
+
82
+ ##
83
+ # Marks a modified file to be included in the next commit.
84
+ # If your VCS does this implicitly, this should be defined as a no-op.
85
+ #
86
+ # @param [[String]] filenames a list of files to include for committing
87
+ # @return [Boolean] true for success, false for failure
88
+ def include(*filenames)
89
+ raise NotImplementedError.new("include() must be implemented by subclasses of AbstractStagingArea.")
90
+ end
91
+ alias_method :stage, :include
92
+
93
+ ##
94
+ # Mark a modified file to not be included in the next commit.
95
+ # If your VCS does not include this idea because staging a file is implicit, this should
96
+ # be defined as a no-op.
97
+ #
98
+ # @param [[String]] filenames a list of files to remove from the staging area for committing
99
+ # @return [Boolean] true for success, false for failure
100
+ def exclude(*filenames)
101
+ raise NotImplementedError.new("exclude() must be implemented by subclasses of AbstractStagingArea.")
102
+ end
103
+ alias_method :unstage, :exclude
104
+
105
+ ##
106
+ # Returns a Symbol.
107
+ # Possible results:
108
+ # :added (subset of :included)
109
+ # :removed
110
+ # :untracked
111
+ # :included
112
+ # :normal
113
+ #
114
+ def file_status(filename)
115
+ raise NotImplementedError.new("file_status() must be implemented by subclasses of AbstractStagingArea.")
116
+ end
117
+
118
+ ##
119
+ # The directory used by the VCS to store magical information (.hg, .git, etc.).
120
+ #
121
+ # @api
122
+ # @return [String] relative to root
123
+ def vcs_dir
124
+ raise NotImplementedError.new("vcs_dir() must be implemented by subclasses of AbstractStagingArea.")
125
+ end
126
+
127
+ ##
128
+ # Saves the staging area's state. Any added files, removed files, "normalized" files
129
+ # will have that status saved here.
130
+ def save
131
+ raise NotImplementedError.new("save() must be implemented by subclasses of AbstractStagingArea")
132
+ end
133
+
134
+ ##
135
+ # Returns all files tracked by the repository *for the working directory* - not
136
+ # to be confused with the most recent changeset.
137
+ #
138
+ # @api
139
+ # @return [Array<String>] all files tracked by the repository at this moment in
140
+ # time, including just-added files (for example) that haven't been committed yet.
141
+ def all_files
142
+ raise NotImplementedError.new("all_files() must be implemented by subclasses of AbstractStagingArea.")
143
+ end
144
+
145
+ ##
146
+ # Returns whether the given directory is being ignored. Optional method - defaults to
147
+ # +false+ at all times.
148
+ #
149
+ # @api-optional
150
+ # @param [String] directory the directory to check against ignoring rules
151
+ # @return [Boolean] are we ignoring this directory?
152
+ def ignoring_directory?(directory)
153
+ false
154
+ end
155
+
156
+ ##
157
+ # Returns whether the given file is being ignored. Optional method - defaults to
158
+ # +false+ at all times.
159
+ #
160
+ # @api-optional
161
+ # @param [String] file the file to check against ignoring rules
162
+ # @return [Boolean] are we ignoring this file?
163
+ def ignoring_file?(file)
164
+ false
165
+ end
166
+
167
+ ##
168
+ # Does a detailed look at a file, to see if it is clean, modified, or needs to have its
169
+ # content checked precisely.
170
+ #
171
+ # Supplements the built-in #status command so that its output will be cleaner.
172
+ #
173
+ # Defaults to report files as normal - it cannot check if a file has been modified
174
+ # without this method being overridden.
175
+ #
176
+ # @api-optional
177
+ #
178
+ # @param [String] file the filename to look up
179
+ # @param [File::Stats] st the current results of File.lstat(file)
180
+ # @return [Symbol] a symbol representing the current file's status
181
+ def file_precise_status(file, st)
182
+ return :lookup
183
+ end
184
+
185
+ ##
186
+ # Calculates the difference (in bytes) between a file and its last tracked state.
187
+ #
188
+ # Defaults to zero - in other words, it deactivates the delta feature.
189
+ #
190
+ # @api-optional
191
+ # @param [String] file the filename to look up
192
+ # @param [File::Stats] st the current results of File.lstat(file)
193
+ # @return [Fixnum] the number of bytes difference between the file and
194
+ # its last tracked state.
195
+ def calculate_delta(file, st)
196
+ 0
197
+ end
198
+
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,116 @@
1
+ ##################################################################
2
+ # Licensing Information #
3
+ # #
4
+ # The following code is licensed, as standalone code, under #
5
+ # the Ruby License, unless otherwise directed within the code. #
6
+ # #
7
+ # For information on the license of this code when distributed #
8
+ # with and used in conjunction with the other modules in the #
9
+ # Amp project, please see the root-level LICENSE file. #
10
+ # #
11
+ # © Michael J. Edgar and Ari Brown, 2009-2010 #
12
+ # #
13
+ ##################################################################
14
+
15
+ module Amp
16
+ module Core
17
+ module Repositories
18
+ class AbstractVersionedFile
19
+ include CommonVersionedFileMethods
20
+
21
+ ##
22
+ # The changeset to which this versioned file belongs.
23
+ #
24
+ # @return [AbstractChangeset]
25
+ def changeset
26
+ raise NotImplementedError.new("changeset() must be implemented by subclasses of AbstractVersionedFile.")
27
+ end
28
+
29
+ ##
30
+ # The repo to which this {VersionedFile} belongs
31
+ #
32
+ # @return [AbstractLocalRepository]
33
+ def repo
34
+ raise NotImplementedError.new("repo() must be implemented by subclasses of AbstractVersionedFile.")
35
+ end
36
+ alias_method :repository, :repo
37
+
38
+ ##
39
+ # The path to this file
40
+ #
41
+ # @return [String]
42
+ def path
43
+ raise NotImplementedError.new("path() must be implemented by subclasses of AbstractVersionedFile.")
44
+ end
45
+
46
+ ##
47
+ # The revision of the file. This can be vague, so let me explain:
48
+ # This is the revision of the repo from which this VersionedFile is.
49
+ # If this is unclear, please submit a patch fixing it.
50
+ #
51
+ # @return [Integer]
52
+ def revision
53
+ raise NotImplementedError.new("revision() must be implemented by subclasses of AbstractVersionedFile.")
54
+ end
55
+
56
+ ##
57
+ # The size of this file
58
+ #
59
+ # @return [Integer]
60
+ def size
61
+ raise NotImplementedError.new("size() must be implemented by subclasses of AbstractVersionedFile.")
62
+ end
63
+
64
+ ##
65
+ # The contents of a file at the given revision
66
+ #
67
+ # @return [String] the data at the current revision
68
+ def data
69
+ raise NotImplementedError.new("data() must be implemented by subclasses of AbstractVersionedFile.")
70
+ end
71
+
72
+ ##
73
+ # The hash value for sticking this fucker in a hash.
74
+ #
75
+ # @return [Integer]
76
+ def hash
77
+ raise NotImplementedError.new("hash() must be implemented by subclasses of AbstractVersionedFile.")
78
+ end
79
+
80
+ ##
81
+ # Has this file been renamed? If so, return some useful info
82
+ def renamed?
83
+ raise NotImplementedError.new("renamed() must be implemented by subclasses of AbstractVersionedFile.")
84
+ end
85
+
86
+ ##
87
+ # Compares to either a bit of text or another versioned file.
88
+ # Returns true if different, false for the same.
89
+ # (much like <=> == 0 for the same)
90
+ #
91
+ # @param [AbstractVersionedFile, String] item what we're being compared to
92
+ # @return [Boolean] true if different, false if same.
93
+ def cmp(item)
94
+ raise NotImplementedError.new("cmp() must be implemented by subclasses of AbstractVersionedFile.")
95
+ end
96
+
97
+ ##
98
+ # Are two versioned files the same? This means same path and revision indexes.
99
+ #
100
+ # @param [AbstractVersionedFile] vfile what we're being compared to
101
+ # @return [Boolean]
102
+ def ==(vfile)
103
+ raise NotImplementedError.new("==() must be implemented by subclasses of AbstractVersionedFile.")
104
+ end
105
+
106
+ ##
107
+ # Gets the flags for this file ('x', 'l', or '')
108
+ #
109
+ # @return [String] 'x', 'l', or ''
110
+ def flags
111
+ raise NotImplementedError.new("flags() must be implemented by subclasses of AbstractVersionedFile.")
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,185 @@
1
+ ##################################################################
2
+ # Licensing Information #
3
+ # #
4
+ # The following code is licensed, as standalone code, under #
5
+ # the Ruby License, unless otherwise directed within the code. #
6
+ # #
7
+ # For information on the license of this code when distributed #
8
+ # with and used in conjunction with the other modules in the #
9
+ # Amp project, please see the root-level LICENSE file. #
10
+ # #
11
+ # © Michael J. Edgar and Ari Brown, 2009-2010 #
12
+ # #
13
+ ##################################################################
14
+
15
+ module Amp
16
+ module Core
17
+ module Repositories
18
+
19
+ ##
20
+ # = CommonVersionedFileMethods
21
+ #
22
+ # These methods are common to all repositories, and this module is mixed into
23
+ # the AbstractLocalRepository class. This guarantees that all repositories will
24
+ # have these methods.
25
+ #
26
+ # No methods should be placed into this module unless it relies on methods in the
27
+ # general API for repositories.
28
+ module CommonChangesetMethods
29
+
30
+ ##
31
+ # A hash of the files that have been changed and their most recent diffs.
32
+ # The diffs are lazily made upon access. To just get the files, use #altered_files
33
+ # or #changed_files.keys
34
+ # Checks whether this changeset included a given file or not.
35
+ #
36
+ # @return [Hash{String => String}] a hash of {filename => the diff}
37
+ def changed_files
38
+ h = {}
39
+ class << h
40
+ def [](*args)
41
+ super(*args).call # we expect a proc
42
+ end
43
+ end
44
+
45
+ altered_files.inject(h) do |s, k|
46
+ s[k] = proc do
47
+ other = parents.first[k]
48
+ other.unified_diff_with self[k]
49
+ end
50
+ s
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Gets the number of lines added and removed in this changeset.
56
+ #
57
+ # @return [Hash{Symbol => Integer}] a hash returning line-changed
58
+ # statistics. Keys are :added, :deleted
59
+ def changed_lines_statistics
60
+ result = {:added => 0, :removed => 0}
61
+ changed_files.each do |_, diffproc|
62
+ diff = diffproc.call
63
+ diff.split("\n").each do |line|
64
+ if line.start_with?("+") && !line.start_with?("+++")
65
+ result[:added] += 1
66
+ elsif line.start_with?("-") && !line.start_with?("---")
67
+ result[:removed] += 1
68
+ end
69
+ end
70
+ end
71
+ result
72
+ end
73
+
74
+ ##
75
+ # Is +file+ being tracked at this point in time?
76
+ #
77
+ # @param [String] file the file to lookup
78
+ # @return [Boolean] whether the file is in this changeset's manifest
79
+ def include?(file)
80
+ all_files.include? file
81
+ end
82
+
83
+ ##
84
+ # Gets the list of parents useful for printing in a template - no
85
+ # point in saying that revision 127's only parent is 126, for example.
86
+ #
87
+ # @return [Array<Changeset>] a list of changesets that are the parent of
88
+ # this node and happen to be worth noting to the user.
89
+ def useful_parents
90
+ ret_parents = self.parents
91
+ if ret_parents[1].nil?
92
+ if ret_parents[0].revision >= self.revision - 1
93
+ ret_parents = []
94
+ else
95
+ ret_parents = [ret_parents[0]]
96
+ end
97
+ end
98
+ ret_parents
99
+ end
100
+
101
+ ##
102
+ # Renders the changeset with a given template. Takes a few different options
103
+ # to control output.
104
+ #
105
+ # @param [Hash] opts ({}) the options to control rendering to text
106
+ # @option opts [Boolean] :no_output (false) Don't actually return any output
107
+ # @option opts [String] :"template-raw" a bare string to render as a template.
108
+ # Nice option for letting users drop a teensy bit of template code into their
109
+ # commands.
110
+ # @option opts [String, Template] :template ("default-log") The name of the
111
+ # template to use (if a String is provided), or the actual template to use
112
+ # (if a Template object is provided).
113
+ # @return [String] the changeset pumped through the template.
114
+ def to_templated_s(opts={})
115
+ locals = {:change_node => node,
116
+ :revision => self.revision,
117
+ :username => self.user,
118
+ :date => self.easy_date,
119
+ :files => self.altered_files,
120
+ :description => self.description,
121
+ :branch => self.branch,
122
+ :cs_tags => self.tags,
123
+
124
+ :added => opts[:added] || [],
125
+ :removed => opts[:removed] || [],
126
+ :updated => opts[:updated] || [],
127
+ :config => opts,
128
+
129
+ :parents => useful_parents.map {|p| [p.revision, p.node.short_hex] }
130
+ }
131
+
132
+ return "" if opts[:no_output]
133
+
134
+ template_for_options(opts).render(locals, binding)
135
+ end
136
+
137
+ ##
138
+ # Returns template to handle templating based on the options provided.
139
+ #
140
+ # @param [Hash] options a list of options to configure template usage
141
+ # @return [Template] a template to use to print the changeset to a
142
+ # nice string.
143
+ def template_for_options(opts = {})
144
+ type = opts[:template_type] || 'log'
145
+ if opts[:"template-raw"]
146
+ Support::RawERbTemplate.new(opts[:"template-raw"])
147
+ elsif opts[:template].is_a?(Support::Template)
148
+ opts[:template]
149
+ else
150
+ template = opts[:template]
151
+ template = "default-#{type}" if template.nil? || template.to_s == "default"
152
+ Support::Template['mercurial', template]
153
+ end
154
+ end
155
+
156
+ ##
157
+ # recursively walk
158
+ #
159
+ # @param [Amp::Matcher] match this is a custom object that knows files
160
+ # magically. Not your grampa's proc!
161
+ def walk(match)
162
+ # just make it so the keys are there
163
+ results = []
164
+
165
+ hash = Hash.with_keys match.files
166
+ hash.delete '.'
167
+
168
+ each do |file|
169
+ hash.each {|f, val| (hash.delete file and break) if f == file }
170
+
171
+ results << file if match.call file # yield file if match.call file
172
+ end
173
+
174
+ hash.keys.sort.each do |file|
175
+ if match.bad file, "No such file in revision #{revision}" and match[file]
176
+ results << file # yield file
177
+ end
178
+ end
179
+ results
180
+ end
181
+
182
+ end
183
+ end
184
+ end
185
+ end