File Coverage

blib/lib/File/Tree/Snapshot.pm
Criterion Covered Total %
statement 28 60 46.6
branch 4 20 20.0
condition n/a
subroutine 8 14 57.1
pod 6 7 85.7
total 46 101 45.5


line stmt bran cond sub pod time code
1             # most of this was sponsored by socialflow.com
2              
3             package File::Tree::Snapshot;
4 1     1   24088 use Moo;
  1         15244  
  1         7  
5 1     1   1890 use File::Path;
  1         2  
  1         82  
6 1     1   6 use File::Basename;
  1         2  
  1         1115  
7              
8             our $VERSION = '0.000001';
9             $VERSION = eval $VERSION;
10              
11             has storage_path => (is => 'ro', required => 1);
12              
13             has allow_empty => (is => 'ro');
14              
15 0     0 1 0 sub file { join '/', (shift)->storage_path, @_}
16              
17             sub open {
18 0     0 1 0 my ($self, $mode, $file, %opt) = @_;
19 0 0       0 $file = $self->file($file)
20             unless $opt{is_absolute};
21 0 0       0 $self->_mkpath(dirname $file)
22             if $opt{mkpath};
23 0 0       0 open my $fh, $mode, $file
24             or die "Unable to write '$file': $!\n";
25 0         0 return $fh;
26             }
27              
28             sub _mkpath {
29 1     1   2 my ($self, $dir) = @_;
30 1         53 mkpath($dir, { error => \(my $err) });
31 1 50       5 if (@$err) {
32 0         0 warn "Error while attempting to create '$dir': $_\n"
33 0         0 for map { (values %$_) } @$err;
34             }
35 1         3 return 1;
36             }
37              
38             sub _exec {
39 3     3   7 my ($self, $cmd) = @_;
40 3 100       54660 system($cmd) and die "Error during ($cmd)\n";
41 2         112 return 1;
42             }
43              
44             sub _git_exec {
45 3     3   16 my ($self, @cmd) = @_;
46 3         17 my $path = $self->storage_path;
47             #local $ENV{GIT_DIR} = "$path/.git";
48 3         42 return $self->_exec(
49             sprintf q{cd %s && git %s},
50             $path,
51             join ' ', @cmd,
52             );
53             }
54              
55             sub create {
56 1     1 1 2 my ($self) = @_;
57 1         4 my $path = $self->storage_path;
58 1         5 $self->_mkpath($path);
59 1         4 $self->_git_exec('init');
60 1 50       170 CORE::open my $fh, '>', "$path/.gitignore"
61             or die "Unable to write .gitignore in '$path': $!\n";
62 1         14 $self->_git_exec('add', '.gitignore');
63 1         53 $self->_git_exec('commit', '-m', '"Initial commit"');
64 0         0 return 1;
65             }
66              
67             sub _has_changes {
68 0     0   0 my ($self) = @_;
69 0         0 my $path = $self->storage_path;
70 0         0 my $cmd = qq{cd $path && git status --porcelain};
71 0 0       0 CORE::open my $handle, '-|', $cmd
72             or die "Unable to find changes in ($cmd): $!\n";
73 0         0 my @changes = <$handle>;
74 0         0 return scalar @changes;
75             }
76              
77             sub commit {
78 0     0 1 0 my ($self) = @_;
79 0         0 $self->_git_exec('add .');
80 0 0       0 unless ($self->_has_changes) {
81 0         0 print "No changes\n";
82 0         0 return 1;
83             }
84 0 0       0 $self->_git_exec('commit',
85             '--all',
86             ($self->allow_empty ? '--allow-empty' : ()),
87             '-m' => sprintf('"Updated on %s"', scalar localtime),
88             );
89 0         0 return 1;
90             }
91              
92             sub reset {
93 0     0 1 0 my ($self) = @_;
94 0         0 $self->_git_exec('add .');
95 0 0       0 return 1
96             unless $self->_has_changes;
97 0         0 $self->_git_exec('checkout -f');
98 0         0 return 1;
99             }
100              
101             sub exists {
102 1     1 1 1311 my ($self) = @_;
103 1         45 return -e join '/', $self->storage_path, '.git';
104             }
105              
106             sub find_files {
107 0     0 0   my ($self, $ext, @path) = @_;
108 0           my $root = $self->file(@path);
109 0           my @files = `find $root -name '*.$ext' -type f`;
110 0           chomp @files;
111 0           return @files;
112             }
113              
114             1;
115              
116             =head1 NAME
117              
118             File::Tree::Snapshot - Snapshot files in a git repository
119              
120             =head1 SYNOPSIS
121              
122             use File::Tree::Snapshot;
123              
124             my $tree = File::Tree::Snapshot->new(
125             storage_path => '/path/to/tree',
126             );
127              
128             $tree->create
129             unless $tree->exists;
130              
131             # modify files, see methods below
132              
133             $tree->commit;
134             # or
135             $tree->reset;
136              
137             =head1 DESCRIPTION
138              
139             This module manages snapshots of file system trees by wrapping the C
140             command line interface. It currently only manages generating the snapshots.
141              
142             The directories are standard Git repositories and can be accessed in the
143             usual ways.
144              
145             =head1 ATTRIBUTES
146              
147             =head2 storage_path
148              
149             The path to the tree that should hold the files that are snapshot. This
150             attribute is required.
151              
152             =head2 allow_empty
153              
154             If this attribute is set to true, commits will be created even if no changes
155             were registered.
156              
157             =head1 METHODS
158              
159             =head2 new
160              
161             my $tree = File::Tree::Snapshot->new(%attributes);
162              
163             Constructor. See L for possible parameters.
164              
165             =head2 file
166              
167             my $path = $tree->file(@relative_path_parts_to_file);
168              
169             Takes a set of path parts and returns the path to the file inside the
170             storage.
171              
172             =head2 open
173              
174             my $fh = $tree->open($mode, $file, %options);
175              
176             Opens a file within the storage. C<$mode> is passed straight to
177             L. The C<$file> is a relative path inside the storage.
178              
179             Possible options are:
180              
181             =over
182              
183             =item * C
184              
185             If set to true the C<$file> will be assumed to already be an absolute
186             path as returned by L, instead of a path relative to the storage.
187              
188             =item * C
189              
190             Create the path to the file if it doesn't already exist.
191              
192             =back
193              
194             =head2 create
195              
196             $tree->create;
197              
198             Create the directory (if it doesn't exist yet) and initialize it as a
199             Git repository.
200              
201             =head2 exists
202              
203             my $does_exist = $tree->exists;
204              
205             Returns true if the storage is an initialized Git repository.
206              
207             =head2 commit
208              
209             Will commit the changes made to the tree to the Git repository.
210              
211             =head2 reset
212              
213             Rolls back the changes since the last snapshot.
214              
215             =head1 AUTHOR
216              
217             phaylon - Robert Sedlacek (cpan:PHAYLON)
218              
219             =head1 CONTRIBUTORS
220              
221             mst - Matt S. Trout (cpan:MSTROUT)
222              
223             =head1 SPONSORS
224              
225             The development of this module was sponsored by L.
226              
227             =head1 COPYRIGHT
228              
229             Copyright (c) 2012 the File::Tree::Snapshot L, L
230             and L as listed above.
231              
232             =head1 LICENSE
233              
234             This library is free software and may be distributed under the same terms
235             as perl itself.
236              
237             =cut