https://git.spwbk.site/swatson/simply-git/raw/master/sg
___________________________________
#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Long qw(GetOptions);
use Log::Log4perl qw(:easy);
# TODO: This needs to be scoped properly
use lib "/usr/local/lib";
use Shellex::Shellex qw(shellex findBin);
use SimplyGit::Git qw(
       readConfig getStatus returnState addFiles
       commitChanges pushChanges stashAndReset resetFromUpstream
       updateGitIgnore appendRepoUserConfig parseSGConfig
       warnOnUser basicClone basicPull knocker
);

sub initSG($) {

       my $sgDir = shift;
       my $homeDir = shellex("echo \$HOME");
       chomp $homeDir;
       my $path = $homeDir . "/" . $sgDir;
       my $logFile = $homeDir . "/" . $sgDir . "/" . "sgLog.txt";
       my $configFile = $homeDir . "/" . $sgDir . "/" . "sg.config";
       if ( ! -d $path ) {
               print "Creating $path\n";
               shellex("mkdir $path");
       }

       if ( ! -f $logFile ) {
               print "Creating $logFile\n";
               shellex("touch $logFile");
       }

       if ( ! -f $configFile ) {
               print "Creating $configFile\n";
               shellex("touch $configFile");
       }

       return ( $path, $logFile, $configFile );

}

my ( $sgPath, $sgLogFile, $sgConfigFile ) = initSG(".sg");
sub getLogName { return $sgLogFile; };
my $log_conf = q(
       log4perl.rootLogger              = ERROR, LOG1, screen
       log4perl.appender.LOG1           = Log::Log4perl::Appender::File
       log4perl.appender.LOG1.filename  = sub { getLogName(); }
       log4perl.appender.LOG1.mode      = append
       log4perl.appender.LOG1.layout    = Log::Log4perl::Layout::PatternLayout
       log4perl.appender.LOG1.layout.ConversionPattern = %d %p >> %m %n

       log4perl.appender.screen = Log::Log4perl::Appender::Screen
       log4perl.appender.screen.stderr = 0
       log4perl.appender.screen.layout = PatternLayout
       log4perl.appender.screen.layout.ConversionPattern = %d %p >> %m %n
);

Log::Log4perl::init(\$log_conf);
my $logger = get_logger();
my $gitCmd = findBin("git",$logger);
# Removed .sg from repo dir to home, don't need this sub right now
# updateGitIgnore(".","/.sg",$logger);

my %args;
GetOptions(
       \%args,
       'push-all',
       'interactive',
       'view',
       'reset-from-master',
       'reset-from-upstream',
       'upstream-url=s',
       'commit-msg=s',
       'dump-config',
       'configure-local-user',
       'user=s',
       'email=s',
       'config-file=s',
       'knock',
       'knock-clone=s',
       'help',
       'knock-pull',
);

# TODO: This should maybe be more robust?
if ( ! -d ".git" && ( ! defined $args{'knock-clone'} && ! defined $args{'knock'} && ! defined $args{'help'} ) ) {
       print "Not a git dir, exiting...\n";
       exit 1;
}

sub printHelp {

       my $help = <<EOF
simply-git
Usage:
       --view
       Display git status of files and other information

       --dump-config
       Dump .git/config to STDOUT. Not really useful but exposed for testing of reading config into internal data structure

       --push-all [--commit-msg]
       Push all untracked and modified files
               * Can be used with interactive mode
               * Can provide a commit msg with --commit-msg (otherwise a generic will be provided)

       --interactive
       Enable interactive mode with supported opts

       --reset-from-master
       Reset all current changes so that the file tree matches origin master

       --reset-from-upstream [ --upstream-url ]
       If upstream is defined will reset local branch to match upstream ( does not push changes to origin )
               * Assumes you have an upstream configured
               * Pass SSH/HTTPS URL to --upstream-url to add an upstream

       --configure-local-user [--user,--email]
       Configure local git user
               * Can be used with interactive mode

       --config-file
       Default is ~/.sg/sg.config, can use this opt to use another file
               * See example.config

       --knock
       Will try and knock the defined git server at the defined ports before any operation
               * See example.config
               * Can pass this by itself to perform a knock and exit

       --knock-clone
       Will try and knock the defined git server and clone the provided repo
               * Will not check if you're in a git dir

       --knock-pull
       Will try and knock the defined git server and git pull

EOF
;

       print "$help\n";

}

