Git.pm 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package SimplyGit::Git;
  2. use strict;
  3. use warnings;
  4. use Log::Log4perl qw(:easy);
  5. use lib ".";
  6. use SimplyGit::Shellex qw(shellex findBin);
  7. use Exporter qw(import);
  8. our @EXPORT_OK = qw(readConfig getStatus returnState addFiles commitChanges pushChanges stashAndReset resetFromUpstream updateGitIgnore appendRepoUserConfig);
  9. # TODO: Handle "optional" passing of logger object
  10. # Need to identify which subs are called before the logger object is initialized ( if any )
  11. # and then required the logger object arg for all who don't fit that usecase
  12. # TODO: Add info/debug logging for all subroutines
  13. sub checkPath($$) {
  14. my $path = shift;
  15. my $logger = shift;
  16. if ( ! -d $path ) {
  17. $logger->error("$path doesn't look like a dir, exiting...");
  18. exit 1;
  19. }
  20. }
  21. sub returnConfigPath($$) {
  22. my $path = shift;
  23. my $logger = shift;
  24. checkPath($path,$logger);
  25. my $gitConfigPath = $path . "/" . ".git/config";
  26. return $gitConfigPath;
  27. }
  28. sub readConfig($$) {
  29. # This sub is probably not really needed for what I'm trying to do
  30. # git itself already parses this config...but an interesting exercise non the less
  31. # and may be useful later
  32. my $path = shift;
  33. my $logger = shift;
  34. my $gitConfigPath = returnConfigPath($path,$logger);
  35. my $catCmd = findBin("cat",$logger);
  36. my @configLines = split("\n",shellex("$catCmd $gitConfigPath",$logger));
  37. # Key is config header, value is hash ref containing config values
  38. my %gitConfig;
  39. my @valueLines;
  40. my $lineCounter = 0;
  41. foreach my $line ( @configLines ) {
  42. $lineCounter++;
  43. #if ( $line =~ m/\[(.*)\]/ ) {
  44. if ( $line =~ m/\[(.*)\]/ ) {
  45. #$valueLine =~ /\t(.*)\ =\ (.*)$/;
  46. $gitConfig{$1} = "";
  47. }
  48. }
  49. # Tag each line with it's heading
  50. # Only way I could think of that worked to solve how this
  51. # There are almost certainly better ways
  52. # TODO
  53. my @taggedLines;
  54. my $tag = "NULLTAG";
  55. foreach my $line ( @configLines ) {
  56. if ( $line =~ m/\[(.*)\]/ ) {
  57. $tag = $1;
  58. } else {
  59. my $newLine = $tag . $line;
  60. push(@taggedLines,$newLine);
  61. }
  62. }
  63. # Get all of the tagged lines into a hash structure.
  64. foreach my $key ( keys %gitConfig ) {
  65. my %stash;
  66. foreach my $tl ( @taggedLines ) {
  67. if ( $tl =~ m/^($key)/ ) {
  68. $tl =~ s/^($key)//g;
  69. $tl =~ m/^\t(.*)\ \=\ (.*)$/;
  70. my $confKey = $1;
  71. my $confVal = $2;
  72. $stash{$confKey} = $confVal;
  73. }
  74. }
  75. $gitConfig{$key} = \%stash;
  76. }
  77. return %gitConfig;
  78. }
  79. sub getStatus($) {
  80. my $logger = shift;
  81. my $gitCmd = findBin("git",$logger);
  82. my $status = shellex("$gitCmd status -uall --porcelain",$logger);
  83. chomp $status;
  84. return $status;
  85. }
  86. sub returnState($) {
  87. my $logger = shift;
  88. my $gitCmd = findBin("git",$logger);
  89. my $currentStatus = getStatus($logger);
  90. my @statusLines = split("\n", $currentStatus);
  91. my @untracked;
  92. my @modified;
  93. my @added;
  94. my @deleted;
  95. foreach my $file ( @statusLines ) {
  96. $file =~ m/^\ {0,1}([A-Z?]{1,2})\ {1,2}(.*)/;
  97. my $fileAttrs = $1;
  98. my $filename = $2;
  99. my @attrs = split("",$fileAttrs);
  100. foreach my $attr ( @attrs ) {
  101. if ( $attr =~ m/\?/ ) {
  102. push(@untracked, $filename) unless grep $_ eq $filename, @untracked;
  103. }
  104. if ( $attr =~ m/[M]/ ) {
  105. push(@modified, $filename) unless grep $_ eq $filename, @modified;
  106. }
  107. if ( $attr =~ m/[A]/ ) {
  108. push(@added, $filename) unless grep $_ eq $filename, @added;
  109. }
  110. if ( $attr =~ m/[D]/ ) {
  111. push(@deleted, $filename) unless grep $_ eq $filename, @deleted;
  112. }
  113. }
  114. }
  115. return ( \@untracked, \@modified, \@added, \@deleted );
  116. }
  117. sub addFiles($$) {
  118. my $filesToAddRef = shift;
  119. my $logger = shift;
  120. my $gitCmd = findBin("git",$logger);
  121. foreach my $file ( @$filesToAddRef ) {
  122. shellex("$gitCmd add $file",$logger);
  123. }
  124. }
  125. # TODO: Possibly worth returning output for commitChanges(), pushChanges(), stashAndReset, even if I'm not using it right now
  126. sub commitChanges($$) {
  127. my $commitMsg = shift;
  128. chomp $commitMsg;
  129. my $logger = shift;
  130. my $gitCmd = findBin("git",$logger);
  131. shellex("$gitCmd commit -m \"$commitMsg\"",$logger);
  132. }
  133. sub pushChanges($) {
  134. my $logger = shift;
  135. my $gitCmd = findBin("git",$logger);
  136. my $output = shellex("$gitCmd push",$logger);
  137. }
  138. sub dropStash($) {
  139. my $logger = shift;
  140. my $gitCmd = findBin("git",$logger);
  141. my @stashList = split("\n", shellex("$gitCmd stash list",$logger));
  142. my $stashCount = scalar @stashList;
  143. # TODO: Don't need $stashCount, should just be able to iterate over @stashList
  144. if ( scalar @stashList == 0 ) {
  145. print "Stash is empty so not dropping\n";
  146. } else {
  147. foreach my $stashNum ( 1..$stashCount ) {
  148. shellex("$gitCmd stash drop 0",$logger);
  149. }
  150. }
  151. }
  152. sub stashAndReset($) {
  153. my $logger = shift;
  154. my $gitCmd = findBin("git",$logger);
  155. shellex("$gitCmd stash",$logger);
  156. dropStash($logger);
  157. # TODO: Depending on use case need to do more here
  158. shellex("$gitCmd rebase",$logger);
  159. }
  160. sub resetFromUpstream($) {
  161. # git stash and git reset --hard and git pull ? I think
  162. # git reset upstream/master; git stash
  163. my $logger = shift;
  164. my $gitCmd = findBin("git",$logger);
  165. my $upstream = shellex("$gitCmd config --get remote.upstream.url",$logger);
  166. if ( $upstream eq "" || ! defined $upstream ) {
  167. print "Upstream not configured, exiting\n";
  168. exit 1;
  169. }
  170. shellex("$gitCmd reset upstream/master",$logger);
  171. shellex("$gitCmd stash",$logger);
  172. dropStash($logger);
  173. print "Successful reset from upstream\n";
  174. print "Changes have not been pushed, run \'$gitCmd pull\' to revert\n";
  175. }
  176. sub updateGitIgnore($$$) {
  177. my $path = shift;
  178. # Maybe better to accept an array of values
  179. my $ignoreValue = shift;
  180. my $logger = shift;
  181. checkPath($path,$logger);
  182. my $filename = $path . "/" . ".gitignore";
  183. # Make sure we're not appending/writing if entry already exists in gitignore
  184. if ( -f $filename ) {
  185. my $catCmd = findBin("cat",$logger);
  186. my @ignoreLines = split("\n",shellex("$catCmd $filename",$logger));
  187. if ( ! grep( /^$ignoreValue$/, @ignoreLines ) ) {
  188. # TODO: What if logger object is not passed in?
  189. open(my $fh, ">>", $filename) or die $logger->error("Couldn't open $filename, exiting...");
  190. chomp $ignoreValue;
  191. print $fh "$ignoreValue\n";
  192. close $fh;
  193. }
  194. } else {
  195. open(my $fh, ">", $filename) or die $logger->error("Couldn't open $filename, exiting...");
  196. chomp $ignoreValue;
  197. print $fh "$ignoreValue\n";
  198. close $fh;
  199. }
  200. }
  201. sub appendRepoUserConfig($$$) {
  202. my $desiredName = shift;
  203. my $desiredEmail = shift;
  204. my $logger = shift;
  205. my $gitCmd = findBin("git",$logger);
  206. my $currentName = shellex("$gitCmd config --get user.name",$logger);
  207. chomp $currentName;
  208. my $currentEmail = shellex("$gitCmd config --get user.email",$logger);
  209. chomp $currentEmail;
  210. if ( $currentName eq $desiredName ) {
  211. print "Already have $desiredName configured\n";
  212. } else {
  213. shellex("$gitCmd config user.name \"$desiredName\"",$logger);
  214. print "Set $desiredName successfully\n";
  215. }
  216. if ( $currentEmail eq $desiredEmail ) {
  217. print "Already have $desiredEmail configured\n";
  218. } else {
  219. shellex("$gitCmd config user.email \"$desiredEmail\"",$logger);
  220. print "Set $desiredEmail successfully\n";
  221. }
  222. }