File Coverage

blib/lib/Padre/Plugin/HG.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Padre::Plugin::HG;
2              
3 2     2   27434 use 5.008;
  2         7  
  2         92  
4 2     2   12 use warnings;
  2         4  
  2         63  
5 2     2   11 use strict;
  2         8  
  2         92  
6              
7 2     2   1131 use Padre::Config ();
  0            
  0            
8             use Padre::Wx ();
9             use Padre::Plugin ();
10             use Padre::Util ();
11              
12             use Capture::Tiny qw(capture_merged);
13             use File::Basename ();
14             use File::Spec;
15              
16             use Padre::Plugin::HG::ProjectCommit;
17             use Padre::Plugin::HG::ProjectClone;
18             use Padre::Plugin::HG::UserPassPrompt;
19             use Padre::Plugin::HG::DiffView;
20             use Padre::Plugin::HG::LogView;
21             my %projects;
22             our $VERSION = '0.17';
23             our @ISA = 'Padre::Plugin';
24              
25             my $VCS = "Mercurial"
26             # enter the vcs commands here, variables will be evaled in in the sub routines.
27             # was meant as a way to make it more generic. Not sure it is going to
28             # succeed.
29             my %VCSCommand = ( commit => 'hg commit -A -m"$message" $path ',
30             add => 'hg add $path',
31             status =>'hg status --all $path',
32             root => 'hg root',
33             diff => 'hg diff $path',
34             diff_revision => 'hg diff -r $revision $path',
35             clone=> 'hg clone $path',
36             pull =>'hg pull --update --noninteractive ',
37             push =>'hg push $path',
38             log =>'hg log $path');
39            
40              
41              
42             =pod
43              
44             =head1 NAME
45              
46             Padre::Plugin::HG - Mecurial interface for Padre
47              
48             =head1 Instructions
49              
50             Ensure Mecurial is installed and the hg command is in the path.
51              
52             cpan install Padre::Plugin::HG
53              
54             Either open a file in an existing Mecurial project or choose Plugins > HG > Clone and enter an
55             exisiting repository to clone.
56            
57             you can clone this project it self with
58             "hg clone https://code4pay@bitbucket.org/code4pay/padre-plugin-hg/"
59              
60             Once you have a file from the project open got to Plugins > HG > View Project.
61             this will display the project tree in the left hand side bar and allow you to
62             perform operations on the files /project via the right mouse button.
63              
64             Project wide operations like pull are only available by right clicking the project root.
65            
66              
67             =head1 AUTHOR
68              
69             Michael Mueller << >>
70              
71             =head1 BUGS
72              
73             Please report any bugs or feature requests to L
74              
75              
76             =head1 COPYRIGHT & LICENSE
77              
78             Copyright 2008-2009 Michael Mueller
79             all rights reserved.
80              
81             This program is free software; you can redistribute it and/or modify it
82             under the same terms as Perl itself.
83              
84              
85              
86             =cut
87              
88              
89             #####################################################################
90             # Padre::Plugin Methods
91              
92             sub padre_interfaces {
93             'Padre::Plugin' => 0.90
94             }
95              
96             sub plugin_name {
97             'HG';
98             }
99              
100             sub menu_plugins_simple {
101             my $self = shift;
102             return $self->plugin_name => [
103             'About' => sub { $self->show_about },
104             'View Project' => sub {$self->show_statusTree},
105             'Clone' => sub {$self->show_project_clone},
106              
107             ];
108             }
109              
110             sub plugin_disable
111             {
112             require Class::Unload;
113             Class::Unload->unload('Padre::Plugin::HG::StatusTree;');
114             }
115              
116             sub padre_hooks
117             {
118             my %hooks;
119             $hooks{after_save} = \&after_save;
120             return \%hooks;
121             }
122              
123             #####################################################################
124             # Custom Methods
125              
126             sub show_about {
127             my $self = shift;
128              
129             # Generate the About dialog
130             my $about = Wx::AboutDialogInfo->new;
131             $about->SetName("Padre::Plugin::HG");
132             $about->SetDescription( <<"END_MESSAGE" );
133             Mecurial support for Padre
134             END_MESSAGE
135             $about->SetVersion( $VERSION );
136              
137             # Show the About dialog
138             Wx::AboutBox( $about );
139              
140             return;
141             }
142              
143             #
144             #vcs_commit
145             #
146             # performs the commit
147             # $self->vcs_commit($filename, $dir);
148             # will prompt for the commit message.
149             #
150              
151              
152             sub vcs_commit {
153             my ($self, $path, $dir ) = @_;
154             my $main = Padre->ide->wx->main;
155            
156             if (!$self->_project_root($path))
157             {
158             $main->error("File not in a $VCS Project", "Padre $VCS" );
159             return;
160             }
161              
162             my $message = $main->prompt("$VCS Commit of $path", "Please type in your message", "MY_".$VCS."_COMMIT");
163             if ($message) {
164            
165             my $command = eval "qq\0$VCSCommand{commit}\0";
166             my $result = $self->vcs_execute($command, $dir);
167             $main->message( $result, "$VCS Commiting $path" );
168             }
169              
170             return;
171             }
172              
173              
174             #
175             #vcs_add
176             #
177             # Adds the file to the repository
178             # $self->vcs_add($filename, $dir);
179             # will prompt for the commit message.
180             #
181              
182              
183             sub vcs_add {
184             my ($self, $path, $dir) = @_;
185             my $main = Padre->ide->wx->main;
186             my $command = eval "qq\0$VCSCommand{add}\0";
187             my $result = $self->vcs_execute($command,$dir);
188             $main->message( $result, "$VCS Adding to Repository" );
189             return;
190             }
191              
192             #
193             # vcs_diff
194             #
195             # compare the file to the repository tip
196             # $self->vcs_diff($filename, $dir);
197             # provides some basic diffing the current file agains the tip
198              
199             sub vcs_diff {
200             my ($self, $path, $dir) = @_;
201            
202             my $main = Padre->ide->wx->main;
203             my $command = eval "qq\0$VCSCommand{diff}\0";
204             return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
205             my $result = $self->vcs_execute($command, $dir);
206             return $result;
207             }
208              
209             # vcs_diff_revision
210             #
211             # compare the file to a repository revision
212             # $self->vcs_diff($filename, $dir, $revision);
213             # Revision for HG is the changeset id.
214              
215              
216             sub vcs_diff_revision {
217             my ($self, $path, $dir, $revision) = @_;
218            
219             my $main = Padre->ide->wx->main;
220             my $command = eval "qq\0$VCSCommand{diff_revision}\0";
221             return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
222             my $result = $self->vcs_execute($command, $dir);
223             return $result;
224             }
225              
226              
227              
228             # vcs_log
229             #
230             # show the commit history of the passed file.
231             # $self->vcs_commit($filename, $dir);
232             # returns a string containing the log history
233              
234              
235             sub vcs_log {
236             my ($self, $path, $dir) = @_;
237            
238             my $main = Padre->ide->wx->main;
239             my $command = eval "qq\0$VCSCommand{log}\0";
240             return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
241             my $result = $self->vcs_execute($command, $dir);
242             return $result;
243             }
244              
245              
246             #
247             #clone_project
248             #
249             # Adds the file to the repository
250             # $self->vcs_diff($repository, $destination_dir);
251             # Will clone a repository and place it in the destination dir
252             #
253              
254             sub clone_project
255             {
256             my ($self, $path, $dir) = @_;
257             my $main = Padre->ide->wx->main;
258             my $command = eval "qq\0$VCSCommand{clone}\0";
259             my $result = $self->vcs_execute($command, $dir);
260             $main->message( $result, "$VCS Cloning $path" );
261             return;
262             }
263              
264             #
265             # pull_update_project
266             #
267             # Pulls updates to a project.
268             # It will perform an update automatically on the repository
269             # $self->pull_update_project($file, $projectdir);
270             # Only pulls changes from the default repository, which is normally
271             # the one you cloned from.
272              
273             sub pull_update_project
274             {
275             my ($self, $path, $dir) = @_;
276             my $main = Padre->ide->wx->main;
277             return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
278             my $command = eval "qq\0$VCSCommand{pull}\0";
279             my $result = $self->vcs_execute($command, $dir);
280             $main->message( $result, "$VCS Cloning $path" );
281             return;
282             }
283              
284              
285             # Pushes updates to a remote repository.
286             # Prompts for the username and password.
287             # $self->push_project($file, $projectdir);
288             # Only pushes changes to the default remote repository, which is normally
289             # the one you cloned from.
290              
291              
292             sub push_project
293             {
294             my ($self, $path, $dir) = @_;
295             my $main = Padre->ide->wx->main;
296             return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
297             my $config_command = 'hg showconfig';
298             my $result1 = $self->vcs_execute($config_command, $dir); #overwriting path on purpose.
299             #overwriting path on purpose.
300             #gets the configured push path if it exists
301             ($path) = $result1 =~ /paths.default=(.*)/;
302             return $main->error('No default push path', "Padre $VCS" ) if not $path;
303             my ($default_username) = $path =~ /\/\/(.*)@/;
304             my $prompt = Padre::Plugin::HG::UserPassPrompt->new(
305             title=>'Mecurial Push',
306             default_username=>$default_username,
307             default_password =>'');
308             my $username = $prompt->{username};
309             my $password = $prompt->{password};
310             $path =~ s/\/(.*)@/\/\/$username:$password@/g;
311             my $command = eval "qq\0$VCSCommand{push}\0";
312             my $result = $self->vcs_execute($command, $dir);
313             $main->message( $result, "$VCS Pushing $path" );
314             return;
315             }
316              
317              
318              
319             # vcs_execute
320             #
321             # Executes a command after changing to the appropriate dir.
322             # $self->vcs_execute($command, $dir);
323             # All output is captured and returned as a string.
324              
325             sub vcs_execute
326             {
327             my ($self, $command, $dir) = @_;
328             print "Command $command\n";
329             my $busyCursor = Wx::BusyCursor->new();
330             my $result = capture_merged(sub{chdir($dir);system($command)});
331             if (!$result){$result = "Action Completed"}
332             $busyCursor = undef;
333             return $result;
334             }
335              
336              
337              
338              
339              
340             # show_statusTree
341             #
342             # Displays a Project Browser in the side pane. The Browser shows the status of the
343             # files in HG and gives menu options to perform actions.
344              
345              
346             sub show_statusTree
347             {
348             my ($self) = @_;
349             require Padre::Plugin::HG::StatusTree;
350             my $main = Padre->ide->wx->main;
351             my $project_root = $self->_project_root(current_filename());
352             $self->{project_path} = $project_root;
353             return $main->error("Not a $VCS Project") if !$project_root;
354             # we only want to add a tree for projects that don't already have one.
355             if (!exists($projects{$project_root}) )
356             {
357             $projects{$project_root} = Padre::Plugin::HG::StatusTree->new($self,$project_root);
358             }
359             }
360              
361              
362              
363             # close_statusTree
364             #
365             # Closes the Project Browser and deletes the Status tree object
366              
367             sub close_statusTree
368             {
369             my ($self) = @_;
370             my $project_root = $self->_project_root(current_filename());
371             if (exists($projects{$project_root}) )
372             {
373             delete $projects{$project_root} ;
374             print "deleted $project_root\n";
375             }
376             }
377              
378              
379             #
380             #
381             #show_commit_list
382             #
383             # Displays a list of all the files that are awaiting commiting. It will include
384             # not added and deleted files adding and removing them as required.
385              
386              
387             sub show_commit_list
388             {
389             my ($self) = @_;
390             my $main = Padre->ide->wx->main;
391             $self->{project_path} = $self->_project_root(current_filename());
392              
393             return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
394            
395             my $obj = Padre::Plugin::HG::ProjectCommit->showList($self);
396             $obj = undef;
397              
398             }
399              
400              
401             #
402             # show_diff
403             #
404             # Displays a list of all the files that are awaiting commiting. It will include
405             # not added and deleted files adding and removing them as required.
406              
407              
408             sub show_diff
409             {
410             my ($self, $file, $path) = @_;
411             my $main = Padre->ide->wx->main;
412             $self->{project_path} = $self->_project_root($file);
413             my $full_path = File::Spec->catdir(($path,$file));
414             return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
415             my $differences = $self->vcs_diff($file, $path);
416             Padre::Plugin::HG::DiffView->showDiff($self,$differences);
417              
418            
419              
420             }
421              
422             #show_diff_revision
423             #
424             # Displays a list of all the revisions for the selected file.
425             # Allowing you to choose one to diff the current selection to.
426              
427             sub show_diff_revision
428             {
429             my ($self, $file, $path) = @_;
430             my $main = Padre->ide->wx->main;
431             $self->{project_path} = $self->_project_root($file);
432             my $full_path = File::Spec->catdir(($path,$file));
433             return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
434             my $changeset = Padre::Plugin::HG::LogView->showList($self,$full_path);
435             my $differences = $self->vcs_diff_revision($file, $path, $changeset);
436             Padre::Plugin::HG::DiffView->showDiff($self,$differences);
437              
438              
439             }
440              
441             #show_commit_list
442             #
443             # Displays a list of all the files that are awaiting commiting. It will include
444             # not added and deleted files adding and removing them as required.
445              
446             sub show_log
447             {
448             my ($self) = @_;
449             my $main = Padre->ide->wx->main;
450             $self->{project_path} = $self->_project_root(current_filename());
451              
452             return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
453            
454             my $obj = Padre::Plugin::HG::LogView->showList($self,current_filename());
455             $obj = undef;
456              
457             }
458              
459              
460              
461              
462             #show_project_clone
463             #
464             # Dialog for project cloning
465             #
466              
467             sub show_project_clone
468             {
469             my ($self) = @_;
470             my $main = Padre->ide->wx->main;
471             my $clone = Padre::Plugin::HG::ProjectClone->new($self);
472             if ($clone->enter_repository())
473             {
474             $clone->choose_destination();
475             }
476              
477             if ($clone->project_url() and $clone->destination_dir())
478             {
479             $self->clone_project(
480             $clone->project_url(),
481             $clone->destination_dir()
482             );
483             }
484            
485            
486             }
487              
488              
489             # Event Listner for Save
490             # refresh the dir when done
491             #
492              
493             sub after_save
494             {
495             my ( $self ) = @_;
496             my $project_root = $self->_project_root(current_filename());
497             if ($projects{$project_root}){
498             $projects{$project_root}->refresh($projects{$project_root}->{treeCtrl});
499             }
500             print ("saved");
501             }
502              
503              
504              
505              
506             #
507             # _project_root
508             #
509             # $self->_project_root($filename);
510             # Calculates the project root. if the file is not in a project it
511             # will return 0
512             # otherwise it returns the fully qualified path to the project.
513              
514              
515             sub _project_root
516             {
517             my ($self, $filename) = @_;
518             if (!$filename){
519             return 0;
520             }
521             my $dir = File::Basename::dirname($filename);
522             my $project_root = $self->vcs_execute($VCSCommand{root}, $dir);
523             #file in not in a HG project.
524             if ($project_root =~ m/^abort:/)
525             {
526             $project_root = 0;
527             }
528             chomp ($project_root);
529             return $project_root;
530             }
531              
532              
533             # _get_hg_files
534             #
535             # $self->_get_hg_files(@hgStatus);
536             # Pass the output of hg status and it will give back an array
537             # each element of the array is [$status, $filename]
538              
539              
540              
541             sub _get_hg_files
542             {
543             my ($self, @hg_status) = @_;
544             my @files;
545             foreach my $line (@hg_status)
546             {
547             my ($filestatus, $path) = split(/\s/,$line);
548             push (@files, ([$filestatus,$path]));
549             }
550             return @files;
551             }
552              
553              
554             #current_filename
555             #
556             # $self->current_filename();
557             # returns the path of the file with the current attention
558             # in the ide
559              
560              
561             sub current_filename {
562              
563             my $main = Padre->ide->wx->main;
564             my $doc = $main->current->document;
565             my $filename = '';
566             if ($doc){
567             $filename = $doc->filename;
568             }
569             return $main->error("No document found") if not $filename;
570             return ($filename);
571             }
572              
573             #parse_log
574             #
575             # $self->parse_log($log);;
576             # Pass it the output of the hg log command and it will
577             # return an array of hashes with each array element
578             # being a hash of the commit values.
579             # eg changeset, user, date ...
580             #
581              
582              
583              
584             sub parse_log {
585             my ($self,$log) = @_;
586            
587             # log output looks like
588             #
589             #changeset: 3:80d72b2a4751
590             #user: bill@microsoft.com
591             #date: Fri Oct 16 07:05:27 2009 +1100
592             #summary: Added files for CPAN distribution
593             #
594             #changeset: 3:80d72b2a4751
595             #user: bill@microsoft.com
596             #date: Fri Oct 16 07:05:27 2009 +1100
597             #summary: Tricky Comment summary: CPAN distribution
598            
599             #split the output at blank lines
600             my @commits = split(/\n{2,}/, $log);
601             my $i = 0;
602             my @result;
603             foreach my $commit (@commits)
604             {
605            
606            
607             $result[$i] = {
608             changeset=>$commit =~ /^changeset:\s+(.*)/m,
609             user=>$commit=~ /^user:\s+(.*)/m,
610             date=>$commit=~ /^date:\s+(.*)/m,
611             summary=>$commit=~ /^summary:\s+(.*)/m,
612             } ;
613             $i++;
614             }
615            
616             return @result;
617             }
618              
619              
620              
621             # object_for_testing
622             #
623             # creates a blessed object so we can run our tests.
624             #
625              
626              
627             sub object_for_testing
628             {
629             my ($class) = @_;
630             my $self = {};
631             bless $self,$class;
632            
633            
634             }
635              
636             1;
637              
638             # Copyright 2008-2009 Michael Mueller.
639             # LICENSE
640             # This program is free software; you can redistribute it and/or
641             # modify it under the same terms as Perl 5 itself.
642