]> git.vpit.fr Git - perl/modules/rgit.git/blob - lib/App/Rgit/Repository.pm
Always canonify the root, the git path and the repo directory
[perl/modules/rgit.git] / lib / App / Rgit / Repository.pm
1 package App::Rgit::Repository;
2
3 use strict;
4 use warnings;
5
6 use Cwd                   qw/cwd abs_path/;
7 use File::Spec::Functions qw/canonpath catdir splitdir abs2rel/;
8 use POSIX qw/WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG SIGINT SIGQUIT/;
9
10 BEGIN {
11  no warnings 'redefine';
12  *WIFEXITED   = sub { 1 }             unless eval { WIFEXITED(0);   1 };
13  *WEXITSTATUS = sub { shift() >> 8 }  unless eval { WEXITSTATUS(0); 1 };
14  *WIFSIGNALED = sub { shift() & 127 } unless eval { WIFSIGNALED(0); 1 };
15 }
16
17 =head1 NAME
18
19 App::Rgit::Repository - Class representing a Git repository.
20
21 =head1 VERSION
22
23 Version 0.06
24
25 =cut
26
27 our $VERSION = '0.06';
28
29 =head1 DESCRIPTION
30
31 Class representing a Git repository.
32
33 This is an internal class to L<rgit>.
34
35 =head1 METHODS
36
37 =head2 C<< new dir => $dir [, fake => 1 ] >>
38
39 Creates a new repository starting from C<$dir>.
40 If the C<fake> option is passed, C<$dir> isn't checked to be a valid C<git> repository.
41
42 =cut
43
44 sub new {
45  my $class = shift;
46  $class = ref $class || $class;
47
48  my %args = @_;
49
50  my $dir = $args{dir};
51  if (defined $dir) {
52   $dir = abs_path $dir;
53  } else {
54   $dir = cwd;
55  }
56  $dir = canonpath $dir;
57
58  my ($repo, $bare, $name, $work);
59  if ($args{fake}) {
60   $repo = $work = $dir;
61  } else {
62   return unless -d $dir
63             and -d "$dir/refs"
64             and -d "$dir/objects"
65             and -e "$dir/HEAD";
66
67   my @chunks = splitdir $dir;
68   my $last   = pop @chunks;
69   return unless defined $last;
70
71   if ($last eq '.git') {
72    $bare = 0;
73    $name = $chunks[-1];
74    $work = catdir @chunks;
75   } elsif ($last =~ /(.+)\.git$/) {
76    $bare = 1;
77    $name = $1;
78    $work = catdir @chunks, $last;
79   } else {
80    return;
81   }
82
83   $repo = $dir;
84  }
85
86  bless {
87   fake => !!$args{fake},
88   repo => $repo,
89   bare => $bare,
90   name => $name,
91   work => $work,
92  }, $class;
93 }
94
95 =head2 C<chdir>
96
97 C<chdir> into the repository's directory.
98
99 =cut
100
101 sub chdir {
102  my $self = shift;
103  my $dir = $self->work;
104  chdir $dir or do {
105   warn "Couldn't chdir into $dir: $!";
106   return;
107  };
108  return 1;
109 }
110
111 =head2 C<run $conf, @args>
112
113 Runs C<git @args> on the repository for the L<App::Rgit::Config> configuration C<$conf>.
114 When the repository isn't fake, the format substitutions applies to C<@args> elements.
115 Returns the exit code.
116
117 =cut
118
119 sub _abs2rel {
120  my $a = &abs2rel;
121  $a = $_[0] unless defined $a;
122  $a;
123 }
124
125 my %escapes = (
126  '%' => sub { '%' },
127  'n' => sub { shift->name },
128  'g' => sub { _abs2rel(shift->repo, shift->root) },
129  'G' => sub { shift->repo },
130  'w' => sub { _abs2rel(shift->work, shift->root) },
131  'W' => sub { shift->work },
132  'b' => sub {
133   my ($self, $conf) = @_;
134   _abs2rel($self->bare ? $self->repo : $self->work . '.git', $conf->root)
135  },
136  'B' => sub { $_[0]->bare ? $_[0]->repo : $_[0]->work . '.git' },
137  'R' => sub { $_[1]->root },
138 );
139 my $e = quotemeta join '', keys %escapes;
140 $e = "[$e]";
141
142 sub run {
143  my $self = shift;
144  my $conf = shift;
145  return unless $conf->isa('App::Rgit::Config');
146  my @args = @_;
147  unless ($self->fake) {
148   s/%($e)/$escapes{$1}->($self, $conf)/eg for @args;
149  }
150  unshift @args, $conf->git;
151  $conf->info('Executing "', join(' ', @args), '" into ', $self->work, "\n");
152  {
153   local $ENV{GIT_DIR} = $self->repo if exists $ENV{GIT_DIR};
154   local $ENV{GIT_EXEC_PATH} = $conf->git if exists $ENV{GIT_EXEC_PATH};
155   system { $args[0] } @args;
156  }
157  if ($? == -1) {
158   $conf->crit("Failed to execute git: $!\n");
159   return;
160  }
161  my $ret;
162  $ret = WEXITSTATUS($?) if WIFEXITED($?);
163  my $sig;
164  if (WIFSIGNALED($?)) {
165   $sig = WTERMSIG($?);
166   $conf->warn("git died with signal $sig\n");
167   if ($sig == SIGINT || $sig == SIGQUIT) {
168    $conf->err("Aborting\n");
169    exit $sig;
170   }
171  } elsif ($ret) {
172   $conf->info("git returned $ret\n");
173  }
174  return wantarray ? ($ret, $sig) : $ret;
175 }
176
177 =head2 C<fake>
178
179 =head2 C<repo>
180
181 =head2 C<bare>
182
183 =head2 C<name>
184
185 =head2 C<work>
186
187 Read-only accessors.
188
189 =cut
190
191 BEGIN {
192  eval "sub $_ { \$_[0]->{$_} }" for qw/fake repo bare name work/;
193 }
194
195 =head1 SEE ALSO
196
197 L<rgit>.
198
199 =head1 AUTHOR
200
201 Vincent Pit, C<< <perl at profvince.com> >>, L<http://profvince.com>.
202
203 You can contact me by mail or on C<irc.perl.org> (vincent).
204
205 =head1 BUGS
206
207 Please report any bugs or feature requests to C<bug-rgit at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=rgit>.  I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
208
209 =head1 SUPPORT
210
211 You can find documentation for this module with the perldoc command.
212
213     perldoc App::Rgit::Repository
214
215 =head1 COPYRIGHT & LICENSE
216
217 Copyright 2008,2009,2010 Vincent Pit, all rights reserved.
218
219 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
220
221 =cut
222
223 1; # End of App::Rgit::Repository