hglib 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6d1d2540e89d71dd245cd203f0bfac51901c85ecc2e15ad9813566780accb85
4
- data.tar.gz: 921a4beab0aea45f0762a54e010e25d2385534dc10b909a8f0b3d651c1b12e25
3
+ metadata.gz: 609e12ea2bd06f9e02b74b8f9c44502ebfe9bf8eb3d76cde39229719f25296be
4
+ data.tar.gz: e2100c0d719f3ae437e8d44120b881bc561f488b81ba84ba77745a2fde8db020
5
5
  SHA512:
6
- metadata.gz: 0642c4fa43ba0f1dba6962e12ef566c3123079c78db53d3d9ffb4a73e1d9a9504a7f66b3966a622ba205fbb22f41ffbcd25df81044e48f0bf4a8f3b6624f8ec9
7
- data.tar.gz: b9838d26c1c4bc7820554463737eb9d3d57f09c934ff0865f21e92597cba2650e4ae791caadad5011e3d6437f7b79074618dc4986469770a401fc98abd512b16
6
+ metadata.gz: df2e978345babfd706e31e90214042bf79cb4ec0f2a945117e23466f15a56ed6cb2a1fccd3b0cad4e4722a43b6060e15f83cb8e8a2a688855acf9894195ff75d
7
+ data.tar.gz: 9fc6c4485ec905578ae2928a83f61e6e30a9e9ec80ef1a81e4abd1fbd5067d24471cb69c4493a362ba2aad8a22b69465573e835014e1324c976b7891fe212b76
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/History.md CHANGED
@@ -1,3 +1,30 @@
1
+ # Release History for hglib
2
+
3
+ ---
4
+
5
+ ## v0.3.0 [2019-10-02] Michael Granger <ged@FaerieMUD.org>
6
+
7
+ Changes:
8
+
9
+ - Changed Repo#status to return Repo::StatusEntry objects
10
+ - Change Repo#id -> #identity with alias
11
+
12
+ Improvements:
13
+
14
+ - Handle multi-part errors
15
+ - Handle numeric option args
16
+ - Added implementations of:
17
+ - config
18
+ - log
19
+ - tag
20
+ - bookmark
21
+ - diff
22
+ - push
23
+ - paths
24
+ - version
25
+ - sign
26
+
27
+
1
28
  ## v0.2.0 [2019-04-03] Michael Granger <ged@FaerieMUD.org>
2
29
 
3
30
  Improvements:
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # hglib
2
2
 
3
3
  home
4
- : http://bitbucket.org/ged/ruby-hglib
4
+ : https://hg.sr.ht/~ged/hglib
5
5
 
6
6
  code
7
- : http://bitbucket.org/ged/ruby-hglib
7
+ : https://hg.sr.ht/~ged/hglib
8
8
 
9
9
  github
10
10
  : https://github.com/ged/ruby-hglib
@@ -21,6 +21,10 @@ that uses the [Command Server][cmdserver] for efficiency.
21
21
 
22
22
  ### Examples
23
23
 
24
+ require 'hglib'
25
+
26
+ repo = Hglib.clone( 'https://https://hg.sr.ht/~ged/hglib' )
27
+ # => #<Hglib::Repo:0x00007fae3880ec90 @path=#<Pathname:/Users/ged/temp/hglib>, @server=nil>
24
28
 
25
29
 
26
30
  ## Prerequisites
@@ -36,20 +40,24 @@ that uses the [Command Server][cmdserver] for efficiency.
36
40
  ## Contributing
37
41
 
38
42
  You can check out the current development source with Mercurial via its
