https://git.spwbk.site/swatson/simply-git/raw/master/lib/SimplyGit/Git.pm
___________________________________
package SimplyGit::Git;
use strict;
use warnings;
use Log::Log4perl qw(:easy);
use lib "/usr/local/lib/";
use Shellex::Shellex qw(shellex findBin);
use Exporter qw(import);
our @EXPORT_OK = qw(
       readConfig getStatus returnState addFiles
       commitChanges pushChanges stashAndReset resetFromUpstream
       updateGitIgnore appendRepoUserConfig parseSGConfig
       warnOnUser basicClone basicPull knocker
);

# TODO: Add info/debug logging for all subroutines

sub knocker($$$) {

       my $target = shift;
       my $portRef = shift;
       my $logger = shift;
       my $nmapCmd = findBin("nmap",$logger);
       foreach my $port (@$portRef) {
               print "Knocking at $port\r";
               shellex("$nmapCmd -Pn --host_timeout 201 --max-retries 0 -p $port $target > /dev/null",$logger);
       }

       # So we don't have random chars potentially on the line after using \r
       print "\n";

}

sub checkPath($$) {

       my $path = shift;
       my $logger = shift;
       if ( ! -d $path ) {
               $logger->error("$path doesn't look like a dir, exiting...");
               exit 1;
       }

}

sub warnOnUser($$$) {

       my $user = shift;
       my $email = shift;
       my $logger = shift;

       my $gitCmd = findBin("git",$logger);
       my $configuredUser = shellex("$gitCmd config --get user.name",$logger);
       my $configuredEmail = shellex("$gitCmd config --get user.email",$logger);

       if ( $configuredUser ne $user || $configuredEmail ne $email ) {
               print "***************\n";
               print "Your configured user/email don't match what you declared in the config file!\n";
               print "Desired User: $user\nConfigured User: $configuredUser\nDesired Email: $email\nConfigured Email: $configuredEmail\n";
               print "***************\n";
       }

}

# https://perlmaven.com/trim
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };

sub parseSGConfig($$) {

       my $config = shift;
       my $logger = shift;
       if ( ! -e $config ) {
               $logger->error("$config doesn't look like a regular file, exiting...");
               exit 1;
       }
       my $catCmd = findBin("cat",$logger);
       my @configLines = split("\n",shellex("$catCmd $config",$logger));
       my %configHash;
       foreach my $line ( @configLines ) {
               chomp $line;
               if ( $line =~ m/^(.*)\ =\ "(.*)"$/ ) {
                       $configHash{$1} = $2;
               }

               if ( $line =~ m/^(.*)\ =\ \[(.*)\]/ ) {
                       my @trimmedPorts;
                       my @ports = split(",",$2);
                       foreach my $port (@ports) {
                               $port =~ /(\d{1,5})/;
                               push(@trimmedPorts,trim($1));
                       }
                       $configHash{$1} = \@trimmedPorts;
               }
       }

       return %configHash;

}

sub returnConfigPath($$) {

       my $path = shift;
       my $logger = shift;
       checkPath($path,$logger);

       my $gitConfigPath = $path . "/" . ".git/config";
       return $gitConfigPath;

}


sub readConfig($$) {

       # This sub is probably not really needed for what I'm trying to do
       # git itself already parses this config...but an interesting exercise non the less
       # and may be useful later
       my $path = shift;
       my $logger = shift;
       my $gitConfigPath = returnConfigPath($path,$logger);
       my $catCmd = findBin("cat",$logger);
       my @configLines = split("\n",shellex("$catCmd $gitConfigPath",$logger));
       # Key is config header, value is hash ref containing config values
       my %gitConfig;
       my @valueLines;
       my $lineCounter = 0;
       foreach my $line ( @configLines ) {
               $lineCounter++;
               #if ( $line =~ m/\[(.*)\]/ ) {
               if ( $line =~ m/\[(.*)\]/ ) {
                       #$valueLine =~ /\t(.*)\ =\ (.*)$/;
                       $gitConfig{$1} = "";
               }

       }

       # Tag each line with it's heading
       # Only way I could think of that worked to solve how this
       # There are almost certainly better ways
       my @taggedLines;
       my $tag = "NULLTAG";
       foreach my $line ( @configLines ) {
               if ( $line =~ m/\[(.*)\]/ ) {
                       $tag = $1;
               } else {
                       my $newLine = $tag . $line;
                       push(@taggedLines,$newLine);
               }
       }

       # Get all of the tagged lines into a hash structure.
       foreach my $key ( keys %gitConfig ) {
               my %stash;
               foreach my $tl ( @taggedLines ) {
                       if ( $tl =~ m/^($key)/ ) {
                               $tl =~ s/^($key)//g;
                               $tl =~ m/^\t(.*)\ \=\ (.*)$/;
                               my $confKey = $1;
                               my $confVal = $2;
                               $stash{$confKey} = $confVal;
                       }
               }
               $gitConfig{$key} = \%stash;
       }

       return %gitConfig;

}

sub getStatus($) {

       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       my $status = shellex("$gitCmd status -uall --porcelain",$logger);
       chomp $status;
       return $status;

}