if ( scalar keys %args < 1 ) {
       printHelp();
}

###
# TODO: This args processing is better and more predictable than it was, bit there is still
# likely a lot of room for improvement...
###

sub parseArgs {

       if ( defined $args{'help'} ) {
               printHelp();
               exit 0;
       }

       if ( defined $args{'view'} && scalar keys %args > 1 ) {
               print "Can't pass other args with --view\n";
               exit 1;
       }

       if ( defined $args{'dump-config'} && scalar keys %args > 1 ) {
               print "Can't pass other args with --dump-config\n";
               exit 1;
       }

       if ( defined $args{'reset-from-master'} && scalar keys %args > 1 ) {
               print "Can't pass other args with --reset-from-master\n";
               exit 1;
       }

       if ( defined $args{'push-all'} ) {
               foreach my $arg ( keys %args ) {
                       if ( $arg eq "interactive" || $arg eq "commit-msg" || $arg eq "push-all" || $arg eq "knock" ) {
                               next;
                       } else {
                               print "Can only pass --interactive and --commit-msg with --push-all\n";
                               exit 1;
                       }
               }
       }

       if ( defined $args{'configure-local-user'} ) {
               if ( scalar keys %args < 2 ) {
                       print "Must pass either --interactive or --user AND --email to --configure-local-user\n";
                       exit 1;
               }

               foreach my $arg ( keys %args ) {
                       if ( $arg eq "interactive" || $arg eq "user" || $arg eq "email" || $arg eq "configure-local-user" ) {
                               next;
                       } else {
                               print "Must/can only pass --interactive, OR --user AND --email with --configure-local-user\n";
                               exit 1;
                       }
               }

               if ( ! defined $args{'interactive'} && ! defined $args{'user'} || ! defined $args{'interactive'} && ! defined $args{'email'} ) {
                       print "If not using --interactive with --configure-local-user, --user and --email MUST be defined\n";
                       exit 1;
               }
       }

       if ( defined $args{'reset-from-upstream'} ) {
               if ( scalar keys %args > 2  ) {
                       print "Can only pass --upstream-url with --reset-from-upstream\n";
                       exit 1;
               }

               foreach my $arg ( keys %args ) {
                       if ( $arg eq "reset-from-upstream" || $arg eq "upstream-url" ) {
                               next;
                       } else {
                               print "Can only pass --upstream-url with --reset-from-upstream\n";
                               exit 1;
                       }
               }
       }

       if ( ! defined $args{'config-file'} ) {
               $args{'config-file'} = $sgConfigFile;
       }

       if ( defined $args{'knock-clone'} ) {
               if ( scalar keys %args > 2 ) {
                       print "--knock-clone accepts no other args\n";
                       exit 1;
               }
       }

       if ( defined $args{'knock-pull'} ) {
               if ( scalar keys %args > 2 ) {
                       print "--knock-pull accepts no other args\n";
                       exit 1;
               }
       }

}

