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