File Coverage

blib/lib/App/GitHub/FindRepository.pm
Criterion Covered Total %
statement 15 122 12.3
branch 0 56 0.0
condition 0 17 0.0
subroutine 5 19 26.3
pod 2 9 22.2
total 22 223 9.8


line stmt bran cond sub pod time code
1             package App::GitHub::FindRepository;
2              
3 2     2   44665 use warnings;
  2         5  
  2         71  
4 2     2   11 use strict;
  2         3  
  2         183  
5              
6             =head1 NAME
7              
8             App::GitHub::FindRepository - Determine the right case for a GitHub repository
9              
10             =head1 VERSION
11              
12             Version 0.06
13              
14             =cut
15              
16             our $VERSION = '0.06';
17              
18             =head1 SYNOPSIS
19              
20             github-find-repository git://github.com/robertkrimen/Doc-Simply.git
21             # git://github.com/robertkrimen/doc-simply.git
22              
23             github-find-repository robertkrimen,Doc-Simply
24             # git://github.com/robertkrimen/doc-simply.git
25              
26             github-find-repository --pinger=./bin/git-ls-remote ...
27              
28             # ... or ...
29              
30             use App::GitHub::FindRepository
31              
32             my $url = App::GitHub::FindRepository->find( robertkrimen/Doc-Simply )
33             # git://github.com/robertkrimen/doc-simply.git
34              
35             =head1 DESCRIPTION
36              
37             GitHub recently made a change that now allows mixed-case repository names. Unfortunately, their git daemon
38             will not find the right repository given the wrong case.
39              
40             L (C) will interrogate the repository home page (HTML),
41             looking for the "right" repository name in a case insensitive manner
42              
43             If LWP is not installed and curl is not available, then the finder will fallback to using the git protocol (via git-ls-remote or git-peek-remote).
44             It will first attempt to ping the mixed-case version, and, failing that, will attempt to ping the lowercase version.
45              
46             In either case, it will return/print the valid repository url, if any
47              
48             =head1 CAVEAT
49              
50             When finding via the git protocol, the following applies:
51              
52             Given a mixed-case repository, the find routine will try the mixed-case once, then the lowercase. It will not find anything
53             else
54              
55             github-find-repository --git-protocol robertkrimen/Doc-Simply
56              
57             ...will work, as long as the real repository is C or C
58              
59             If the real repository is C then the finder will NOT see it
60              
61             =head1 INSTALL
62              
63             You can install L by using L:
64              
65             cpan -i App::GitHub::FindRepository
66              
67             If that doesn't work properly, you can find help at:
68              
69             http://sial.org/howto/perl/life-with-cpan/
70             http://sial.org/howto/perl/life-with-cpan/macosx/ # Help on Mac OS X
71             http://sial.org/howto/perl/life-with-cpan/non-root/ # Help with a non-root account
72              
73             =head1 CONTRIBUTE
74              
75             You can contribute or fork this project via GitHub:
76              
77             L
78              
79             git clone git://github.com/robertkrimen/App-GitHub-FindRepository.git
80              
81             =cut
82              
83             =head1 USAGE
84              
85             =head2 github-find-repository
86              
87             A commandline application that will print out the the repository with the right casing
88              
89             Usage: github-find-repository [...]
90              
91             --pinger The pinger to use (default is either git-ls-remote or git-peek-remote)
92              
93             --getter The getter to use (default is LWP then curl)
94              
95             --git-protocol Don't try to determine the repository by sniffing HTML, just use git://
96             NOTE: This mode will only check the given casing then lowercase
97              
98             --output One of (case insensitive):
99              
100             Given "http://github.com/robertkrimen/aPp-giTHub-findRepoSitory.git"
101              
102             URL http://github.com/robertkrimen/App-GitHub-FindRepository.git
103             public git://github.com/robertkrimen/App-GitHub-FindRepository.git
104             private git@github.com:robertkrimen/App-GitHub-FindRepository.git
105             base robertkrimen/App-GitHub-FindRepository
106             name App-GitHub-FindRepository
107             home http://github.com/robertkrimen/App-GitHub-FindRepository
108              
109             --help, -h, -? This help
110              
111             The repository to test, can be like:
112              
113             git://github.com/robertkrimen/App-GitHub-FindRepository.git
114             robertkrimen/App-GitHub-FindRepository.git
115             robertkrimen,App-GitHub-FindRepository
116              
117             For example:
118              
119             github-find-repository --getter curl robertkrimen,aPp-giTHuB-findRepOsitory
120              
121             =head2 $repository = AppGitHub::FindRepository->find( [, ...] )
122              
123             Given a mixed-case repository URI, it will return the version with the right case
124              
125             getter The method to use to access the repository home page (HTML)
126             pinger The pinger to use to access the repository via the git protocol
127              
128             =head2 $repository = AppGitHub::FindRepository->find_by_git( [, ...] )
129            
130             pinger The pinger to use to access the repository via the git protocol
131              
132             Given a mixed-case repository URI, it will return the version with the right case, but only using the git protocol
133              
134             NOTE: This method will only check the given casing then lowercase. See CAVEAT
135              
136             =head1 ::Repository
137              
138             The repository object that C<< ->find >> and C<< ->find_by_git >> return
139              
140             The object will stringify via the C<< ->url >> method
141              
142             =head2 $repository->url
143              
144             The URL (URI) of the repository (depends on what the object was instantiated with)
145              
146             =head2 $repository->public
147              
148             The public github clone URL:
149              
150             git://github.com/.git
151              
152             =head2 $repository->private
153              
154             The private github clone URL:
155              
156             git@github.com:.git
157              
158             =head2 $repository->base
159              
160             The user/project part of the repository path (WITHOUT the .git suffix):
161              
162             robertkrimen/App-GitHub-FindRepository
163              
164             =head2 $repository->name
165              
166             The name of the project:
167              
168             App-GitHub-FindRepository
169              
170             =head2 $repository->home
171              
172             The home page of the project on GitHub:
173              
174             http://github.com/ # Will redirect to .../tree/master
175              
176             =head1 A bash function as an alternative
177              
178             If you do not want to install App::GitHub::FindRepository, here is a bash equivalent (using the git protocol, see CAVEAT):
179              
180             #!/bin/bash
181              
182             function github-find-repository() {
183             local pinger=`which git-ls-remote`
184             if [ "$pinger" == "" ]; then pinger=`which git-peek-remote`; fi
185             if [ "$pinger" == "" ]; then echo "Couldn't find pinger (git-ls-remote or git-peek-remote)"; return -1; fi
186             local repository=$1
187             if [ "`$pinger $repository 2>/dev/null`" ]; then echo $repository; return 0; fi
188             repository=`echo $repository | tr "[:upper:]" "[:lower:]" `
189             if [ "`$pinger $repository 2>/dev/null`" ]; then echo $repository; return 0; fi
190             return -1
191             }
192              
193             github-find-repository $*
194              
195             =head1 SEE ALSO
196              
197             L
198              
199             =cut
200              
201 2     2   1138 use App::GitHub::FindRepository::Repository;
  2         6  
  2         61  
