package SimplyGit::Git; use strict; use warnings; use Log::Log4perl qw(:easy); use lib "."; use SimplyGit::Shellex qw(shellex findBin knocker); use Exporter qw(import); our @EXPORT_OK = qw( readConfig getStatus returnState addFiles commitChanges pushChanges stashAndReset resetFromUpstream updateGitIgnore appendRepoUserConfig parseSGConfig warnOnUser basicClone basicPull ); # TODO: Add info/debug logging for all subroutines 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"; }