PageRenderTime 58ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/blib/lib/Padre/Plugin/HG.pm

https://bitbucket.org/code4pay/padre-plugin-hg
Perl | 597 lines | 282 code | 130 blank | 185 comment | 20 complexity | 8ce2b042c1542f5c90575615d0e942d9 MD5 | raw file
  1. package Padre::Plugin::HG;
  2. use 5.008;
  3. use warnings;
  4. use strict;
  5. use Padre::Config ();
  6. use Padre::Wx ();
  7. use Padre::Plugin ();
  8. use Padre::Util ();
  9. use Capture::Tiny qw(capture_merged);
  10. use File::Basename ();
  11. use File::Spec;
  12. use Padre::Plugin::HG::ProjectCommit;
  13. use Padre::Plugin::HG::ProjectClone;
  14. use Padre::Plugin::HG::UserPassPrompt;
  15. use Padre::Plugin::HG::DiffView;
  16. use Padre::Plugin::HG::LogView;
  17. my %projects;
  18. our $VERSION = '0.16';
  19. our @ISA = 'Padre::Plugin';
  20. my $VCS = "Mercurial";
  21. # enter the vcs commands here, variables will be evaled in in the sub routines.
  22. # was meant as a way to make it more generic. Not sure it is going to
  23. # succeed.
  24. my %VCSCommand = ( commit => 'hg commit -A -m"$message" $path ',
  25. add => 'hg add $path',
  26. status =>'hg status --all $path',
  27. root => 'hg root',
  28. diff => 'hg diff $path',
  29. diff_revision => 'hg diff -r $revision $path',
  30. clone=> 'hg clone $path',
  31. pull =>'hg pull --update --noninteractive ',
  32. push =>'hg push $path',
  33. log =>'hg log $path');
  34. =pod
  35. =head1 NAME
  36. Padre::Plugin::HG - Mecurial interface for Padre
  37. =head1 Instructions
  38. Ensure Mecurial is installed and the hg command is in the path.
  39. cpan install Padre::Plugin::HG
  40. Either open a file in an existing Mecurial project or choose Plugins > HG > Clone and enter an
  41. exisiting repository to clone.
  42. you can clone this project it self with
  43. "hg clone https://code4pay@bitbucket.org/code4pay/padre-plugin-hg/"
  44. Once you have a file from the project open got to Plugins > HG > View Project.
  45. this will display the project tree in the left hand side bar and allow you to
  46. perform operations on the files /project via the right mouse button.
  47. Project wide operations like pull are only available by right clicking the project root.
  48. =head1 AUTHOR
  49. Michael Mueller << <michael at muellers.net.au> >>
  50. =head1 BUGS
  51. Please report any bugs or feature requests to L<http://padre.perlide.org/>
  52. =head1 COPYRIGHT & LICENSE
  53. Copyright 2008-2009 Michael Mueller
  54. all rights reserved.
  55. This program is free software; you can redistribute it and/or modify it
  56. under the same terms as Perl itself.
  57. =cut
  58. #####################################################################
  59. # Padre::Plugin Methods
  60. sub padre_interfaces {
  61. 'Padre::Plugin' => 0.72
  62. }
  63. sub plugin_name {
  64. 'HG';
  65. }
  66. sub menu_plugins_simple {
  67. my $self = shift;
  68. return $self->plugin_name => [
  69. 'About' => sub { $self->show_about },
  70. 'View Project' => sub {$self->show_statusTree},
  71. 'Clone' => sub {$self->show_project_clone},
  72. ];
  73. }
  74. sub plugin_disable
  75. {
  76. require Class::Unload;
  77. Class::Unload->unload('Padre::Plugin::HG::StatusTree;');
  78. }
  79. #####################################################################
  80. # Custom Methods
  81. sub show_about {
  82. my $self = shift;
  83. # Generate the About dialog
  84. my $about = Wx::AboutDialogInfo->new;
  85. $about->SetName("Padre::Plugin::HG");
  86. $about->SetDescription( <<"END_MESSAGE" );
  87. Mecurial support for Padre
  88. END_MESSAGE
  89. $about->SetVersion( $VERSION );
  90. # Show the About dialog
  91. Wx::AboutBox( $about );
  92. return;
  93. }
  94. #
  95. #vcs_commit
  96. #
  97. # performs the commit
  98. # $self->vcs_commit($filename, $dir);
  99. # will prompt for the commit message.
  100. #
  101. sub vcs_commit {
  102. my ($self, $path, $dir ) = @_;
  103. my $main = Padre->ide->wx->main;
  104. if (!$self->_project_root($path))
  105. {
  106. $main->error("File not in a $VCS Project", "Padre $VCS" );
  107. return;
  108. }
  109. my $message = $main->prompt("$VCS Commit of $path", "Please type in your message", "MY_".$VCS."_COMMIT");
  110. if ($message) {
  111. my $command = eval "qq\0$VCSCommand{commit}\0";
  112. my $result = $self->vcs_execute($command, $dir);
  113. $main->message( $result, "$VCS Commiting $path" );
  114. }
  115. return;
  116. }
  117. #
  118. #vcs_add
  119. #
  120. # Adds the file to the repository
  121. # $self->vcs_add($filename, $dir);
  122. # will prompt for the commit message.
  123. #
  124. sub vcs_add {
  125. my ($self, $path, $dir) = @_;
  126. my $main = Padre->ide->wx->main;
  127. my $command = eval "qq\0$VCSCommand{add}\0";
  128. my $result = $self->vcs_execute($command,$dir);
  129. $main->message( $result, "$VCS Adding to Repository" );
  130. return;
  131. }
  132. #
  133. # vcs_diff
  134. #
  135. # compare the file to the repository tip
  136. # $self->vcs_diff($filename, $dir);
  137. # provides some basic diffing the current file agains the tip
  138. sub vcs_diff {
  139. my ($self, $path, $dir) = @_;
  140. my $main = Padre->ide->wx->main;
  141. my $command = eval "qq\0$VCSCommand{diff}\0";
  142. return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
  143. my $result = $self->vcs_execute($command, $dir);
  144. return $result;
  145. }
  146. # vcs_diff_revision
  147. #
  148. # compare the file to a repository revision
  149. # $self->vcs_diff($filename, $dir, $revision);
  150. # Revision for HG is the changeset id.
  151. sub vcs_diff_revision {
  152. my ($self, $path, $dir, $revision) = @_;
  153. my $main = Padre->ide->wx->main;
  154. my $command = eval "qq\0$VCSCommand{diff_revision}\0";
  155. return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
  156. my $result = $self->vcs_execute($command, $dir);
  157. return $result;
  158. }
  159. # vcs_log
  160. #
  161. # show the commit history of the passed file.
  162. # $self->vcs_commit($filename, $dir);
  163. # returns a string containing the log history
  164. sub vcs_log {
  165. my ($self, $path, $dir) = @_;
  166. my $main = Padre->ide->wx->main;
  167. my $command = eval "qq\0$VCSCommand{log}\0";
  168. return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
  169. my $result = $self->vcs_execute($command, $dir);
  170. return $result;
  171. }
  172. #
  173. #clone_project
  174. #
  175. # Adds the file to the repository
  176. # $self->vcs_diff($repository, $destination_dir);
  177. # Will clone a repository and place it in the destination dir
  178. #
  179. sub clone_project
  180. {
  181. my ($self, $path, $dir) = @_;
  182. my $main = Padre->ide->wx->main;
  183. my $command = eval "qq\0$VCSCommand{clone}\0";
  184. my $result = $self->vcs_execute($command, $dir);
  185. $main->message( $result, "$VCS Cloning $path" );
  186. return;
  187. }
  188. #
  189. # pull_update_project
  190. #
  191. # Pulls updates to a project.
  192. # It will perform an update automatically on the repository
  193. # $self->pull_update_project($file, $projectdir);
  194. # Only pulls changes from the default repository, which is normally
  195. # the one you cloned from.
  196. sub pull_update_project
  197. {
  198. my ($self, $path, $dir) = @_;
  199. my $main = Padre->ide->wx->main;
  200. return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
  201. my $command = eval "qq\0$VCSCommand{pull}\0";
  202. my $result = $self->vcs_execute($command, $dir);
  203. $main->message( $result, "$VCS Cloning $path" );
  204. return;
  205. }
  206. # Pushes updates to a remote repository.
  207. # Prompts for the username and password.
  208. # $self->push_project($file, $projectdir);
  209. # Only pushes changes to the default remote repository, which is normally
  210. # the one you cloned from.
  211. sub push_project
  212. {
  213. my ($self, $path, $dir) = @_;
  214. my $main = Padre->ide->wx->main;
  215. return $main->error('File not in a $VCS Project', "Padre $VCS" ) if not $self->_project_root($path);
  216. my $config_command = 'hg showconfig';
  217. my $result1 = $self->vcs_execute($config_command, $dir); #overwriting path on purpose.
  218. #overwriting path on purpose.
  219. #gets the configured push path if it exists
  220. ($path) = $result1 =~ /paths.default=(.*)/;
  221. return $main->error('No default push path', "Padre $VCS" ) if not $path;
  222. my ($default_username) = $path =~ /\/\/(.*)@/;
  223. my $prompt = Padre::Plugin::HG::UserPassPrompt->new(
  224. title=>'Mecurial Push',
  225. default_username=>$default_username,
  226. default_password =>'');
  227. my $username = $prompt->{username};
  228. my $password = $prompt->{password};
  229. $path =~ s/\/(.*)@/\/\/$username:$password@/g;
  230. my $command = eval "qq\0$VCSCommand{push}\0";
  231. my $result = $self->vcs_execute($command, $dir);
  232. $main->message( $result, "$VCS Pushing $path" );
  233. return;
  234. }
  235. # vcs_execute
  236. #
  237. # Executes a command after changing to the appropriate dir.
  238. # $self->vcs_execute($command, $dir);
  239. # All output is captured and returned as a string.
  240. sub vcs_execute
  241. {
  242. my ($self, $command, $dir) = @_;
  243. print "Command $command\n";
  244. my $busyCursor = Wx::BusyCursor->new();
  245. my $result = capture_merged(sub{chdir($dir);system($command)});
  246. if (!$result){$result = "Action Completed"}
  247. $busyCursor = undef;
  248. return $result;
  249. }
  250. # show_statusTree
  251. #
  252. # Displays a Project Browser in the side pane. The Browser shows the status of the
  253. # files in HG and gives menu options to perform actions.
  254. sub show_statusTree
  255. {
  256. my ($self) = @_;
  257. require Padre::Plugin::HG::StatusTree;
  258. my $main = Padre->ide->wx->main;
  259. my $project_root = $self->_project_root(current_filename());
  260. $self->{project_path} = $project_root;
  261. return $main->error("Not a $VCS Project") if !$project_root;
  262. # we only want to add a tree for projects that don't already have one.
  263. if (!exists($projects{$project_root}) )
  264. {
  265. $projects{$project_root} = Padre::Plugin::HG::StatusTree->new($self,$project_root);
  266. }
  267. }
  268. #
  269. #
  270. #show_commit_list
  271. #
  272. # Displays a list of all the files that are awaiting commiting. It will include
  273. # not added and deleted files adding and removing them as required.
  274. sub show_commit_list
  275. {
  276. my ($self) = @_;
  277. my $main = Padre->ide->wx->main;
  278. $self->{project_path} = $self->_project_root(current_filename());
  279. return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
  280. my $obj = Padre::Plugin::HG::ProjectCommit->showList($self);
  281. $obj = undef;
  282. }
  283. #
  284. # show_diff
  285. #
  286. # Displays a list of all the files that are awaiting commiting. It will include
  287. # not added and deleted files adding and removing them as required.
  288. sub show_diff
  289. {
  290. my ($self, $file, $path) = @_;
  291. my $main = Padre->ide->wx->main;
  292. $self->{project_path} = $self->_project_root($file);
  293. my $full_path = File::Spec->catdir(($path,$file));
  294. return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
  295. my $differences = $self->vcs_diff($file, $path);
  296. Padre::Plugin::HG::DiffView->showDiff($self,$differences);
  297. }
  298. #show_diff_revision
  299. #
  300. # Displays a list of all the revisions for the selected file.
  301. # Allowing you to choose one to diff the current selection to.
  302. sub show_diff_revision
  303. {
  304. my ($self, $file, $path) = @_;
  305. my $main = Padre->ide->wx->main;
  306. $self->{project_path} = $self->_project_root($file);
  307. my $full_path = File::Spec->catdir(($path,$file));
  308. return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
  309. my $changeset = Padre::Plugin::HG::LogView->showList($self,$full_path);
  310. my $differences = $self->vcs_diff_revision($file, $path, $changeset);
  311. Padre::Plugin::HG::DiffView->showDiff($self,$differences);
  312. }
  313. #show_commit_list
  314. #
  315. # Displays a list of all the files that are awaiting commiting. It will include
  316. # not added and deleted files adding and removing them as required.
  317. sub show_log
  318. {
  319. my ($self) = @_;
  320. my $main = Padre->ide->wx->main;
  321. $self->{project_path} = $self->_project_root(current_filename());
  322. return $main->error("Not a $VCS Project") if ! $self->{project_path} ;
  323. my $obj = Padre::Plugin::HG::LogView->showList($self,current_filename());
  324. $obj = undef;
  325. }
  326. #show_project_clone
  327. #
  328. # Dialog for project cloning
  329. #
  330. sub show_project_clone
  331. {
  332. my ($self) = @_;
  333. my $main = Padre->ide->wx->main;
  334. my $clone = Padre::Plugin::HG::ProjectClone->new($self);
  335. if ($clone->enter_repository())
  336. {
  337. $clone->choose_destination();
  338. }
  339. if ($clone->project_url() and $clone->destination_dir())
  340. {
  341. $self->clone_project(
  342. $clone->project_url(),
  343. $clone->destination_dir()
  344. );
  345. }
  346. }
  347. #
  348. # _project_root
  349. #
  350. # $self->_project_root($filename);
  351. # Calculates the project root. if the file is not in a project it
  352. # will return 0
  353. # otherwise it returns the fully qualified path to the project.
  354. sub _project_root
  355. {
  356. my ($self, $filename) = @_;
  357. my $dir = File::Basename::dirname($filename);
  358. my $project_root = $self->vcs_execute($VCSCommand{root}, $dir);
  359. #file in not in a HG project.
  360. if ($project_root =~ m/^abort:/)
  361. {
  362. $project_root = 0;
  363. }
  364. chomp ($project_root);
  365. return $project_root;
  366. }
  367. # _get_hg_files
  368. #
  369. # $self->_get_hg_files(@hgStatus);
  370. # Pass the output of hg status and it will give back an array
  371. # each element of the array is [$status, $filename]
  372. sub _get_hg_files
  373. {
  374. my ($self, @hg_status) = @_;
  375. my @files;
  376. foreach my $line (@hg_status)
  377. {
  378. my ($filestatus, $path) = split(/\s/,$line);
  379. push (@files, ([$filestatus,$path]));
  380. }
  381. return @files;
  382. }
  383. #current_filename
  384. #
  385. # $self->current_filename();
  386. # returns the path of the file with the current attention
  387. # in the ide.
  388. sub current_filename {
  389. my $main = Padre->ide->wx->main;
  390. my $doc = $main->current->document;
  391. my $filename = $doc->filename;
  392. return $main->error("No document found") if not $filename;
  393. return ($filename);
  394. }
  395. #parse_log
  396. #
  397. # $self->parse_log($log);;
  398. # Pass it the output of the hg log command and it will
  399. # return an array of hashes with each array element
  400. # being a hash of the commit values.
  401. # eg changeset, user, date ...
  402. #
  403. sub parse_log {
  404. my ($self,$log) = @_;
  405. # log output looks like
  406. #
  407. #changeset: 3:80d72b2a4751
  408. #user: bill@microsoft.com
  409. #date: Fri Oct 16 07:05:27 2009 +1100
  410. #summary: Added files for CPAN distribution
  411. #
  412. #changeset: 3:80d72b2a4751
  413. #user: bill@microsoft.com
  414. #date: Fri Oct 16 07:05:27 2009 +1100
  415. #summary: Tricky Comment summary: CPAN distribution
  416. #split the output at blank lines
  417. my @commits = split(/\n{2,}/, $log);
  418. my $i = 0;
  419. my @result;
  420. foreach my $commit (@commits)
  421. {
  422. $result[$i] = {
  423. changeset=>$commit =~ /^changeset:\s+(.*)/m,
  424. user=>$commit=~ /^user:\s+(.*)/m,
  425. date=>$commit=~ /^date:\s+(.*)/m,
  426. summary=>$commit=~ /^summary:\s+(.*)/m,
  427. } ;
  428. $i++;
  429. }
  430. return @result;
  431. }
  432. # object_for_testing
  433. #
  434. # creates a blessed object so we can run our tests.
  435. #
  436. sub object_for_testing
  437. {
  438. my ($class) = @_;
  439. my $self = {};
  440. bless $self,$class;
  441. }
  442. 1;
  443. # Copyright 2008-2009 Michael Mueller.
  444. # LICENSE
  445. # This program is free software; you can redistribute it and/or
  446. # modify it under the same terms as Perl 5 itself.