202              
203 2     2   2608 use Getopt::Long;
  2         27526  
  2         11  
204 2     2   2357 use Env::Path qw/PATH/;
  2         5271  
  2         29  
205              
206             if (Env::Path->MSWIN) {
207             require Cwd;
208             (my $cwd = Cwd::getcwd()) =~ s{/}{\\}g;
209             PATH->Remove($cwd);
210             PATH->Prepend($cwd);
211             }
212              
213             sub _find_in_path ($) {
214              
215 0     0     for ( PATH->Whence( shift ) ) {
216 0 0 0       return $_ if -f && -r _ && -x _;
      0        
217             }
218              
219 0           return undef;
220             }
221              
222             sub do_usage (;$) {
223 0     0 0   my $error = shift;
224 0 0         warn $error if $error;
225 0           warn <<_END_;
226              
227             Usage: github-find-repository [...]
228              
229             --pinger The pinger to use (default is either git-ls-remote or git-peek-remote)
230              
231             --getter The getter to use (default is LWP then curl)
232              
233             --git-protocol Don't try to determine the repository by sniffing HTML, just use git://
234             NOTE: This mode will only check the given casing then lowercase
235              
236             --output One of (case insensitive):
237              
238             Given "http://github.com/robertkrimen/aPp-giTHub-findRepoSitory.git"
239              
240             URL http://github.com/robertkrimen/App-GitHub-FindRepository.git
241             public git://github.com/robertkrimen/App-GitHub-FindRepository.git
242             private git\@github.com:robertkrimen/App-GitHub-FindRepository.git
243             base robertkrimen/App-GitHub-FindRepository
244             name App-GitHub-FindRepository
245             home http://github.com/robertkrimen/App-GitHub-FindRepository
246              
247             --help, -h, -? This help
248              
249             The repository to test, can be like:
250              
251             git://github.com/robertkrimen/App-GitHub-FindRepository.git
252             robertkrimen/App-GitHub-FindRepository.git
253             robertkrimen,App-GitHub-FindRepository
254              
255             For example:
256              
257             github-find-repository --getter curl robertkrimen,aPp-giTHuB-findRepOsitory
258              
259             _END_
260              
261 0 0         exit -1 if $error;
262             }
263              
264             sub do_found ($$) {
265 0     0 0   my $output = shift;
266 0           my $repository = shift;
267 0 0         if ($output) {
268 0           print $repository->$output, "\n";
269             }
270             else {
271 0           print "$repository\n";
272             }
273 0           exit 0;
274             }
275              
276             sub do_not_found ($) {
277 0     0 0   my $repository = shift;
278 0           warn <<_END_;
279             $0: Repository \"$repository\" not found
280             _END_
281 0           exit -1;
282             }
283              
284             sub pinger {
285 0     0 0   my $self = shift;
286 0   0       return $ENV{GH_FR_PINGER} || _find_in_path 'git-ls-remote' || _find_in_path 'git-peek-remote';
287             }
288              
289             sub _get_by_LWP {
290 0     0     my $self = shift;
291             return sub {
292 0     0     my $url = shift;
293 0           my $agent = LWP::UserAgent->new;
294 0           my $response = $agent->get( $url );
295 0 0         die $response->status_line, "\n" unless $response->is_success;
296 0           return $response->decoded_content;
297 0           };
298             }
299              
300             sub _get_by_curl {
301 0     0     my $self = shift;
302 0           my $curl = shift;
303             return sub {
304 0     0     my $url = shift;
305 0           return `$curl -s -L $url`;
306 0           };
307             }
308             sub getter {
309 0     0 0   my $self = shift;
310 0           my $getter = shift;
311              
312 0 0         return $getter if ref $getter eq 'CODE';
313              
314 0 0         $getter = 'LWP' unless $getter;
315            
316 0 0         die "Oh my god no!\n" if $getter eq '^';
317              
318 0           my $command;
319 0 0 0       if ($getter =~ m/^LWP$/i && eval "require LWP::UserAgent") {
    0          
320 0           return $self->_get_by_LWP;
321             }
322             elsif ($command = _find_in_path 'curl') {
323 0           return $self->_get_by_curl( $command );
324             }
325              
326 0           return undef;
327             }
328              
329             sub parse_repository {
330 0     0 0   my $self = shift;
331 0           return App::GitHub::FindRepository::Repository->parse( @_ );
332             }
333              
334             sub find {
335 0     0 1   my $self = shift;
336 0           my $repository = $self->parse_repository( shift );
337 0           my %given = @_;
338 0           my $getter = $self->getter( $given{getter} );
339 0           my $pinger = $given{pinger};
340              
341 0 0         die "No repository given\n" unless $repository;
342 0 0         if (! $getter ) {
343 0           warn "Unable to use/find LWP or curl\n";
344 0           warn "Falling back to git protocol\n";
345 0           return $self->find_by_git( $repository, pinger => $pinger );
346             }
347              
348 0           my $url = $repository->home;
349 0           my $base = $repository->base;
350              
351 0           my $content;
352 0           eval {
353 0           $content = $getter->( $url );
354             };
355 0 0         unless ($content) {
356 0   0       my $error = $@ || "Unknown error";
357 0           chomp $error;
358 0           warn "Failed GET $url since: $error\n";
359 0           warn "Falling back to git protocol\n";
360 0           return $self->find_by_git( $repository, pinger => $pinger );
361             }
362              
363 0           my ($canonical) = $content =~ m/\/($base)\//i;
364              
365 0 0         unless ($canonical) {
366 0           warn "Failed to find \"/$base/\" in content of size ", length $content, "\n";
367 0           warn "Falling back to git protocol\n";
368 0           return $self->find_by_git( $repository, pinger => $pinger );
369             }
370              
371 0           $repository->base( $canonical );
372              
373 0           return $repository;
374             }
375              
376             sub find_by_git {
377 0     0 1   my $self = shift;
378 0           my $repository = $self->parse_repository( shift );
379 0           my %given = @_;
380 0   0       my $pinger = $given{pinger} || $self->pinger;
381              
382 0 0         die "No or invalid repository given\n" unless $repository;
383 0 0         die "No pinger!\n" unless $pinger;
384              
385 0           my $test_repository = $repository->test;
386              
387 0 0         return $repository if !system( "$pinger $test_repository 1>/dev/null 2>/dev/null" );
388            
389 0 0         if ($repository->base =~ m/[A-Z]/) {
390 0           $repository->base( lc $repository->base );
391 0           my $test_repository = $repository->test;
392 0 0         return $repository if !system( "$pinger $test_repository 1>/dev/null 2>/dev/null" );
393             }
394              
395 0           return undef;
396             }
397              
398             sub run {
399 0     0 0   my $self = shift;
400            
401 0           my ($getter, $pinger, $git_protocol, $output, $help);
402 0           GetOptions(
403             'help|h|?' => \$help,
404             'getter=s' => \$getter,
405             'pinger=s' => \$pinger,
406             'output=s' => \$output,
407             'git-protocol' => \$git_protocol,
408             );
409              
410 0 0         if ($help) {
411 0           do_usage;
412 0           exit 0;
413             }
414              
415 0 0         $pinger = $self->pinger unless $pinger;
416              
417 0           my $repository = join '', @ARGV;
418              
419 0 0         do_usage <<_END_ unless $repository;
420             $0: You need to specify a repository
421             _END_
422              
423 0 0         if ($output) {
424 0           $output = lc $output;
425 0 0         $output =~ m/^(base|public|private|url|name|home)$/ or do_usage <<_END_;
426             $0: Unrecogonized output option "$output"
427             _END_
428             }
429              
430 0           eval {
431 0           my $repository = $repository;
432 0 0         if ($git_protocol) {
433 0           $repository = $self->find_by_git( $repository, pinger => $pinger );
434             }
435             else {
436 0           $repository = $self->find( $repository, getter => $getter, pinger => $pinger );
437             }
438              
439 0 0         do_found $output, $repository if $repository;
440             };
441 0 0         if ($@ =~ m/No pinger!/) {
    0          
442 0           do_usage <<_END_;
443             $0: No pinger given and couldn't find git-ls-remote or git-peek-remote in \$PATH
444             _END_
445             }
446             elsif ($@) {
447 0           my $error = $@;
448 0           chomp $error;
449 0           do_usage <<_END_;
450             $0: There was an error: $error
451             _END_
452             }
453              
454 0           do_not_found $repository;
455             }
456              
457             =head1 AUTHOR
458              
459             Robert Krimen, C<< >>
460              
461             =head1 BUGS
462              
463             Please report any bugs or feature requests to C, or through
464             the web interface at L. I will be notified, and then you'll
465             automatically be notified of progress on your bug as I make changes.
466              
467              
468              
469              
470             =head1 SUPPORT
471              
472             You can find documentation for this module with the perldoc command.
473              
474             perldoc App::GitHub::FindRepository
475              
476              
477             You can also look for information at:
478              
479             =over 4
480              
481             =item * RT: CPAN's request tracker
482              
483             L
484              
485             =item * AnnoCPAN: Annotated CPAN documentation
486              
487             L
488              
489             =item * CPAN Ratings
490              
491             L
492              
493             =item * Search CPAN
494              
495             L
496              
497             =back
498              
499              
500             =head1 ACKNOWLEDGEMENTS
501              
502              
503             =head1 COPYRIGHT & LICENSE
504              
505             Copyright 2009 Robert Krimen, all rights reserved.
506              
507             This program is free software; you can redistribute it and/or modify it
508             under the same terms as Perl itself.
509              
510              
511             =cut
512              
513             __PACKAGE__; # End of App::GitHub::FindRepository