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