sub color_print($$) {


       #echo -e "\033[0mNC (No color)"
       #echo -e "\033[1;37mWHITE\t\033[0;30mBLACK"
       #echo -e "\033[0;34mBLUE\t\033[1;34mLIGHT_BLUE"
       #echo -e "\033[0;32mGREEN\t\033[1;32mLIGHT_GREEN"
       #echo -e "\033[0;36mCYAN\t\033[1;36mLIGHT_CYAN"
       #echo -e "\033[0;31mRED\t\033[1;31mLIGHT_RED"
       #echo -e "\033[0;35mPURPLE\t\033[1;35mLIGHT_PURPLE"
       #echo -e "\033[0;33mYELLOW\t\033[1;33mLIGHT_YELLOW"
       #echo -e "\033[1;30mGRAY\t\033[0;37mLIGHT_GRAY"
       #
       # Doing it this way likely hurts portability but
       # it's better than nothing which is what I'm currently doing
       # and would like to avoid additional module deps, as this
       # isn't too hard to implement

       my $print_string = shift;
       my $color = shift;

       if ( $color ne "BLUE" && $color ne "GREEN" && $color ne "RED" ) {
               $logger->error("Bad color passed to color_print");
               exit 1;
       }

       my %color_map = (
               BLUE  => "\033[0;34m",
               GREEN => "\033[0;32m",
               RED   => "\033[0;31m",
               RESET => "\033[0m",
       );

       printf "$color_map{$color}$print_string$color_map{'RESET'}";

}


parseArgs();
my %sgConfig = parseSGConfig($args{'config-file'},$logger);
if ( defined $sgConfig{'UserWarn'}  && -d ".git" ) {
       warnOnUser($sgConfig{'user.name'},$sgConfig{'user.email'},$logger);
}

sub knock() {
       if ( defined $sgConfig{'Knock'} && ( defined $args{'knock'} || defined $args{'knock-clone'} || defined $args{'knock-pull'} ) ) {
               knocker($sgConfig{'knock.target'},$sgConfig{'ports'},$logger);
       }
}

if ( defined $args{'knock'} && scalar keys %args == 2 ) {
       print "Just knocking then exiting...\n";
       knock();
       exit 1;
}

# TODO: This sub could be more concise with a sub to print array refs
if ( defined $args{'view'} ) {
       my ( $untrackedRef, $modifiedRef, $addedRef, $deletedRef ) = returnState($logger);
       #my $refs = shellex("$gitCmd show-ref",$logger);
       my $branch = shellex("$gitCmd show-branch",$logger);
       my $name = shellex("$gitCmd config --get user.name",$logger);
       chomp $name;
       my $email = shellex("$gitCmd config --get user.email",$logger);
       chomp $email;
       color_print("-->Username: $name\n-->Email: $email\n","BLUE");
       print "Branches:\n";
       color_print("$branch\n","GREEN");
       #print "$refs\n";
       print "Files:\n";
       my $swpWarning = "\t# Likely a Vi .swp file";

       my $untrackedTotal = scalar @$untrackedRef;
       print "* $untrackedTotal untracked file(s):\n";
       foreach my $file ( @$untrackedRef ) {
               if ( $file =~ m/.swp/ ) {
                       color_print("\t$file $swpWarning\n","GREEN");
               } else {
                       color_print("\t$file\n","GREEN");
               }
       }

       my $modifiedTotal = scalar @$modifiedRef;
       print "* $modifiedTotal modified file(s):\n";
       foreach my $file ( @$modifiedRef ) {
               if ( $file =~ m/.swp/ ) {
                       color_print("\t$file $swpWarning\n","GREEN");
               } else {
                       color_print("\t$file\n","GREEN");
               }
       }

       my $commitTotal = scalar @$addedRef;
       print "* $commitTotal file(s) added to commit:\n";
       foreach my $file ( @$addedRef ) {
               if ( $file =~ m/.swp/ ) {
                       print "\t$file $swpWarning\n";
               } else {
                       print "\t$file\n";
               }
       }

       my $deletedTotal = scalar @$deletedRef;
       print "* $deletedTotal file(s) to be deleted from commit:\n";
       foreach my $file ( @$deletedRef ) {
               if ( $file =~ m/.swp/ ) {
                       color_print("\t$file $swpWarning\n","RED");
               } else {
                       color_print("\t$file\n","RED");
               }
       }
}

