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