line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
## Config::Versioned |
2
|
|
|
|
|
|
|
## |
3
|
|
|
|
|
|
|
## Written 2011-2012 by Scott T. Hardin for the OpenXPKI project |
4
|
|
|
|
|
|
|
## Copyright (C) 2010-2012 by The OpenXPKI Project |
5
|
|
|
|
|
|
|
## |
6
|
|
|
|
|
|
|
## Was based on the CPAN module App::Options, but the import() stuff |
7
|
|
|
|
|
|
|
## bit me so we're turning into a Moose. |
8
|
|
|
|
|
|
|
## |
9
|
|
|
|
|
|
|
## vim: syntax=perl |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package Config::Versioned; |
12
|
|
|
|
|
|
|
|
13
|
5
|
|
|
5
|
|
801917
|
use Moose; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use namespace::autoclean; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 NAME |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
Config::Versioned - Simple, versioned access to configuration data |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=cut |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
our $VERSION = '1.01'; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
use Carp; |
25
|
|
|
|
|
|
|
use Config::Std; |
26
|
|
|
|
|
|
|
use Data::Dumper; |
27
|
|
|
|
|
|
|
use DateTime; |
28
|
|
|
|
|
|
|
use Git::PurePerl; |
29
|
|
|
|
|
|
|
use Path::Class; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
has 'path' => ( is => 'ro', isa => 'ArrayRef', default => sub { [qw( . )] } ); |
32
|
|
|
|
|
|
|
has 'filename' => ( is => 'ro', isa => 'Str' ); |
33
|
|
|
|
|
|
|
has 'dbpath' => |
34
|
|
|
|
|
|
|
( is => 'ro', default => 'cfgver.git', required => 1 ); |
35
|
|
|
|
|
|
|
has 'author_name' => ( is => 'ro', isa => 'Str', default => "process: $@" ); |
36
|
|
|
|
|
|
|
has 'author_mail' => ( |
37
|
|
|
|
|
|
|
is => 'ro', |
38
|
|
|
|
|
|
|
isa => 'Str', |
39
|
|
|
|
|
|
|
default => $ENV{GIT_AUTHOR_EMAIL} || $ENV{USER} . '@localhost' |
40
|
|
|
|
|
|
|
); |
41
|
|
|
|
|
|
|
has 'autocreate' => ( is => 'ro', isa => 'Bool', default => 0 ); |
42
|
|
|
|
|
|
|
has 'commit_time' => ( is => 'ro', isa => 'DateTime' ); |
43
|
|
|
|
|
|
|
has 'comment' => ( is => 'rw', isa => 'Str' ); |
44
|
|
|
|
|
|
|
has 'delimiter' => ( is => 'ro', isa => 'Str', default => '.' ); |
45
|
|
|
|
|
|
|
has 'delimiter_regex' => |
46
|
|
|
|
|
|
|
( is => 'ro', isa => 'RegexpRef', default => sub { qr{ \. }xms } ); |
47
|
|
|
|
|
|
|
has 'log_get_callback' => ( is => 'ro' ); |
48
|
|
|
|
|
|
|
has '_git' => ( is => 'rw' ); |
49
|
|
|
|
|
|
|
has 'debug' => ( is => 'rw', isa => 'Int', default => 0 ); |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# a reference to the singleton Config::Versioned object that parsed the command line |
52
|
|
|
|
|
|
|
#my ($default_option_processor); |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
#my (%path_is_secure); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head1 SYNOPSIS |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
use Config::Versioned; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
my $cfg = Config::Versioned->new(); |
61
|
|
|
|
|
|
|
my $param1 = $cfg->get('subsystem1.group.param1'); |
62
|
|
|
|
|
|
|
my $old1 = $cfg->get('subsystem1.group.param1', $version); |
63
|
|
|
|
|
|
|
my @keys = $cfg->list('subsys1.db'); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=head1 DESCRIPTION |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
Config::Versioned allows an application to access configuration parameters |
69
|
|
|
|
|
|
|
not only by parameter name, but also by version number. This allows for |
70
|
|
|
|
|
|
|
the configuration subsystem to store previous versions of the configuration |
71
|
|
|
|
|
|
|
parameters. When requesting the value for a specific attribute, the programmer |
72
|
|
|
|
|
|
|
specifies whether to fetch the most recent value or a previous value. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
This is useful for long-running tasks such as in a workflow-based application |
75
|
|
|
|
|
|
|
where task-specific values (e.g.: profiles) are static over the life of a |
76
|
|
|
|
|
|
|
workflow, while global values (e.g.: name of an LDAP server to be queried) |
77
|
|
|
|
|
|
|
should always be the most recent. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Config::Versioned handles the versions by storing the configuration data |
80
|
|
|
|
|
|
|
in an internal Git repository. Each import of configuration files into |
81
|
|
|
|
|
|
|
the repository is documented with a commit. When a value is fetched, it is |
82
|
|
|
|
|
|
|
this commit that is referenced directly when specifying the version. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
The access to the individual attributes is via a named-parameter scheme, where |
85
|
|
|
|
|
|
|
the key is a dot-separated string. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Currently, C<Config::Std> is used for the import of the data files into the |
88
|
|
|
|
|
|
|
internal Git repository. Support for other configuration modules (e.g.: |
89
|
|
|
|
|
|
|
C<Config::Any>) is planned. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=head1 METHODS |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=head2 init() |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
This is invoked automatically via import(). It is called when running the |
96
|
|
|
|
|
|
|
following code: |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
use Config::Versioned; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
The init() method reads the configuration data from the configuration files |
101
|
|
|
|
|
|
|
and populates an internal data structure. |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Optionally, parameters may be passed to init(). The following |
104
|
|
|
|
|
|
|
named-parameters are supported: |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=over 8 |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=item path |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Specifies an anonymous array contianing the names of the directories to |
111
|
|
|
|
|
|
|
check for the configuration files. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
path => qw( /etc/yourapp/etc /etc/yourapp/local/etc . ), |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
The default path is just the current directory. |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=item filename |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Specifies the name of the configuration file to be found in the given path. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
filename => qw( yourapp.conf ), |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
If no filename is given, no new configuration data will be imported and |
124
|
|
|
|
|
|
|
the internal git repository will be used. |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=item dbpath |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
The directory for the internal git repository that stores the config. |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
dbpath => qw( config.git ), |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
The default is "cfgver.git". |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item author_name, author_mail |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
The name and e-mail address to use in the internal git repository for |
137
|
|
|
|
|
|
|
commits. |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=item autocreate |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
If no internal git repository exists, it will be created during code |
142
|
|
|
|
|
|
|
initialization. Note that if an import filename is specified, this |
143
|
|
|
|
|
|
|
automatically sets autocreate to true. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
autocreate => 1, |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
The default is "0". |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Note: this option might become deprecated. I just wanted some extra |
150
|
|
|
|
|
|
|
"insurance" during the early stages of development. |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=item commit_time |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
This sets the time to use for the commits in the internal git repository. |
155
|
|
|
|
|
|
|
It is used for debugging purposes only! |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
Note: this must be a DateTime object instance. |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=item delimiter |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
Specifies the delimiter used to separate the different levels in the |
162
|
|
|
|
|
|
|
string used to designate the location of a configuration parameter. [Default: '.'] |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=item delimiter_regex |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
Specifies the delimiter used to separate the different levels in the |
167
|
|
|
|
|
|
|
string used to designate the location of a configuration parameter. |
168
|
|
|
|
|
|
|
[Default: qr/ \. /xms] |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=item log_get_callback |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Specifies a callback function to be called by get() after fetching |
173
|
|
|
|
|
|
|
the value for the given key. The subroutine should accept the |
174
|
|
|
|
|
|
|
parameters LOCATION, VERSION, VALUE. The VALUE may either be a single |
175
|
|
|
|
|
|
|
scalar value or an array reference containing a list of values. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
sub cb_log_get { |
178
|
|
|
|
|
|
|
my $self = shift; |
179
|
|
|
|
|
|
|
my $loc = shift; |
180
|
|
|
|
|
|
|
my $ver = shift; |
181
|
|
|
|
|
|
|
my $val = shift; |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
warn "Access config parameter: $loc ($ver) => ", |
184
|
|
|
|
|
|
|
ref($val) eq 'ARRAY' |
185
|
|
|
|
|
|
|
? join(', ', @{ $val }) |
186
|
|
|
|
|
|
|
: $val, |
187
|
|
|
|
|
|
|
"\n"; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
my $cfg = Config::Versioned->new( { log_get_callback => 'cb_log_get' } ); |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
Note: if log_get_callback is a code ref, it will be called as a function. |
192
|
|
|
|
|
|
|
Otherwise, the log_get_callback will specify a method name that is to be |
193
|
|
|
|
|
|
|
called on the current object instance. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=back |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head2 BUILD( { PARAMS } ) |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
NOTE: This is used internally, so the typical user shouldn't bother with this. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
This is called after an object is created. When cloning, it is important that |
202
|
|
|
|
|
|
|
the new instance gets a reference to the same Git::PurePerl instance. This |
203
|
|
|
|
|
|
|
will prevent two instances from getting out of sync if modifications are made |
204
|
|
|
|
|
|
|
to the configuration data at runtime. To handle this, the parameter 'GITREF' |
205
|
|
|
|
|
|
|
must be passed when cloning. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Note 2: this should be handled automatically in the _near_ future. |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
my $cv2 = $cv1->new( GITREF => $cv1->_git() ); |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=cut |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub BUILD { |
214
|
|
|
|
|
|
|
my $self = shift; |
215
|
|
|
|
|
|
|
my $args = shift; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
if ( defined $ENV{CONFIG_VERSIONED_DEBUG} ) { |
218
|
|
|
|
|
|
|
$self->debug( $ENV{CONFIG_VERSIONED_DEBUG} ); |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
if ( not $self->_init_repo() ) { |
222
|
|
|
|
|
|
|
return; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
# if ( not $self->_git() ) { |
225
|
|
|
|
|
|
|
# if ( $args->{GITREF} ) { |
226
|
|
|
|
|
|
|
# $self->_git( $args->{GITREF} ); |
227
|
|
|
|
|
|
|
# } |
228
|
|
|
|
|
|
|
# else { |
229
|
|
|
|
|
|
|
# if ( not $self->_init_repo() ) { |
230
|
|
|
|
|
|
|
# return; |
231
|
|
|
|
|
|
|
# } |
232
|
|
|
|
|
|
|
# } |
233
|
|
|
|
|
|
|
# } |
234
|
|
|
|
|
|
|
# |
235
|
|
|
|
|
|
|
# $self->parser($args); |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
return ($self); |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=head2 get( LOCATION [, VERSION ] ) |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
This is the accessor for fetching the value(s) of the given parameter. The |
243
|
|
|
|
|
|
|
value may either be zero or more elements. |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
In list context, the values are returned. In scalar context, C<undef> is |
246
|
|
|
|
|
|
|
returned if the variable is empty. Otherwise, the first element is returned. |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
Optionally, a VERSION may be specified to return the value for that |
249
|
|
|
|
|
|
|
specific version. |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=cut |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub get { |
254
|
|
|
|
|
|
|
my $self = shift; |
255
|
|
|
|
|
|
|
my $location = shift; |
256
|
|
|
|
|
|
|
my $version = shift; |
257
|
|
|
|
|
|
|
my $cb = $self->log_get_callback(); |
258
|
|
|
|
|
|
|
my ( $obj, $deobj ) = $self->_findobjx( $location, $version ); |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
if ( not defined $obj ) { |
261
|
|
|
|
|
|
|
$self->$cb( $location, $version, '<undefined>' ) if $cb; |
262
|
|
|
|
|
|
|
return; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
if ( $obj->kind eq 'blob' ) { |
266
|
|
|
|
|
|
|
$self->$cb( $location, $version, $obj->content ) if $cb; |
267
|
|
|
|
|
|
|
if ( $deobj->mode() == 120000 ) { |
268
|
|
|
|
|
|
|
my $tmp = $obj->content; |
269
|
|
|
|
|
|
|
return \$tmp; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
else { |
272
|
|
|
|
|
|
|
return $obj->content; |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
elsif ( $obj->kind eq 'tree' ) { |
276
|
|
|
|
|
|
|
my @entries = $obj->directory_entries; |
277
|
|
|
|
|
|
|
my @ret = (); |
278
|
|
|
|
|
|
|
foreach my $de (@entries) { |
279
|
|
|
|
|
|
|
push @ret, $de->filename; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
my @sorted = |
282
|
|
|
|
|
|
|
sort { ( $a =~ /^\d+$/ and $b =~ /^\d+$/ ) ? $a <=> $b : $a cmp $b } |
283
|
|
|
|
|
|
|
@ret; |
284
|
|
|
|
|
|
|
$self->$cb( $location, $version, \@sorted ) if $cb; |
285
|
|
|
|
|
|
|
return @sorted; |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
else { |
288
|
|
|
|
|
|
|
$self->$cb( $location, $version, |
289
|
|
|
|
|
|
|
"<error: non-blob object '" . $obj->kind . "' not supported>" ) |
290
|
|
|
|
|
|
|
if $cb; |
291
|
|
|
|
|
|
|
warn "# DEBUG: get() was asked to return a non-blob object [kind=", |
292
|
|
|
|
|
|
|
$obj->kind, "]\n" if $self->debug(); |
293
|
|
|
|
|
|
|
return; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=head2 kind ( LOCATION [, VERSION ] ) |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
The get() method tries to return a scalar when the location corresponds |
300
|
|
|
|
|
|
|
to a single value and a list when the location has child nodes. Sometimes, |
301
|
|
|
|
|
|
|
however, it is helpful to have a definitive answer on what a location |
302
|
|
|
|
|
|
|
contains. |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
The kind() method returns the object type that the given location accesses. |
305
|
|
|
|
|
|
|
This can be one of the following values: |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=over |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=item tree |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
The given location contains a tree object containing zero or more child |
312
|
|
|
|
|
|
|
objects. The get() method will return a list of the entry names. |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=item blob |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
The data node that usually contains a scalar value, but in future implementations |
317
|
|
|
|
|
|
|
may contain other encoded data. |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=back |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
B<Note:> As a side-effect, this can be used to test whether the given location |
322
|
|
|
|
|
|
|
exists at all in the configuration. If not found, C<undef> is returned. |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=cut |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
sub kind { |
327
|
|
|
|
|
|
|
my $self = shift; |
328
|
|
|
|
|
|
|
my $location = shift; |
329
|
|
|
|
|
|
|
my $version = shift; |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
my $obj = $self->_findobj( $location, $version ); |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
if ( not defined $obj ) { |
334
|
|
|
|
|
|
|
return; # if nothing found, just return undef |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
if ( $obj->kind eq 'blob' ) { |
338
|
|
|
|
|
|
|
return 'blob'; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
elsif ( $obj->kind eq 'tree' ) { |
341
|
|
|
|
|
|
|
return 'tree'; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
else { |
344
|
|
|
|
|
|
|
$@ = "Internal object error (expected tree or blob): [gpp kind=" |
345
|
|
|
|
|
|
|
. $obj->kind . "]\n"; |
346
|
|
|
|
|
|
|
warn "# DEBUG: " . $@ if $self->debug(); |
347
|
|
|
|
|
|
|
return; |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=head2 listattr( LOCATION [, VERSION ] ) |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
This fetches a list of the parameters available for a given location in the |
355
|
|
|
|
|
|
|
configuration tree. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=cut |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub listattr { |
360
|
|
|
|
|
|
|
my $self = shift; |
361
|
|
|
|
|
|
|
my $location = shift; |
362
|
|
|
|
|
|
|
my $version = shift; |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
my $obj = $self->_findobj( $location, $version ); |
365
|
|
|
|
|
|
|
if ( $obj and $obj->kind eq 'tree' ) { |
366
|
|
|
|
|
|
|
my @entries = $obj->directory_entries; |
367
|
|
|
|
|
|
|
my @ret = (); |
368
|
|
|
|
|
|
|
foreach my $de (@entries) { |
369
|
|
|
|
|
|
|
push @ret, $de->filename; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
return @ret; |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
else { |
374
|
|
|
|
|
|
|
$@ = "obj at $location not found"; |
375
|
|
|
|
|
|
|
return; |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=head2 dumptree( [ VERSION ] ) |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
This fetches the entire tree for the given version (default: newest version) |
382
|
|
|
|
|
|
|
and returns a hashref to a named-parameter list. |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=cut |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub dumptree { |
387
|
|
|
|
|
|
|
my $self = shift; |
388
|
|
|
|
|
|
|
my $version = shift; |
389
|
|
|
|
|
|
|
my $cfg = $self->_git(); |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# If no version hash was given, default to the HEAD of master |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
if ( not $version ) { |
394
|
|
|
|
|
|
|
my $master = $self->_git()->ref('refs/heads/master'); |
395
|
|
|
|
|
|
|
if ( $master ) { |
396
|
|
|
|
|
|
|
$version = $master->sha1; |
397
|
|
|
|
|
|
|
} else { |
398
|
|
|
|
|
|
|
# if no sha1s are in repo, there's nothing to return |
399
|
|
|
|
|
|
|
return; |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
my $obj = $cfg->get_object($version); |
404
|
|
|
|
|
|
|
if ( not $obj ) { |
405
|
|
|
|
|
|
|
$@ = "No object found for SHA1 " . $version ? $version : ''; |
406
|
|
|
|
|
|
|
return; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
if ( $obj->kind eq 'commit' ) { |
410
|
|
|
|
|
|
|
$obj = $obj->tree; |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
my $ret = {}; |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
my @directory_entries = $obj->directory_entries; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
foreach my $de (@directory_entries) { |
418
|
|
|
|
|
|
|
my $child = $cfg->get_object( $de->sha1 ); |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# warn "DEBUG: dump - child name = ", $de->filename, "\n"; |
421
|
|
|
|
|
|
|
# warn "DEBUG: dump - child kind = ", $child->kind, "\n"; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
if ( $child->kind eq 'tree' ) { |
424
|
|
|
|
|
|
|
my $subret = $self->dumptree( $de->sha1 ); |
425
|
|
|
|
|
|
|
foreach my $key ( keys %{$subret} ) { |
426
|
|
|
|
|
|
|
$ret->{ $de->filename . $self->delimiter() . $key } = |
427
|
|
|
|
|
|
|
$subret->{$key}; |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
elsif ( $child->kind eq 'blob' ) { |
431
|
|
|
|
|
|
|
$ret->{ $de->filename } = $child->content; |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
else { |
434
|
|
|
|
|
|
|
die "ERROR: unexpected kind: ", $child->kind, "\n"; |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
return $ret; |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=head2 version |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
This returns the current version of the configuration database, which |
444
|
|
|
|
|
|
|
happens to be the SHA1 hash of the HEAD of the internal git repository. |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
Optionally, a version hash may be passed and version() will return a true |
447
|
|
|
|
|
|
|
value if it is found. |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=cut |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
sub version { |
452
|
|
|
|
|
|
|
my $self = shift; |
453
|
|
|
|
|
|
|
my $version = shift; |
454
|
|
|
|
|
|
|
my $cfg = $self->_git(); |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
if ($version) { |
457
|
|
|
|
|
|
|
my $obj = $cfg->get_object($version); |
458
|
|
|
|
|
|
|
if ( $obj and $obj->sha1 eq $version ) { |
459
|
|
|
|
|
|
|
return $version; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
else { |
462
|
|
|
|
|
|
|
return; |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
else { |
466
|
|
|
|
|
|
|
my $head = $cfg->head; |
467
|
|
|
|
|
|
|
return $head->sha1; |
468
|
|
|
|
|
|
|
} |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=head1 INTERNALS |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
=head2 _init_repo |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
Initializes the internal git repository used for storing the config |
476
|
|
|
|
|
|
|
values. |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
If the I<objects> directory in the C<dbpath> does not exist, an |
479
|
|
|
|
|
|
|
C<init()> on the C<Git::PurePerl> class is run. Otherwise, the |
480
|
|
|
|
|
|
|
instance is initialized using the existing bare repository. |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
On error, it returns C<undef> and the reason is in C<$@>. |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=cut |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
sub _init_repo { |
487
|
|
|
|
|
|
|
my $self = shift; |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
my $git; |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
# if ( not $init_args->{dbpath} ) { |
492
|
|
|
|
|
|
|
# die "ERROR: dbpath not set"; |
493
|
|
|
|
|
|
|
# } |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
if ( not -d $self->dbpath() . '/objects' ) { |
496
|
|
|
|
|
|
|
if ( $self->filename() || $self->autocreate() ) { |
497
|
|
|
|
|
|
|
if ( not -d $self->dbpath() ) { |
498
|
|
|
|
|
|
|
if ( not dir( $self->dbpath() )->mkpath ) { |
499
|
|
|
|
|
|
|
die 'Error creating directory ' . $self->dbpath() . ': ' . $!; |
500
|
|
|
|
|
|
|
} |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
$git = Git::PurePerl->init( gitdir => $self->dbpath() ); |
503
|
|
|
|
|
|
|
} else { |
504
|
|
|
|
|
|
|
die 'Error: dbpath (' . $self->dbpath() . ') does not exist'; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
else { |
508
|
|
|
|
|
|
|
$git = Git::PurePerl->new( gitdir => $self->dbpath() ); |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
$self->_git($git); |
511
|
|
|
|
|
|
|
$self->parser(); |
512
|
|
|
|
|
|
|
return $self; |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=head2 _get_anon_scalar |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
Creates an anonymous scalar for representing symlinks in the tree structure. |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
=cut |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
sub _get_anon_scalar { |
522
|
|
|
|
|
|
|
my $temp = shift; |
523
|
|
|
|
|
|
|
return \$temp; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
=head2 parser ARGS |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
Imports the configuration read and writes it to the internal database. If no |
529
|
|
|
|
|
|
|
filename is passed as an argument, then it will quietly skip the commit. |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Note: if you override this method in a child class, it must create an |
532
|
|
|
|
|
|
|
anonymous hash tree and pass the reference to the commit() method. Here |
533
|
|
|
|
|
|
|
is a simple example: |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
sub parser { |
536
|
|
|
|
|
|
|
my $self = shift; |
537
|
|
|
|
|
|
|
my $args = shift; |
538
|
|
|
|
|
|
|
$args->{comment} = 'import from my perl hash'; |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
my $cfg = { |
541
|
|
|
|
|
|
|
group1 => { |
542
|
|
|
|
|
|
|
subgroup1 => { |
543
|
|
|
|
|
|
|
param1 => 'val1', |
544
|
|
|
|
|
|
|
param2 => 'val2', |
545
|
|
|
|
|
|
|
}, |
546
|
|
|
|
|
|
|
}, |
547
|
|
|
|
|
|
|
group2 => { |
548
|
|
|
|
|
|
|
subgroup1 => { |
549
|
|
|
|
|
|
|
param3 => 'val3', |
550
|
|
|
|
|
|
|
param4 => 'val4', |
551
|
|
|
|
|
|
|
}, |
552
|
|
|
|
|
|
|
}, |
553
|
|
|
|
|
|
|
# This creates a symlink from 'group3.subgroup3' to 'connector1/group4'. |
554
|
|
|
|
|
|
|
# Note the use of the scalar reference using the backslash. |
555
|
|
|
|
|
|
|
group3 => { |
556
|
|
|
|
|
|
|
subgroup3 => \'connector1/group4', |
557
|
|
|
|
|
|
|
}, |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
}; |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
# pass original args, appended with a comment string for the commit |
562
|
|
|
|
|
|
|
$self->commit( $cfg, $args ); |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
In the comment, you should include details on where the config came from |
566
|
|
|
|
|
|
|
(i.e.: the filename or directory). |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
=cut |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
sub parser { |
571
|
|
|
|
|
|
|
my $self = shift; |
572
|
|
|
|
|
|
|
my $args = shift; |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
foreach |
575
|
|
|
|
|
|
|
my $key (qw( comment filename path author_name author_mail commit_time )) |
576
|
|
|
|
|
|
|
{ |
577
|
|
|
|
|
|
|
if ( not exists $args->{$key} ) { |
578
|
|
|
|
|
|
|
$args->{$key} = $self->$key(); |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
# If no filename was specified, then there is no import of |
583
|
|
|
|
|
|
|
# configuration files needed. Quietly exit method. |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
if ( not $args->{filename} ) { |
586
|
|
|
|
|
|
|
return $self; |
587
|
|
|
|
|
|
|
} |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
# Read the configuration from the import files |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
my %cfg = (); |
592
|
|
|
|
|
|
|
$self->_read_config_path( $args->{filename}, \%cfg, @{ $args->{path} } ); |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
$args->{comment} ||= "Import config from " |
595
|
|
|
|
|
|
|
. $self->_which( $args->{filename}, @{ $args->{path} } ); |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
# convert the foreign data structure to a simple hash tree, |
598
|
|
|
|
|
|
|
# where the value is either a scalar or a hash reference. |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
my $tmphash = {}; |
601
|
|
|
|
|
|
|
foreach my $sect ( keys %cfg ) { |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
# build up the underlying branch for these leaves |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
my @sectpath = split( $self->delimiter_regex(), $sect ); |
606
|
|
|
|
|
|
|
my $sectref = $tmphash; |
607
|
|
|
|
|
|
|
foreach my $nodename (@sectpath) { |
608
|
|
|
|
|
|
|
$sectref->{$nodename} ||= {}; |
609
|
|
|
|
|
|
|
$sectref = $sectref->{$nodename}; |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
# now add the leaves |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
foreach my $leaf ( keys %{ $cfg{$sect} } ) { |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
# If the leaf start or ends with an '@', treat it as |
617
|
|
|
|
|
|
|
# a symbolic link. |
618
|
|
|
|
|
|
|
if ( $leaf =~ |
619
|
|
|
|
|
|
|
m{ (?: \A @ (.*?) @ \z | \A @ (.*) | (.*?) @ \z ) }xms ) |
620
|
|
|
|
|
|
|
{ |
621
|
|
|
|
|
|
|
my $match = $1 || $2 || $3; |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
# make it a ref to an anonymous scalar so we know it's a symlink |
624
|
|
|
|
|
|
|
#my $t = _get_anon_scalar($1); |
625
|
|
|
|
|
|
|
$sectref->{$match} = \( $cfg{$sect}{$leaf} ); |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
else { |
628
|
|
|
|
|
|
|
$sectref->{$leaf} = $cfg{$sect}{$leaf}; |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
$self->commit( $tmphash, $args ); |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
=head2 commit CFGHASH[, ARGS] |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
Import the configuration tree in the CFGHASH anonymous hash and commit |
640
|
|
|
|
|
|
|
the modifications to the internal git bare repository. |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
ARGS is a ref to a named-parameter list (e.g. HASH) that may contain the |
643
|
|
|
|
|
|
|
following keys to override the instance defaults: |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
author_name, author_mail, comment, commit_time |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
=cut |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
sub commit { |
650
|
|
|
|
|
|
|
my $self = shift; |
651
|
|
|
|
|
|
|
my $hash = shift; |
652
|
|
|
|
|
|
|
my $args = shift; |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
if ( ref($hash) ne 'HASH' ) { |
655
|
|
|
|
|
|
|
confess "ERR: commit() - arg not hash ref [$hash]"; |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
my $parent = undef; |
659
|
|
|
|
|
|
|
my $master = undef; |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
$master = $self->_git()->ref('refs/heads/master'); |
662
|
|
|
|
|
|
|
if ( $master ) { |
663
|
|
|
|
|
|
|
$parent = $master->sha1; |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
# warn "# author_name: ", $self->author_name(), "\n"; |
667
|
|
|
|
|
|
|
my $tree = $self->_hash2tree($hash); |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
if ( $self->debug() ) { |
670
|
|
|
|
|
|
|
print join( "\n# ", '', $self->_debugtree($tree) ), "\n"; |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
# |
674
|
|
|
|
|
|
|
# Now that we have a "staging" tree, compare its hash with |
675
|
|
|
|
|
|
|
# that of the current top-level tree. If they are the same, |
676
|
|
|
|
|
|
|
# there were no changes made to the config and we should |
677
|
|
|
|
|
|
|
# not create a commit object |
678
|
|
|
|
|
|
|
# |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
if ( $parent and $master->tree->sha1 eq $tree->sha1 ) { |
681
|
|
|
|
|
|
|
if ( $self->debug() ) { |
682
|
|
|
|
|
|
|
carp("Nothing to commit (index matches HEAD)"); |
683
|
|
|
|
|
|
|
} |
684
|
|
|
|
|
|
|
return $self; |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
# |
688
|
|
|
|
|
|
|
# Prepare and execute the commit |
689
|
|
|
|
|
|
|
# |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
my $actor = Git::PurePerl::Actor->new( |
692
|
|
|
|
|
|
|
name => $args->{author_name} || $self->author_name, |
693
|
|
|
|
|
|
|
email => $args->{author_mail} || $self->author_mail, |
694
|
|
|
|
|
|
|
); |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
my $time = $args->{commit_time} || $self->commit_time || DateTime->now; |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
my @commit_attrs = ( |
699
|
|
|
|
|
|
|
tree => $tree->sha1, |
700
|
|
|
|
|
|
|
author => $actor, |
701
|
|
|
|
|
|
|
authored_time => $time, |
702
|
|
|
|
|
|
|
committer => $actor, |
703
|
|
|
|
|
|
|
committed_time => $time, |
704
|
|
|
|
|
|
|
comment => $args->{comment} || $self->comment(), |
705
|
|
|
|
|
|
|
); |
706
|
|
|
|
|
|
|
if ($parent) { |
707
|
|
|
|
|
|
|
push @commit_attrs, parent => $parent; |
708
|
|
|
|
|
|
|
} |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
my $commit = Git::PurePerl::NewObject::Commit->new(@commit_attrs); |
711
|
|
|
|
|
|
|
$self->_git()->put_object($commit); |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
sub _hash2tree { |
716
|
|
|
|
|
|
|
my $self = shift; |
717
|
|
|
|
|
|
|
my $hash = shift; |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
if ( ref($hash) ne 'HASH' ) { |
720
|
|
|
|
|
|
|
confess "ERR: _hash2tree() - arg not hash ref [$hash]"; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
if ( $self->debug() ) { |
723
|
|
|
|
|
|
|
warn "Entered _hash2tree( $hash ): ", join( ', ', %{$hash} ), "\n"; |
724
|
|
|
|
|
|
|
} |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
my @dir_entries = (); |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
foreach my $key ( keys %{$hash} ) { |
729
|
|
|
|
|
|
|
if ( $self->debug() ) { |
730
|
|
|
|
|
|
|
warn "# _hash2tree() processing $key -> ", $hash->{$key}, "\n"; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
if ( ref( $hash->{$key} ) eq 'HASH' ) { |
733
|
|
|
|
|
|
|
if ( $self->debug() ) { |
734
|
|
|
|
|
|
|
warn "# _hash2tree() adding subtree for $key\n"; |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
my $subtree = $self->_hash2tree( $hash->{$key} ); |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
next unless($subtree); |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
my $local_key = $key; |
741
|
|
|
|
|
|
|
if ( $] > 5.007 && utf8::is_utf8($local_key) ) { |
742
|
|
|
|
|
|
|
utf8::downgrade($local_key); |
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
my $de = Git::PurePerl::NewDirectoryEntry->new( |
746
|
|
|
|
|
|
|
mode => '40000', |
747
|
|
|
|
|
|
|
filename => $local_key, |
748
|
|
|
|
|
|
|
sha1 => $subtree->sha1(), |
749
|
|
|
|
|
|
|
); |
750
|
|
|
|
|
|
|
push @dir_entries, $de; |
751
|
|
|
|
|
|
|
} |
752
|
|
|
|
|
|
|
elsif ( ref( $hash->{$key} ) eq 'SCALAR' ) { |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
# Support for symbolic links |
755
|
|
|
|
|
|
|
if ( $self->debug() ) { |
756
|
|
|
|
|
|
|
warn "# _hash2tree() adding symlink for $key\n"; |
757
|
|
|
|
|
|
|
} |
758
|
|
|
|
|
|
|
my $obj = |
759
|
|
|
|
|
|
|
Git::PurePerl::NewObject::Blob->new( |
760
|
|
|
|
|
|
|
content => ${ $hash->{$key} } ); |
761
|
|
|
|
|
|
|
$self->_git()->put_object($obj); |
762
|
|
|
|
|
|
|
my $local_key = $key; |
763
|
|
|
|
|
|
|
if ( $] > 5.007 && utf8::is_utf8($local_key) ) { |
764
|
|
|
|
|
|
|
utf8::downgrade($local_key); |
765
|
|
|
|
|
|
|
} |
766
|
|
|
|
|
|
|
my $de = Git::PurePerl::NewDirectoryEntry->new( |
767
|
|
|
|
|
|
|
mode => '120000', # symlink |
768
|
|
|
|
|
|
|
filename => $local_key, |
769
|
|
|
|
|
|
|
sha1 => $obj->sha1(), |
770
|
|
|
|
|
|
|
); |
771
|
|
|
|
|
|
|
push @dir_entries, $de; |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
elsif ( defined $hash->{$key} ) { |
774
|
|
|
|
|
|
|
my $obj = |
775
|
|
|
|
|
|
|
Git::PurePerl::NewObject::Blob->new( content => $hash->{$key} ); |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
my $local_key = $key; |
778
|
|
|
|
|
|
|
if ( $] > 5.007 && utf8::is_utf8($local_key) ) { |
779
|
|
|
|
|
|
|
utf8::downgrade($local_key); |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
warn "# created blob for '$key' with sha " . $obj->sha1() if $self->debug(); |
783
|
|
|
|
|
|
|
warn "# '$key' utf8 flag: ", utf8::is_utf8($key) if $self->debug(); |
784
|
|
|
|
|
|
|
$self->_git()->put_object($obj); |
785
|
|
|
|
|
|
|
my $de = Git::PurePerl::NewDirectoryEntry->new( |
786
|
|
|
|
|
|
|
mode => '100644', # plain file |
787
|
|
|
|
|
|
|
filename => $local_key, |
788
|
|
|
|
|
|
|
sha1 => $obj->sha1(), |
789
|
|
|
|
|
|
|
); |
790
|
|
|
|
|
|
|
push @dir_entries, $de; |
791
|
|
|
|
|
|
|
} else { |
792
|
|
|
|
|
|
|
warn "# _hash2tree() value is undef for key $key\n" if $self->debug(); |
793
|
|
|
|
|
|
|
} |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
if (!scalar @dir_entries) { |
797
|
|
|
|
|
|
|
warn "# _hash2tree() nothing to push\n" if $self->debug();; |
798
|
|
|
|
|
|
|
return undef; |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
my $tree = |
802
|
|
|
|
|
|
|
Git::PurePerl::NewObject::Tree->new( directory_entries => |
803
|
|
|
|
|
|
|
[ sort { $a->filename cmp $b->filename } @dir_entries ] ); |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
if ( $self->debug() ) { |
806
|
|
|
|
|
|
|
my $content = $tree->content; |
807
|
|
|
|
|
|
|
$content =~ s/(.)/sprintf("%x",ord($1))/eg; |
808
|
|
|
|
|
|
|
warn "# Added tree with dir entries: ", |
809
|
|
|
|
|
|
|
join( ', ', map { $_->filename } @dir_entries ), "\n"; |
810
|
|
|
|
|
|
|
warn "# content: ", $content, "\n"; |
811
|
|
|
|
|
|
|
warn "# size: ", $tree->size, "\n"; |
812
|
|
|
|
|
|
|
warn "# kind: ", $tree->kind, "\n"; |
813
|
|
|
|
|
|
|
warn "# sha1: ", $tree->sha1, "\n"; |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
} |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
$self->_git()->put_object($tree); |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
return $tree; |
820
|
|
|
|
|
|
|
} |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
=head2 _mknode LOCATION |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
Creates a node at the given LOCATION, creating parent nodes if necessary. |
825
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
A reference to the node at the LOCATION is returned. |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
=cut |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
sub _mknode { |
831
|
|
|
|
|
|
|
my $self = shift; |
832
|
|
|
|
|
|
|
my $location = shift; |
833
|
|
|
|
|
|
|
my $ref = $self->_git(); |
834
|
|
|
|
|
|
|
foreach my $key ( split( $self->delimiter_regex(), $location ) ) { |
835
|
|
|
|
|
|
|
if ( not exists $ref->{$key} ) { |
836
|
|
|
|
|
|
|
$ref->{$key} = {}; |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
elsif ( ref( $ref->{$key} ) ne 'HASH' ) { |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
# TODO: fix this ugly error to something more appropriate |
841
|
|
|
|
|
|
|
die "Location at $key in $location already assigned to non-HASH"; |
842
|
|
|
|
|
|
|
} |
843
|
|
|
|
|
|
|
$ref = $ref->{$key}; |
844
|
|
|
|
|
|
|
} |
845
|
|
|
|
|
|
|
return $ref; |
846
|
|
|
|
|
|
|
} |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
=head2 _findobjx LOCATION [, VERSION ] |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
Returns the Git::PurePerl and Git::PurePerl::DirectoryEntry objects found in |
851
|
|
|
|
|
|
|
the file path at LOCATION. |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
my ($ref1, $de1) = $cfg->_findnode("smartcard.ldap.uri"); |
854
|
|
|
|
|
|
|
my $ref2, $de2) = $cfg->_findnode("certs.signature.duration", $wfcfgver); |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
In most cases, the C<_findobj> version is sufficient. This extended version |
857
|
|
|
|
|
|
|
is used to look at the attribtes of the directory entry for things like whether |
858
|
|
|
|
|
|
|
the blob is a symlink. |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
=cut |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
sub _findobjx { |
863
|
|
|
|
|
|
|
my $self = shift; |
864
|
|
|
|
|
|
|
my $location = shift; |
865
|
|
|
|
|
|
|
my $ver = shift; |
866
|
|
|
|
|
|
|
my $cfg = $self->_git(); |
867
|
|
|
|
|
|
|
my ( $obj, $deobj ); |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
# If no version hash was given, default to the HEAD of master |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
if ( not $ver ) { |
872
|
|
|
|
|
|
|
my $master = $self->_git()->ref('refs/heads/master'); |
873
|
|
|
|
|
|
|
if ( $master ) { |
874
|
|
|
|
|
|
|
$ver = $master->sha1; |
875
|
|
|
|
|
|
|
} else { |
876
|
|
|
|
|
|
|
# if no sha1s are in repo, there's nothing to return |
877
|
|
|
|
|
|
|
return; |
878
|
|
|
|
|
|
|
} |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
} |
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
# TODO: is this the way we want to handle the error of not finding |
883
|
|
|
|
|
|
|
# the given object? |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
$obj = $cfg->get_object($ver); |
886
|
|
|
|
|
|
|
if ( not $obj ) { |
887
|
|
|
|
|
|
|
$@ = "No object found for SHA1 $ver"; |
888
|
|
|
|
|
|
|
return; |
889
|
|
|
|
|
|
|
} |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
if ( $obj->kind eq 'commit' ) { |
892
|
|
|
|
|
|
|
$obj = $obj->tree; |
893
|
|
|
|
|
|
|
} |
894
|
|
|
|
|
|
|
my @keys = split $self->delimiter_regex(), $location; |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
# iterate thru the levels in the location |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
while (@keys) { |
899
|
|
|
|
|
|
|
my $key = shift @keys; |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
# if the object is a blob, we already reached the leaf |
902
|
|
|
|
|
|
|
if ($obj->kind eq 'blob') { |
903
|
|
|
|
|
|
|
return undef; |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
# $obj should contain the parent tree object. |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
my @directory_entries = $obj->directory_entries; |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
# find the corresponding child object |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
my $found = 0; |
913
|
|
|
|
|
|
|
foreach my $de (@directory_entries) { |
914
|
|
|
|
|
|
|
if ( $de->filename eq $key ) { |
915
|
|
|
|
|
|
|
$found++; |
916
|
|
|
|
|
|
|
$obj = $cfg->get_object( $de->sha1 ); |
917
|
|
|
|
|
|
|
$deobj = $de; |
918
|
|
|
|
|
|
|
last; |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
} |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
if ( not $found ) { |
923
|
|
|
|
|
|
|
return; |
924
|
|
|
|
|
|
|
} |
925
|
|
|
|
|
|
|
} |
926
|
|
|
|
|
|
|
return $obj, $deobj; |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
} |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
=head2 _findobj LOCATION [, VERSION ] |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
Returns the Git::PurePerl object found in the file path at LOCATION. |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
my $ref1 = $cfg->_findnode("smartcard.ldap.uri"); |
935
|
|
|
|
|
|
|
my $ref2 = $cfg->_findnode("certs.signature.duration", $wfcfgver); |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
=cut |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
sub _findobj { |
940
|
|
|
|
|
|
|
my $self = shift; |
941
|
|
|
|
|
|
|
my ( $obj, $deobj ) = $self->_findobjx(@_); |
942
|
|
|
|
|
|
|
if ( defined $obj ) { |
943
|
|
|
|
|
|
|
return $obj; |
944
|
|
|
|
|
|
|
} |
945
|
|
|
|
|
|
|
else { |
946
|
|
|
|
|
|
|
return; |
947
|
|
|
|
|
|
|
} |
948
|
|
|
|
|
|
|
} |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=head2 _get_sect_key LOCATION |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
Returns the section and key needed by Config::Std to access the |
953
|
|
|
|
|
|
|
configuration values. The given LOCATION is split on the last delimiter. |
954
|
|
|
|
|
|
|
The resulting section and key are returned as a list. |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
=cut |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
sub _get_sect_key { |
959
|
|
|
|
|
|
|
my $self = shift; |
960
|
|
|
|
|
|
|
my $key = shift; |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
# Config::Std uses section/key, so we need to split up the |
963
|
|
|
|
|
|
|
# given key |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
my @tokens = split( $self->delimiter_regex(), $key ); |
966
|
|
|
|
|
|
|
$key = pop @tokens; |
967
|
|
|
|
|
|
|
my $sect = join( $self->delimiter(), @tokens ); |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
return $sect, $key; |
970
|
|
|
|
|
|
|
} |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
=head2 _which( NAME, DIR ... ) |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
Searches the directory list DIR, returning the full path in which the file NAME was |
975
|
|
|
|
|
|
|
found. |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
=cut |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
sub _which { |
980
|
|
|
|
|
|
|
my $self = shift; |
981
|
|
|
|
|
|
|
my $name = shift; |
982
|
|
|
|
|
|
|
my @dirs = @_; |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
foreach (@dirs) { |
985
|
|
|
|
|
|
|
my $path = $_ . '/' . $name; |
986
|
|
|
|
|
|
|
if ( -f $path ) { |
987
|
|
|
|
|
|
|
return $path; |
988
|
|
|
|
|
|
|
} |
989
|
|
|
|
|
|
|
} |
990
|
|
|
|
|
|
|
return; |
991
|
|
|
|
|
|
|
} |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
=head2 _read_config_path SELF, FILENAME, CFGREF, PATH |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
Searches for FILENAME in the given directories in PATH. When found, |
996
|
|
|
|
|
|
|
the file is parsed and a data structure is written to the location |
997
|
|
|
|
|
|
|
in CFGREF. |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
Note: this is the wrapper around the underlying libs that read the |
1000
|
|
|
|
|
|
|
configuration data from the files. |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
=cut |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
sub _read_config_path { |
1005
|
|
|
|
|
|
|
my $self = shift; |
1006
|
|
|
|
|
|
|
my $cfgname = shift; |
1007
|
|
|
|
|
|
|
my $cfgref = shift; |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
my $cfgfile = $self->_which( $cfgname, @_ ); |
1010
|
|
|
|
|
|
|
if ( not $cfgfile ) { |
1011
|
|
|
|
|
|
|
die "ERROR: couldn't find $cfgname in ", join( ', ', @_ ); |
1012
|
|
|
|
|
|
|
} |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
read_config( $cfgfile => %{$cfgref} ); |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
=head2 _debugtree( OBJREF | SHA1 ) |
1018
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
This fetches the entire tree for the given SHA1 and dumps it in a |
1020
|
|
|
|
|
|
|
human-readable format. |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
=cut |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
sub _debugtree { |
1025
|
|
|
|
|
|
|
my $self = shift; |
1026
|
|
|
|
|
|
|
my $start = shift; |
1027
|
|
|
|
|
|
|
my $indent = shift || 0; |
1028
|
|
|
|
|
|
|
my $cfg = $self->_git(); |
1029
|
|
|
|
|
|
|
my @out = (); |
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
my $tabsize = 2; |
1032
|
|
|
|
|
|
|
my $obj; |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
# Soooo, let's see what we've been fed... |
1035
|
|
|
|
|
|
|
if ( not $start ) { # default to the HEAD of master |
1036
|
|
|
|
|
|
|
my $master = $cfg->ref('refs/heads/master'); |
1037
|
|
|
|
|
|
|
if ( $master ) { |
1038
|
|
|
|
|
|
|
$obj = $cfg->get_object( $master->sha1 ); |
1039
|
|
|
|
|
|
|
} |
1040
|
|
|
|
|
|
|
else { |
1041
|
|
|
|
|
|
|
push @out, "NO SHA1s IN TREE"; |
1042
|
|
|
|
|
|
|
return @out; # if no sha1s are in repo, there's nothing to return |
1043
|
|
|
|
|
|
|
} |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
} |
1046
|
|
|
|
|
|
|
elsif ( not ref($start) ) { # possibly a sha1 |
1047
|
|
|
|
|
|
|
$obj = $cfg->get_object($start); |
1048
|
|
|
|
|
|
|
if ( not $obj ) { |
1049
|
|
|
|
|
|
|
$@ = "No object found for SHA1 " . $start ? $start : ''; |
1050
|
|
|
|
|
|
|
return $@; |
1051
|
|
|
|
|
|
|
} |
1052
|
|
|
|
|
|
|
} |
1053
|
|
|
|
|
|
|
elsif ( ref($start) =~ /^(REF|SCALAR|ARRAY|HASH|CODE|GLOB)$/ ) { |
1054
|
|
|
|
|
|
|
croak( "_debugtree doesn't support ref type " . ref($start) ); |
1055
|
|
|
|
|
|
|
} |
1056
|
|
|
|
|
|
|
else { |
1057
|
|
|
|
|
|
|
$obj = $start; |
1058
|
|
|
|
|
|
|
} |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
# At this point, we should have a Git::PurePerl (new) Object. |
1061
|
|
|
|
|
|
|
# Let's double-check. |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
if ( $obj->can('kind') ) { |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
# push @out, ( ' ' x ( $tabsize * $indent ) ) . ('=' x 40); |
1066
|
|
|
|
|
|
|
#foreach my $attr (qw( kind size content sha1 git )) { |
1067
|
|
|
|
|
|
|
foreach my $attr (qw( kind size sha1 )) { |
1068
|
|
|
|
|
|
|
if ( $obj->can($attr) ) { |
1069
|
|
|
|
|
|
|
push @out, |
1070
|
|
|
|
|
|
|
( ' ' x ( $tabsize * $indent ) ) . $attr . ': ' . $obj->$attr; |
1071
|
|
|
|
|
|
|
} |
1072
|
|
|
|
|
|
|
} |
1073
|
|
|
|
|
|
|
} |
1074
|
|
|
|
|
|
|
elsif ($obj->isa('Git::PurePerl::NewDirectoryEntry') |
1075
|
|
|
|
|
|
|
or $obj->isa('Git::PurePerl::DirectoryEntry') ) |
1076
|
|
|
|
|
|
|
{ |
1077
|
|
|
|
|
|
|
foreach my $attr (qw( mode filename sha1 )) { |
1078
|
|
|
|
|
|
|
if ( $obj->can($attr) ) { |
1079
|
|
|
|
|
|
|
push @out, |
1080
|
|
|
|
|
|
|
( ' ' x ( $tabsize * $indent ) ) . $attr . ': ' . $obj->$attr; |
1081
|
|
|
|
|
|
|
} |
1082
|
|
|
|
|
|
|
} |
1083
|
|
|
|
|
|
|
push @out, $self->_debugtree( $obj->sha1, $indent + 1 ); |
1084
|
|
|
|
|
|
|
return @out; |
1085
|
|
|
|
|
|
|
} |
1086
|
|
|
|
|
|
|
else { |
1087
|
|
|
|
|
|
|
die "Obj $obj doesn't seem to be supported"; |
1088
|
|
|
|
|
|
|
} |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
if ( $obj->kind eq 'commit' ) { |
1091
|
|
|
|
|
|
|
foreach my $attr ( |
1092
|
|
|
|
|
|
|
qw( tree_sha1 parent_sha1s author authored_time committer |
1093
|
|
|
|
|
|
|
commited_time comment encoding ) |
1094
|
|
|
|
|
|
|
) |
1095
|
|
|
|
|
|
|
{ |
1096
|
|
|
|
|
|
|
if ( $obj->can($attr) ) { |
1097
|
|
|
|
|
|
|
push @out, |
1098
|
|
|
|
|
|
|
( ' ' x ( $tabsize * $indent ) ) . $attr . ': ' . $obj->$attr; |
1099
|
|
|
|
|
|
|
} |
1100
|
|
|
|
|
|
|
} |
1101
|
|
|
|
|
|
|
push @out, $self->_debugtree( $obj->tree, $indent + 1 ); |
1102
|
|
|
|
|
|
|
} |
1103
|
|
|
|
|
|
|
elsif ( $obj->kind eq 'tree' ) { |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
push @out, ( ' ' x ( $tabsize * $indent ) ) . 'raw: '; |
1106
|
|
|
|
|
|
|
push @out, map { |
1107
|
|
|
|
|
|
|
chomp $_; |
1108
|
|
|
|
|
|
|
( ' ' x ( $tabsize * $indent ) ) . $_ |
1109
|
|
|
|
|
|
|
} hdump( $obj->kind . ' ' . $obj->size . "\0" . $obj->content ); |
1110
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
my $sha1a = Digest::SHA->new; |
1112
|
|
|
|
|
|
|
$sha1a->add( $obj->kind . ' ' . $obj->size . "\0" . $obj->content ); |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
push @out, |
1115
|
|
|
|
|
|
|
( ' ' x ( $tabsize * $indent ) ) |
1116
|
|
|
|
|
|
|
. 'my sha1 from Digest::SHA: ' |
1117
|
|
|
|
|
|
|
. $sha1a->hexdigest; |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
my @directory_entries = $obj->directory_entries; |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
foreach my $de (@directory_entries) { |
1122
|
|
|
|
|
|
|
push @out, |
1123
|
|
|
|
|
|
|
( ' ' x ( $tabsize * $indent ) ) |
1124
|
|
|
|
|
|
|
. 'Directory Entry: '; # . $de->filename; |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
push @out, $self->_debugtree( $de, $indent + 1 ); |
1127
|
|
|
|
|
|
|
} |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
elsif ( $obj->kind eq 'blob' ) { |
1130
|
|
|
|
|
|
|
push @out, ' ' x ( $tabsize * ($indent) ) . 'content: '; |
1131
|
|
|
|
|
|
|
push @out, ( ' ' x ( $tabsize * ( $indent + 1 ) ) ) |
1132
|
|
|
|
|
|
|
. join( |
1133
|
|
|
|
|
|
|
"\n" . ( ' ' x ( $tabsize * ( $indent + 1 ) ) ), |
1134
|
|
|
|
|
|
|
split( /\n/, $obj->content ) |
1135
|
|
|
|
|
|
|
); |
1136
|
|
|
|
|
|
|
} |
1137
|
|
|
|
|
|
|
else { |
1138
|
|
|
|
|
|
|
push @out, |
1139
|
|
|
|
|
|
|
' ' x ( $tabsize * $indent ) |
1140
|
|
|
|
|
|
|
. 'Dump object kind ' |
1141
|
|
|
|
|
|
|
. $obj->kind |
1142
|
|
|
|
|
|
|
. ' not implemented'; |
1143
|
|
|
|
|
|
|
} |
1144
|
|
|
|
|
|
|
return @out; |
1145
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
} |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
=head2 hdump |
1149
|
|
|
|
|
|
|
|
1150
|
|
|
|
|
|
|
Return hexdump of given data. |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
=cut |
1153
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
sub hdump { |
1155
|
|
|
|
|
|
|
my $offset = 0; |
1156
|
|
|
|
|
|
|
my @out = (); |
1157
|
|
|
|
|
|
|
my ( @array, $format ); |
1158
|
|
|
|
|
|
|
foreach |
1159
|
|
|
|
|
|
|
my $data ( unpack( "a16" x ( length( $_[0] ) / 16 ) . "a*", $_[0] ) ) |
1160
|
|
|
|
|
|
|
{ |
1161
|
|
|
|
|
|
|
my ($len) = length($data); |
1162
|
|
|
|
|
|
|
if ( $len == 16 ) { |
1163
|
|
|
|
|
|
|
@array = unpack( 'N4', $data ); |
1164
|
|
|
|
|
|
|
$format = "0x%08x (%05d) %08x %08x %08x %08x %s\n"; |
1165
|
|
|
|
|
|
|
} |
1166
|
|
|
|
|
|
|
else { |
1167
|
|
|
|
|
|
|
@array = unpack( 'C*', $data ); |
1168
|
|
|
|
|
|
|
$_ = sprintf "%2.2x", $_ for @array; |
1169
|
|
|
|
|
|
|
push( @array, ' ' ) while $len++ < 16; |
1170
|
|
|
|
|
|
|
$format = |
1171
|
|
|
|
|
|
|
"0x%08x (%05d)" . " %s%s%s%s %s%s%s%s %s%s%s%s %s%s%s%s %s\n"; |
1172
|
|
|
|
|
|
|
} |
1173
|
|
|
|
|
|
|
$data =~ tr/\0-\37\177-\377/./; |
1174
|
|
|
|
|
|
|
push @out, sprintf $format, $offset, $offset, @array, $data; |
1175
|
|
|
|
|
|
|
$offset += 16; |
1176
|
|
|
|
|
|
|
} |
1177
|
|
|
|
|
|
|
return @out; |
1178
|
|
|
|
|
|
|
} |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
Was based on the CPAN module App::Options, but since been converted to Moose. |
1183
|
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
=head1 AUTHOR |
1185
|
|
|
|
|
|
|
|
1186
|
|
|
|
|
|
|
Scott T. Hardin, C<< <mrscotty at cpan.org> >> |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
Martin Bartosch |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
Oliver Welter |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
=head1 BUGS |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
Please report any bugs or feature requests to C<bug-config-versioned at |
1195
|
|
|
|
|
|
|
rt.cpan.org>, or through the web interface at |
1196
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Config-Versioned>. |
1197
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress |
1198
|
|
|
|
|
|
|
on your bug as I make changes. |
1199
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
=head1 SUPPORT |
1201
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
perldoc Config::Versioned |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
You can also look for information at: |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
=over 4 |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Config-Versioned> |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
L<http://annocpan.org/dist/Config-Versioned> |
1218
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
=item * CPAN Ratings |
1220
|
|
|
|
|
|
|
|
1221
|
|
|
|
|
|
|
L<http://cpanratings.perl.org/d/Config-Versioned> |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
=item * Search CPAN |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
L<http://search.cpan.org/dist/Config-Versioned/> |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
=back |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
=head1 COPYRIGHT |
1231
|
|
|
|
|
|
|
|
1232
|
|
|
|
|
|
|
Copyright 2011 Scott T. Hardin, all rights reserved. |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
This program is free software; you can redistribute it |
1235
|
|
|
|
|
|
|
and/or modify it under the same terms as Perl itself. |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
=cut |
1238
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
1; # End of Config::Versioned |
1242
|
|
|
|
|
|
|
|