if ( defined $args{'push-all'} ) {

       my ( $untrackedRef, $modifiedRef ) = returnState($logger);
       my @files;
       push(@files,@$untrackedRef); push(@files,@$modifiedRef);
       my @filesToCommit;
       if ( defined $args{'interactive'} ) {
               foreach my $file ( @files ) {
                       print "Add $file to commit (y/n): ";
                       my $input = <STDIN>;
                       chomp $input;
                       if ( $input =~ m/^Y|^y/ ) {
                               push(@filesToCommit,$file);
                       } else {
                               next;
                       }
               }

       } else {
               @filesToCommit = @files;
       }

       print "Commiting the following files:\n";
       foreach my $file ( @filesToCommit ) {
               print "\t$file\n";
       }

       if ( defined $args{'interactive'} ) {
               print "Does this look correct (y/n) : ";
               my $input = <STDIN>;
               chomp $input;
               if ( $input !~ m/^Y|^y/ ) {
                       print "Canceling...\n";
                       exit 1;
               }
       }

       addFiles(\@filesToCommit,$logger);

       if ( defined $args{'interactive'} && ! defined $args{'commit-msg'}) {
               print "Enter a commit message: ";
               my $input = <STDIN>;
               chomp $input;
               commitChanges($input,$logger);
       } elsif ( defined $args{'commit-msg'} ) {
               my $commitMsg = "$args{'commit-msg'}";
               commitChanges($commitMsg,$logger);
       } else {
               my $epoch = time();
               my $commitMsg = "Generic Commit at $epoch";
               commitChanges($commitMsg,$logger);
       }

       if ( defined $args{'interactive'} ) {
               print "Push changes? (y/n): ";
               my $input = <STDIN>;
               chomp $input;
               if ( $input !~ m/^Y|^y/ ) {
                       # TODO: Unstage changes?
                       print "Canceling...\n";
                       exit 1;
               }

               knock();
               my $gitOutput = pushChanges($logger);
               print "Git returned:\n$gitOutput\n";

       }

       else {

               knock();
               pushChanges($logger);
               my $gitOutput = pushChanges($logger);
               print "Git returned:\n$gitOutput\n";

       }


}

if ( defined $args{'reset-from-master'} ) {

       knock();
       stashAndReset($logger);

}

if ( defined $args{'reset-from-upstream'} ) {

       if ( defined $args{'upstream-url'} ) {
               print "Setting upstream to $args{'upstream-url'}\n";
               chomp $args{'upstream-url'};
               shellex("$gitCmd remote add upstream $args{'upstream-url'}",$logger);
               shellex("$gitCmd fetch upstream",$logger);
               knock();
               resetFromUpstream($logger);
       } else {
               knock();
               resetFromUpstream($logger);
       }
}

if ( defined $args{'dump-config'} ) {

       my %configHash = readConfig(".",$logger);
       foreach my $key ( keys %configHash ) {
               my $hRef = $configHash{$key};
               print "[$key]\n";
               foreach my $ckey ( keys %$hRef ) {
                       print "\t$ckey = ${$hRef}{$ckey}\n";
               }
       }

}

if ( defined $args{'configure-local-user'} ) {

       if ( defined $args{'interactive'} ) {
               print "Enter user to set: ";
               my $desiredName = <STDIN>;
               chomp $desiredName;
               print "Enter email to set: ";
               my $desiredEmail = <STDIN>;
               chomp $desiredEmail;

               appendRepoUserConfig($desiredName,$desiredEmail,$logger);

       } else {

               appendRepoUserConfig($args{'user'},$args{'email'},$logger);

       }

}

if ( defined $args{'knock-clone'} ) {
       knock();
       basicClone($args{'knock-clone'},$logger);
}

if ( defined $args{'knock-pull'} ) {
       knock();
       basicPull($logger);
}