Git.pm 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. package SimplyGit::Git;
  2. use strict;
  3. use warnings;
  4. use Log::Log4perl qw(:easy);
  5. use lib "/usr/local/lib/";
  6. use Shellex::Shellex qw(shellex findBin);
  7. use Exporter qw(import);
  8. our @EXPORT_OK = qw(
  9. readConfig getStatus returnState addFiles
  10. commitChanges pushChanges stashAndReset resetFromUpstream
  11. updateGitIgnore appendRepoUserConfig parseSGConfig
  12. warnOnUser basicClone basicPull knocker
  13. );
  14. # TODO: Add info/debug logging for all subroutines
  15. sub knocker($$$) {
  16. my $target = shift;
  17. my $portRef = shift;
  18. my $logger = shift;
  19. my $nmapCmd = findBin("nmap",$logger);
  20. foreach my $port (@$portRef) {
  21. print "Knocking at $port\r";
  22. shellex("$nmapCmd -Pn --host_timeout 201 --max-retries 0 -p $port $target > /dev/null",$logger);
  23. }
  24. # So we don't have random chars potentially on the line after using \r
  25. print "\n";
  26. }
  27. sub checkPath($$) {
  28. my $path = shift;
  29. my $logger = shift;
  30. if ( ! -d $path ) {
  31. $logger->error("$path doesn't look like a dir, exiting...");
  32. exit 1;
  33. }
  34. }
  35. sub warnOnUser($$$) {
  36. my $user = shift;
  37. my $email = shift;
  38. my $logger = shift;
  39. my $gitCmd = findBin("git",$logger);
  40. my $configuredUser = shellex("$gitCmd config --get user.name",$logger);
  41. my $configuredEmail = shellex("$gitCmd config --get user.email",$logger);
  42. if ( $configuredUser ne $user || $configuredEmail ne $email ) {
  43. print "***************\n";
  44. print "Your configured user/email don't match what you declared in the config file!\n";
  45. print "Desired User: $user\nConfigured User: $configuredUser\nDesired Email: $email\nConfigured Email: $configuredEmail\n";
  46. print "***************\n";
  47. }
  48. }
  49. # https://perlmaven.com/trim
  50. sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
  51. sub parseSGConfig($$) {
  52. my $config = shift;
  53. my $logger = shift;
  54. if ( ! -e $config ) {
  55. $logger->error("$config doesn't look like a regular file, exiting...");
  56. exit 1;
  57. }
  58. my $catCmd = findBin("cat",$logger);
  59. my @configLines = split("\n",shellex("$catCmd $config",$logger));
  60. my %configHash;
  61. foreach my $line ( @configLines ) {
  62. chomp $line;
  63. if ( $line =~ m/^(.*)\ =\ "(.*)"$/ ) {
  64. $configHash{$1} = $2;
  65. }
  66. if ( $line =~ m/^(.*)\ =\ \[(.*)\]/ ) {
  67. my @trimmedPorts;
  68. my @ports = split(",",$2);
  69. foreach my $port (@ports) {
  70. $port =~ /(\d{1,5})/;
  71. push(@trimmedPorts,trim($1));
  72. }
  73. $configHash{$1} = \@trimmedPorts;
  74. }
  75. }
  76. return %configHash;
  77. }
  78. sub returnConfigPath($$) {
  79. my $path = shift;
  80. my $logger = shift;
  81. checkPath($path,$logger);
  82. my $gitConfigPath = $path . "/" . ".git/config";
  83. return $gitConfigPath;
  84. }
  85. sub readConfig($$) {
  86. # This sub is probably not really needed for what I'm trying to do
  87. # git itself already parses this config...but an interesting exercise non the less
  88. # and may be useful later
  89. my $path = shift;
  90. my $logger = shift;
  91. my $gitConfigPath = returnConfigPath($path,$logger);
  92. my $catCmd = findBin("cat",$logger);
  93. my @configLines = split("\n",shellex("$catCmd $gitConfigPath",$logger));
  94. # Key is config header, value is hash ref containing config values
  95. my %gitConfig;
  96. my @valueLines;
  97. my $lineCounter = 0;
  98. foreach my $line ( @configLines ) {
  99. $lineCounter++;
  100. #if ( $line =~ m/\[(.*)\]/ ) {
  101. if ( $line =~ m/\[(.*)\]/ ) {
  102. #$valueLine =~ /\t(.*)\ =\ (.*)$/;
  103. $gitConfig{$1} = "";
  104. }
  105. }
  106. # Tag each line with it's heading
  107. # Only way I could think of that worked to solve how this
  108. # There are almost certainly better ways
  109. my @taggedLines;
  110. my $tag = "NULLTAG";
  111. foreach my $line ( @configLines ) {
  112. if ( $line =~ m/\[(.*)\]/ ) {
  113. $tag = $1;
  114. } else {
  115. my $newLine = $tag . $line;
  116. push(@taggedLines,$newLine);
  117. }
  118. }
  119. # Get all of the tagged lines into a hash structure.
  120. foreach my $key ( keys %gitConfig ) {
  121. my %stash;
  122. foreach my $tl ( @taggedLines ) {
  123. if ( $tl =~ m/^($key)/ ) {
  124. $tl =~ s/^($key)//g;
  125. $tl =~ m/^\t(.*)\ \=\ (.*)$/;
  126. my $confKey = $1;
  127. my $confVal = $2;
  128. $stash{$confKey} = $confVal;
  129. }
  130. }
  131. $gitConfig{$key} = \%stash;
  132. }
  133. return %gitConfig;
  134. }
  135. sub getStatus($) {
  136. my $logger = shift;
  137. my $gitCmd = findBin("git",$logger);
  138. my $status = shellex("$gitCmd status -uall --porcelain",$logger);
  139. chomp $status;
  140. return $status;
  141. }
  142. sub returnState($) {
  143. my $logger = shift;
  144. my $gitCmd = findBin("git",$logger);
  145. my $currentStatus = getStatus($logger);
  146. my @statusLines = split("\n", $currentStatus);
  147. my @untracked;
  148. my @modified;
  149. my @added;
  150. my @deleted;
  151. foreach my $file ( @statusLines ) {
  152. $file =~ m/^\ {0,1}([A-Z?]{1,2})\ {1,2}(.*)/;
  153. my $fileAttrs = $1;
  154. my $filename = $2;
  155. my @attrs = split("",$fileAttrs);
  156. foreach my $attr ( @attrs ) {
  157. if ( $attr =~ m/\?/ ) {
  158. push(@untracked, $filename) unless grep $_ eq $filename, @untracked;
  159. }
  160. if ( $attr =~ m/[M]/ ) {
  161. push(@modified, $filename) unless grep $_ eq $filename, @modified;
  162. }
  163. if ( $attr =~ m/[A]/ ) {
  164. push(@added, $filename) unless grep $_ eq $filename, @added;
  165. }
  166. if ( $attr =~ m/[D]/ ) {
  167. push(@deleted, $filename) unless grep $_ eq $filename, @deleted;
  168. }
  169. }
  170. }
  171. return ( \@untracked, \@modified, \@added, \@deleted );
  172. }
  173. sub addFiles($$) {
  174. my $filesToAddRef = shift;
  175. my $logger = shift;
  176. my $gitCmd = findBin("git",$logger);
  177. foreach my $file ( @$filesToAddRef ) {
  178. shellex("$gitCmd add $file",$logger);
  179. }
  180. }
  181. sub commitChanges($$) {
  182. my $commitMsg = shift;
  183. chomp $commitMsg;
  184. my $logger = shift;
  185. my $gitCmd = findBin("git",$logger);
  186. shellex("$gitCmd commit -m \"$commitMsg\"",$logger);
  187. }
  188. sub pushChanges($) {
  189. my $logger = shift;
  190. my $gitCmd = findBin("git",$logger);
  191. my $output = shellex("$gitCmd push",$logger);
  192. }
  193. sub dropStash($) {
  194. my $logger = shift;
  195. my $gitCmd = findBin("git",$logger);
  196. my @stashList = split("\n", shellex("$gitCmd stash list",$logger));
  197. my $stashCount = scalar @stashList;
  198. # TODO: Don't need $stashCount, should just be able to iterate over @stashList
  199. if ( scalar @stashList == 0 ) {
  200. print "Stash is empty so not dropping\n";
  201. } else {
  202. foreach my $stashNum ( 1..$stashCount ) {
  203. shellex("$gitCmd stash drop 0",$logger);
  204. }
  205. }
  206. }
  207. sub stashAndReset($) {
  208. my $logger = shift;
  209. my $gitCmd = findBin("git",$logger);
  210. shellex("$gitCmd stash",$logger);
  211. dropStash($logger);
  212. shellex("$gitCmd rebase",$logger);
  213. }
  214. sub resetFromUpstream($) {
  215. # git stash and git reset --hard and git pull ? I think
  216. # git reset upstream/master; git stash
  217. my $logger = shift;
  218. my $gitCmd = findBin("git",$logger);
  219. my $upstream = shellex("$gitCmd config --get remote.upstream.url",$logger);
  220. if ( $upstream eq "" || ! defined $upstream ) {
  221. print "Upstream not configured, exiting\n";
  222. exit 1;
  223. }
  224. shellex("$gitCmd reset upstream/master",$logger);
  225. shellex("$gitCmd stash",$logger);
  226. dropStash($logger);
  227. print "Successful reset from upstream\n";
  228. print "Changes have not been pushed, run \'$gitCmd pull\' to revert\n";
  229. }
  230. sub updateGitIgnore($$$) {
  231. my $path = shift;
  232. # Maybe better to accept an array of values
  233. my $ignoreValue = shift;
  234. my $logger = shift;
  235. checkPath($path,$logger);
  236. my $filename = $path . "/" . ".gitignore";
  237. # Make sure we're not appending/writing if entry already exists in gitignore
  238. if ( -f $filename ) {
  239. my $catCmd = findBin("cat",$logger);
  240. my @ignoreLines = split("\n",shellex("$catCmd $filename",$logger));
  241. if ( ! grep( /^$ignoreValue$/, @ignoreLines ) ) {
  242. open(my $fh, ">>", $filename) or die $logger->error("Couldn't open $filename, exiting...");
  243. chomp $ignoreValue;
  244. print $fh "$ignoreValue\n";
  245. close $fh;
  246. }
  247. } else {
  248. open(my $fh, ">", $filename) or die $logger->error("Couldn't open $filename, exiting...");
  249. chomp $ignoreValue;
  250. print $fh "$ignoreValue\n";
  251. close $fh;
  252. }
  253. }
  254. sub appendRepoUserConfig($$$) {
  255. my $desiredName = shift;
  256. my $desiredEmail = shift;
  257. my $logger = shift;
  258. my $gitCmd = findBin("git",$logger);
  259. my $currentName = shellex("$gitCmd config --get user.name",$logger);
  260. chomp $currentName;
  261. my $currentEmail = shellex("$gitCmd config --get user.email",$logger);
  262. chomp $currentEmail;
  263. if ( $currentName eq $desiredName ) {
  264. print "Already have $desiredName configured\n";
  265. } else {
  266. shellex("$gitCmd config user.name \"$desiredName\"",$logger);
  267. print "Set $desiredName successfully\n";
  268. }
  269. if ( $currentEmail eq $desiredEmail ) {
  270. print "Already have $desiredEmail configured\n";
  271. } else {
  272. shellex("$gitCmd config user.email \"$desiredEmail\"",$logger);
  273. print "Set $desiredEmail successfully\n";
  274. }
  275. }
  276. sub basicClone($$) {
  277. my $cloneTarget = shift;
  278. my $logger = shift;
  279. my $gitCmd = findBin("git",$logger);
  280. shellex("$gitCmd clone $cloneTarget",$logger);
  281. print "Successfully cloned $cloneTarget\n";
  282. }
  283. sub basicPull($) {
  284. my $logger = shift;
  285. my $gitCmd = findBin("git",$logger);
  286. my $gitPullReturn = shellex("$gitCmd pull",$logger);
  287. print "git pull returned:\n$gitPullReturn\n";
  288. }