sub returnState($) {

       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       my $currentStatus = getStatus($logger);
       my @statusLines = split("\n", $currentStatus);
       my @untracked;
       my @modified;
       my @added;
       my @deleted;
       foreach my $file ( @statusLines ) {
               $file =~ m/^\ {0,1}([A-Z?]{1,2})\ {1,2}(.*)/;
               my $fileAttrs = $1;
               my $filename = $2;
               my @attrs = split("",$fileAttrs);
               foreach my $attr ( @attrs ) {

                       if ( $attr =~ m/\?/ ) {
                               push(@untracked, $filename) unless grep $_ eq $filename, @untracked;
                       }

                       if ( $attr =~ m/[M]/ ) {
                               push(@modified, $filename) unless grep $_ eq $filename, @modified;
                       }

                       if ( $attr =~ m/[A]/ ) {
                               push(@added, $filename) unless grep $_ eq $filename, @added;
                       }

                       if ( $attr =~ m/[D]/ ) {
                               push(@deleted, $filename) unless grep $_ eq $filename, @deleted;
                       }

               }
       }

       return ( \@untracked, \@modified, \@added, \@deleted );

}

sub addFiles($$) {

       my $filesToAddRef = shift;
       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       foreach my $file ( @$filesToAddRef ) {
               shellex("$gitCmd add $file",$logger);
       }

}

sub commitChanges($$) {

       my $commitMsg = shift;
       chomp $commitMsg;
       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       shellex("$gitCmd commit -m \"$commitMsg\"",$logger);

}

sub pushChanges($) {

       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       my $output = shellex("$gitCmd push",$logger);

}

sub dropStash($) {

       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       my @stashList = split("\n", shellex("$gitCmd stash list",$logger));
       my $stashCount = scalar @stashList;
       # TODO: Don't need $stashCount, should just be able to iterate over @stashList
       if ( scalar @stashList == 0 ) {
               print "Stash is empty so not dropping\n";
       } else {
               foreach my $stashNum ( 1..$stashCount ) {
                       shellex("$gitCmd stash drop 0",$logger);
               }
       }

}

sub stashAndReset($) {

       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       shellex("$gitCmd stash",$logger);
       dropStash($logger);
       shellex("$gitCmd rebase",$logger);
}

sub resetFromUpstream($) {

       # git stash and git reset --hard and git pull ? I think
       # git reset upstream/master; git stash
       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       my $upstream = shellex("$gitCmd config --get remote.upstream.url",$logger);
       if ( $upstream eq "" || ! defined $upstream ) {
               print "Upstream not configured, exiting\n";
               exit 1;
       }

       shellex("$gitCmd reset upstream/master",$logger);
       shellex("$gitCmd stash",$logger);
       dropStash($logger);
       print "Successful reset from upstream\n";
       print "Changes have not been pushed, run \'$gitCmd pull\' to revert\n";

}

sub updateGitIgnore($$$) {

       my $path = shift;
       # Maybe better to accept an array of values
       my $ignoreValue = shift;
       my $logger = shift;
       checkPath($path,$logger);
       my $filename = $path . "/" . ".gitignore";
       # Make sure we're not appending/writing if entry already exists in gitignore
       if ( -f $filename ) {
               my $catCmd = findBin("cat",$logger);
               my @ignoreLines = split("\n",shellex("$catCmd $filename",$logger));
               if ( ! grep( /^$ignoreValue$/, @ignoreLines ) ) {
                       open(my $fh, ">>", $filename) or die $logger->error("Couldn't open $filename, exiting...");
                       chomp $ignoreValue;
                       print $fh "$ignoreValue\n";
                       close $fh;
               }
       } else {
               open(my $fh, ">", $filename) or die $logger->error("Couldn't open $filename, exiting...");
               chomp $ignoreValue;
               print $fh "$ignoreValue\n";
               close $fh;
       }

}

sub appendRepoUserConfig($$$) {

       my $desiredName = shift;
       my $desiredEmail = shift;
       my $logger = shift;

       my $gitCmd = findBin("git",$logger);
       my $currentName = shellex("$gitCmd config --get user.name",$logger);
       chomp $currentName;
       my $currentEmail = shellex("$gitCmd config --get user.email",$logger);
       chomp $currentEmail;

       if ( $currentName eq $desiredName ) {
               print "Already have $desiredName configured\n";
       } else {
               shellex("$gitCmd config user.name \"$desiredName\"",$logger);
               print "Set $desiredName successfully\n";
       }

       if ( $currentEmail eq $desiredEmail ) {
               print "Already have $desiredEmail configured\n";
       } else {
               shellex("$gitCmd config user.email \"$desiredEmail\"",$logger);
               print "Set $desiredEmail successfully\n";
       }

}

sub basicClone($$) {

       my $cloneTarget = shift;
       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       shellex("$gitCmd clone $cloneTarget",$logger);
       print "Successfully cloned $cloneTarget\n";

}

sub basicPull($) {

       my $logger = shift;
       my $gitCmd = findBin("git",$logger);
       my $gitPullReturn = shellex("$gitCmd pull",$logger);
       print "git pull returned:\n$gitPullReturn\n";

}