39
- [project page](http://bitbucket.org/ged/ruby-hglib). Or if you prefer Git, via
43
+ [project page](https://hg.sr.ht/~ged/hglib). Or if you prefer Git, via
40
44
  [its Github mirror](https://github.com/ged/ruby-hglib).
41
45
 
42
46
  After checking out the source, run:
43
47
 
44
- $ rake newb
48
+ $ gem install -Ng
49
+
50
+ This will install any missing dependencies.
51
+
52
+
53
+ ## Authors
45
54
 
46
- This task will install any missing dependencies, run the tests/specs,
47
- and generate the API documentation.
55
+ - Michael Granger <ged@FaerieMUD.org>
48
56
 
49
57
 
50
58
  ## License
51
59
 
52
- Copyright (c) 2018, Michael Granger
60
+ Copyright (c) 2018-2019, Michael Granger
53
61
  All rights reserved.
54
62
 
55
63
  Redistribution and use in source and binary forms, with or without
data/lib/hglib.rb CHANGED
@@ -12,7 +12,7 @@ module Hglib
12
12
  Exception2MessageMapper
13
13
 
14
14
  # Package version
15
- VERSION = '0.2.0'
15
+ VERSION = '0.3.0'
16
16
 
17
17
  # Version control revision
18
18
  REVISION = %q$Revision$
@@ -29,7 +29,51 @@ module Hglib
29
29
 
30
30
  # Base exception class for errors raised by this library
31
31
  def_exception :Error, "hglib error"
32
- def_exception :CommandError, "error in hg command", Hglib::Error
32
+
33
+
34
+ class CommandError < Hglib::Error
35
+
36
+ def initialize( args )
37
+ @command = args.shift
38
+ @messages = args.flatten.map( &:chomp )
39
+ @messages << "error in hg command" if @messages.empty?
40
+ end
41
+
42
+ ##
43
+ # The command that resulted in an error
44
+ attr_reader :command
45
+
46
+ ##
47
+ # The Array of error messages generated by the command
48
+ attr_reader :messages
49
+
50
+
51
+ ### Returns +true+ if the command resulted in more than one error message.
52
+ def multiple?
53
+ return self.messages.length > 1
54
+ end
55
+
56
+
57
+ ### Overridden to for multi-message errors.
58
+ def message
59
+ msg = String.new( encoding: 'utf-8' )
60
+
61
+ msg << "`%s`: " % [ self.command ]
62
+
63
+ if self.multiple?
64
+ self.messages.each do |errmsg|
65
+ msg << "\n" << ' - ' << errmsg
66
+ end
67
+ msg << "\n"
68
+ else
69
+ msg << self.messages.first
70
+ end
71
+
72
+ return msg
73
+ end
74
+
75
+ end # class CommandError
76
+
33
77
 
34
78
  # Loggability API -- set up a Logger for Hglib objects
35
79
  log_as :hglib
@@ -48,6 +92,7 @@ module Hglib
48
92
 
49
93
 
50
94
  # Set up automatic loading of submodules
95
+ autoload :Config, 'hglib/config'
51
96
  autoload :Server, 'hglib/server'
52
97
  autoload :Repo, 'hglib/repo'
53
98
 
@@ -0,0 +1,60 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'loggability'
5
+ require 'hglib' unless defined?( Hglib )
6
+
7
+
8
+ class Hglib::Config
9
+ extend Loggability
10
+ include Enumerable
11
+
12
+
13
+ # Config item type
14
+ Item = Struct.new( :value, :source )
15
+
16
+
17
+ # Log to the Hglib logger
18
+ log_to :hglib
19
+
20
+
21
+ ### Create a new Config object from the given +config_items+.
22
+ def initialize( *config_items )
23
+ @items = self.expand_config_items( config_items )
24
+ end
25
+
26
+
27
+ ######
28
+ public
29
+ ######
30
+
31
+ ##
32
+ # The Hash of Hglib::Config::Items from the config, keyed by name.
33
+ attr_reader :items
34
+
35
+
36
+ ### Fetch the value of the config item +key+.
37
+ def []( key )
38
+ return self.items[ key ]&.value
39
+ end
40
+
41
+
42
+ ### Call the block once for each config item, yielding the key and the Item.
43
+ def each( &block )
44
+ return self.items.each( &block )
45
+ end
46
+
47
+
48
+ ### Expand the Array of configuration +items+ such as that returned by the JSON
49
+ ### template of `hg showconfig` and return a hierarchical Hash.
50
+ def expand_config_items( items )
51
+ return items.flatten.each_with_object( {} ) do |item, hash|
52
+ self.log.debug "Expanding %p" % [ item ]
53
+ hash[ item[:name] ] = Item.new( item[:value], item[:source] )
54
+ end
55
+ end
56
+
57
+
58
+ end # class Hglib::Config
59
+
60
+
@@ -0,0 +1,77 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'hglib' unless defined?( Hglib )
5
+
6
+
7
+ module Hglib
8
+
9
+ # A collection of methods for declaring other methods.
10
+ #
11
+ # class MyClass
12
+ # extend Hglib::MethodUtilities
13
+ #
14
+ # singleton_attr_accessor :types
15
+ # singleton_method_alias :kinds, :types
16
+ # end
17
+ #
18
+ # MyClass.types = [ :pheno, :proto, :stereo ]
19
+ # MyClass.kinds # => [:pheno, :proto, :stereo]
20
+ #
21
+ module MethodUtilities
22
+
23
+ ### Creates instance variables and corresponding methods that return their
24
+ ### values for each of the specified +symbols+ in the singleton of the
25
+ ### declaring object (e.g., class instance variables and methods if declared
26
+ ### in a Class).
27
+ def singleton_attr_reader( *symbols )
28
+ symbols.each do |sym|
29
+ singleton_class.__send__( :attr_reader, sym )
30
+ end
31
+ end
32
+
33
+ ### Creates methods that allow assignment to the attributes of the singleton
34
+ ### of the declaring object that correspond to the specified +symbols+.
35
+ def singleton_attr_writer( *symbols )
36
+ symbols.each do |sym|
37
+ singleton_class.__send__( :attr_writer, sym )
38
+ end
39
+ end
40
+
41
+ ### Creates readers and writers that allow assignment to the attributes of
42
+ ### the singleton of the declaring object that correspond to the specified
43
+ ### +symbols+.
44
+ def singleton_attr_accessor( *symbols )
45
+ symbols.each do |sym|
46
+ singleton_class.__send__( :attr_accessor, sym )
47
+ end
48
+ end
49
+
50
+ ### Creates an alias for the +original+ method named +newname+.
51
+ def singleton_method_alias( newname, original )
52
+ singleton_class.__send__( :alias_method, newname, original )
53
+ end
54
+
55
+
56
+ ### Create a reader in the form of a predicate for the given +attrname+.
57
+ def attr_predicate( attrname )
58
+ attrname = attrname.to_s.chomp( '?' )
59
+ define_method( "#{attrname}?" ) do
60
+ instance_variable_get( "@#{attrname}" ) ? true : false
61
+ end
62
+ end
63
+
64
+
65
+ ### Create a reader in the form of a predicate for the given +attrname+
66
+ ### as well as a regular writer method.
67
+ def attr_predicate_accessor( attrname )
68
+ attrname = attrname.to_s.chomp( '?' )
69
+ attr_writer( attrname )
70
+ attr_predicate( attrname )
71
+ end
72
+
73
+ end # module MethodUtilities
74
+
75
+
76
+ end # module Hglib
77
+
data/lib/hglib/repo.rb CHANGED
@@ -13,8 +13,11 @@ class Hglib::Repo
13
13
  log_to :hglib
14
14
 
15
15
 
16
+ autoload :Bookmark, 'hglib/repo/bookmark'
16
17
  autoload :Id, 'hglib/repo/id'
17
18
  autoload :LogEntry, 'hglib/repo/log_entry'
19
+ autoload :StatusEntry, 'hglib/repo/status_entry'
20
+ autoload :Tag, 'hglib/repo/tag'
18
21
 
19
22
 
20
23
  ### Create a new Repo object that will operate on the Mercurial repo at the
@@ -44,15 +47,10 @@ class Hglib::Repo
44
47
  ### the file. An empty Hash is returned if there are no files with one of the
45
48
  ### requested statuses.
46
49
  def status( *args, **options )
47
- response = self.server.run( :status, *args, **options )
50
+ response = self.server.run_with_json_template( :status, *args, **options )
48
51
  self.logger.debug "Parsing status response: %p" % [ response ]
49
52
 
50
- return {} if response.length == 1 && response.first.empty?
51
- return response.each_slice( 2 ).inject({}) do |hash, (raw_status, path)|
52
- path = Pathname( path.chomp )
53
- hash[ path ] = raw_status.strip
54
- hash
55
- end
53
+ return response.map {|entry| Hglib::Repo::StatusEntry.new(entry) }
56
54
  end
57
55
  alias_method :stat, :status
58
56
 
@@ -60,30 +58,78 @@ class Hglib::Repo
60
58
  ### Return a Hglib::Repo::Id that identifies the repository state at the
61
59
  ### specified +revision+, or the current revision if unspecified. A +revision+
62
60
  ### of `.` identifies the working directory parent without uncommitted changes.
63
- def id( revision=nil )
64
- options = {}
65
- options[:rev] = revision if revision
66
-
67
- response = self.server.run( :id, **options )
61
+ def identify( source=nil, **options )
62
+ response = self.server.run_with_json_template( :identify, source, **options )
68
63
  self.logger.debug "Got ID response: %p" % [ response ]
69
64
 
70
- return Hglib::Repo::Id.parse( response.first )
65
+ data = response.first
66
+ return Hglib::Repo::Id.new( **data )
71
67
  end
68
+ alias_method :id, :identify
72
69
 
73
70
 
74
71
  ### Return an Array of Hglib::Repo::LogEntry objects that describes the revision
75
72
  ### history of the specified +files+ or the entire project.
76
73
  def log( *files, **options )
77
74
  options[:graph] = false
78
- options[:T] = 'json'
79
75
 
80
- jsonlog = self.server.run( :log, *files, **options )
81
- entries = JSON.parse( jsonlog.join )
76
+ entries = self.server.run_with_json_template( :log, *files, **options )
77
+ self.logger.debug "Got log response: %p" % [ entries ]
82
78
 
83
79
  return entries.map {|entry| Hglib::Repo::LogEntry.new(entry) }
84
80
  end
85
81
 
86
82
 
83
+ ### Return a String showing differences between revisions for the specified
84
+ ### +files+ in the unified diff format.
85
+ def diff( *files, **options )
86
+ response = self.server.run( :diff, *files, **options )
87
+ self.logger.debug "Got diff response: %p" % [ truncate(response) ]
88
+ return response
89
+ end
90
+
91
+
92
+ ### Schedule the given +files+ to be version controlled and added to the
93
+ ### repository on the next commit. To undo an add before that, see #forget.
94
+ ###
95
+ ### If no +files+ are given, add all files to the repository (except files
96
+ ### matching ".hgignore").
97
+ ###
98
+ ### Returns <code>true</code> if all files are successfully added.
99
+ def add( *files, **options )
100
+ response = self.server.run( :add, *files, **options )
101
+ self.logger.debug "Got ADD response: %p" % [ response ]
102
+
103
+ return true
104
+ end
105
+
106
+
107
+ ### Add all new files and remove all missing files from the repository.
108
+ ###
109
+ ### Unless +files+ are given, new files are ignored if they match any of the
110
+ ### patterns in ".hgignore". As with #add, these changes take effect at the
111
+ ### next commit.
112
+ ###
113
+ ### Use the :similarity option to detect renamed files. This option takes a
114
+ ### percentage between 0 (disabled) and 100 (files must be identical) as its
115
+ ### value. With a value greater than 0, this compares every removed file with
116
+ ### every added file and records those similar enough as renames. Detecting
117
+ ### renamed files this way can be expensive. After using this option, you can
118
+ ### call #status with the <code>:copies</code> options to check which files
119
+ ### were identified as moved or renamed. If not specified, :similarity defaults
120
+ ### to 100 and only renames of identical files are detected.
121
+ ###
122
+ ### Returns <code>true</code> if all files are successfully added.
123
+ def addremove( *files, **options )
124
+ response = self.server.run( :addremove, *files, **options )
125
+ self.logger.debug "Got ADD response: %p" % [ response ]
126
+
127
+ return true
128
+ end
129
+ alias_method :add_remove, :addremove
130
+ alias_method :addr, :addremove
131
+
132
+
87
133
  ### Commit the specified +files+ with the given +options+.
88
134
  def commit( *files, **options )
89
135
  response = self.server.run( :commit, *files, **options )
@@ -111,6 +157,119 @@ class Hglib::Repo
111
157
  end
112
158
 
113
159
 
160
+ ### Update the working directory or switch revisions.
161
+ def update( rev=nil, **options )
162
+ response = self.server.run( :update, rev, **options )
163
+ self.logger.debug "Got UPDATE response: %p" % [ response ]
164
+
165
+ return true
166
+ end
167
+
168
+
169
+ ### Push changes to the specified +destination+.
170
+ def push( destination=nil, **options )
171
+ response = self.server.run( :push, destination, **options )
172
+ self.logger.debug "Got PUSH response: %p" % [ response ]
173
+
174
+ return true
175
+ end
176
+
177
+
178
+ ### Name a revision using +names+.
179
+ def tag( *names, **options )
180
+ raise "expected at least one tag name" if names.empty?
181
+
182
+ response = self.server.run( :tag, *names, **options )
183
+ self.logger.debug "Got TAGS response: %p" % [ response ]
184
+
185
+ return true
186
+ end
187
+
188
+
189
+ ### Return a Hglib::Repo::Tag object for each tag in the repo.
190
+ def tags
191
+ response = self.server.run_with_json_template( :tags )
192
+ self.logger.debug "Got a TAGS response: %p" % [ response ]
193
+
194
+ return response.flatten.map {|tag| Hglib::Repo::Tag.new(self, **tag) }
195
+ end
196
+
197
+
198
+ ### Create new bookmarks with the specified +names+.
199
+ def bookmark( *names, **options )
200
+ raise "expected at least one bookmark name" if names.empty?
201
+
202
+ response = self.server.run( :bookmark, *names, **options )
203
+ self.logger.debug "Got BOOKMARK response: %p" % [ response ]
204
+
205
+ return true
206
+ end
207
+
208
+
209
+ ### Return a Hglib::Repo::Bookmark object for each bookmark in the repo.
210
+ def bookmarks
211
+ options = { list: true }
212
+ response = self.server.run_with_json_template( :bookmarks, **options )
213
+ self.logger.debug "Got a BOOKMARKS response: %p" % [ response ]
214
+
215
+ return response.map {|bk| Hglib::Repo::Bookmark.new(self, **bk) }
216
+ end
217
+
218
+
219
+ ### Fetch the current global Mercurial config and return it as an Hglib::Config
220
+ ### object.
221
+ def config( untrusted: false )
222
+ options = { untrusted: untrusted }
223
+
224
+ config = self.server.run_with_json_template( :showconfig, **options )
225
+ return Hglib::Config.new( config )
226
+ end
227
+
228
+
229
+ ### Fetch a Hash of aliases for remote repositories.
230
+ def paths
231
+ response = self.server.run_with_json_template( :paths )
232
+ self.logger.debug "Got a PATHS response: %p" % [ response ]
233
+
234
+ return response.each_with_object({}) do |entry, hash|
235
+ hash[ entry[:name].to_sym ] = URI( entry[:url] )
236
+ end
237
+ end
238
+
239
+
240
+ ### Sign the given +rev+ (or the current one if not specified).
241
+ def sign( rev=nil, **options )
242
+ response = self.server.run( :sign, rev, **options )
243
+ self.logger.debug "Got a SIGN response: %p" % [ response ]
244
+
245
+ return response.chomp
246
+ end
247
+
248
+
249
+ ### Fetch a Hash of version information about the Mercurial that is being used.
250
+ def versions
251
+ response = self.server.run_with_json_template( :version )
252
+ self.logger.debug "Got a VERSION response: %p" % [ response ]
253
+
254
+ return response.first
255
+ end
256
+
257
+
258
+ ### Fetch the version of Mercurial that's being used as a String.
259
+ def version
260
+ return self.versions[ :ver ]
261
+ end
262
+
263
+
264
+ ### Fetch the version of the Mercurial extensions that're being used as a Hash.
265
+ def extension_versions
266
+ ext_info = self.versions[ :extensions ]
267
+ return ext_info.each_with_object({}) do |ext, hash|
268
+ hash[ ext.delete(:name).to_sym ] = ext
269
+ end
270
+ end
271
+
272
+
114
273
  #########
115
274
  protected
116
275
  #########
@@ -126,4 +285,16 @@ class Hglib::Repo
126
285
  return Loggability[ self ]
127
286
  end
128
287
 
288
+
289
+ ### Return the given +string+ with the middle truncated so that it's +maxlength+
290
+ ### characters long if it exceeds that length.
291
+ def truncate( string, maxlength=80 )
292
+ return string if maxlength < 8
293
+ return string if string.length - maxlength - 5 <= 0
294
+
295
+ return string[0 .. (maxlength / 2) - 3 ] +
296
+ ' ... ' +
297
+ string[ -((maxlength / 2) -3) .. ]
298
+ end
299
+
129
300
  end # class Hglib::Repo