#!/usr/bin/perl
################################################################################################ mgsh (mwx'2023)
use Sys::Hostname qw(hostname);use Socket;
use Config;
use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );
use File::Compare;

$VERSION='3.1.5';
$INFO="mwx'2023";

$BASE="$ENV{HOME}/Source";
$BASE="/db/src" if ($Config{osname}=~/linux/i);
die "$BASE not found" if (!-d $BASE);

$PRJ=$ARGV[0] if (-d "$BASE/$ARGV[0]");

$GITHOST  = '141.14.128.56';
$GITPORT  = '2234';
$GITUSER  = 'git';
$GITKEY   = 'mgit_rsa';
$GITPATH  ="/home/git";

$URL      = "ssh://$GITUSER\@$GITHOST:$GITPORT$GITPATH";
$SSH      = "ssh -p $GITPORT $GITUSER\@$GITHOST";

$IP = inet_ntoa( (gethostbyname(hostname()))[4] );
$HOST=hostname();$HOST=~s/([^\.]+)\..*$/$1/;
$USER=getpwuid( $< );

use Date::Parse;                                                                                   # date hashes
@D=('Sun','Mon','Tue','Wed','Thu','Fri','Sat'); 
@M=('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');


&cmd("git config --global user.name 'Michael Wesemann'",1);                                       # git settings
&cmd("git config --global user.email mike\@pstbx.org",1);
&cmd("git config --global push.default matching",1);

print "mgsh v$VERSION $INFO, $BASE\n";                                                          # ready to start

for (;;) {                                                                                      # main mgsh loop
  if ($PRJ!~/^\s*$/ && -d "$BASE/$PRJ") {                                # set current working directory/project

    $DIR="$BASE/$PRJ";
    
    $BPLSTATE='';
    if (-l "$DIR/build.pl") {
      chomp($rlf=`readlink $DIR/build.pl`);
      $BPLSTATE='/OFF' if ($rlf=~/\.off$/);
    }

    $p=$1 if ($BASE=~/\/([^\/]+)$/);$prompt="$p/$PRJ/$I$BPLSTATE";
  } else {
    $DIR="$BASE";
    $p=$1 if ($BASE=~/\/([^\/]+)$/);$prompt="$p";
  }


  chomp($REMURL= `cd $DIR; git config remote.origin.url`);
  $REPO=$1 if ($REMURL=~/$url\/(.*)\.git\/?$/);


  $prompt=~s/\/\s*$//;$cmd=&readline("$prompt > ","",1);                        # read command from command line


  @tmpcmd=split(' ',$cmd);undef(@cmd);undef(%OPT);                                     # get commandline options
  for (@tmpcmd) {if (/\-(\w)/) {$OPT{$1}="-$1";} else {push(@cmd,$_);}}

  if ($cmd[0] eq 'dist') { ########################################################################### list [-a]
    $CP='/bin/cp';
    $CP='/opt/homebrew/bin/gcp' if (-f '/opt/homebrew/bin/gcp');

    $DDIR="$BASE/dist/$PRJ";
    $DDIR=$cmd[1] if ($cmd[1]!~/^\s*$/);

    if (-d $DDIR) {
      chdir($DIR);
      $n=0;
      open(LS,"cd $DIR;git ls-files |");
      while(<LS>) {
        chomp();
        if (!/^private/) {
          if (compare($_,"$DDIR/$_")!=0) {
            print "$_\n";
            cmd("$CP --parents $_ $DDIR",1);
            $n++;
          }
        }
      }
      print "$n files copied to $DDIR\n";
    } else {
      print "dist path not found ($DDIR)\n";
    }
        
    close(LS);
  }

  elsif ($cmd[0] eq 'list') { ########################################################################### list [-a]
    
    $path=".";$path="./archive" if ($OPT{a});
  
    open(LS,"$SSH /bin/ls -ltr $path |");
    while(<LS>) {
      if ($cmd[1]=~/^\s*$/ || /$cmd[1]/i) {
        if ($OPT{a}) {
          print "$1\n" if (/git\s+git\s+\d+\s+(.*)\.git.tar.gz$/);
        } else {
          print "$1\n" if (/git\s+git\s+\d+\s+(.*)\.git$/);
        }
      }
    }
    close(LS);
    
  }

  elsif ($cmd[0] eq 'show') { ############################################################################# show
    $prj=$PRJ if ($PRJ!~/^\s*$/);
    $prj=$cmd[1] if ($cmd[1]!~/^\s*$/);

    $path=".";
    $found=0;
    open(LS,"$SSH /bin/ls $path |");
    while(<LS>) {
      $found++ if (/^$prj\.git$/);
    }
    close(LS);

    if ($found==1) {
      open($FH,"$SSH \"cd $GITPATH/$prj\.git && git log --reverse --format='%h  %cd  %s'\" |");
      &repolog($FH);
    } else {
      print "repository not found\n";
    }

  }

  elsif ($cmd[0] eq 'log' && $PRJ!~/^\s*$/) { ############################################################## log
    if (-d "$DIR/.git") {
      open($FH,"cd $DIR;git log --reverse --format='%h  %cd  %s' |");
      &repolog($FH);
    } else {
      print "repository not found";
    }
  }

  elsif ($cmd[0] eq 'push' && $PRJ!~/^\s*$/) { ################################################## push [comment]
    
    if (-f "$DIR/push.pl") {
      &shell("perl $DIR/push.pl");
    }

    $comment='';$comment.="$cmd[$_] " for (1..$#cmd);$comment=~s/\s+$//;
    $comment=~s/\s{2}/ /g;
    
    if (-d "$DIR/.git") {
      &shell("git add --all .");
      $msg="[$USER\@$HOST] $comment";$msg=~s/^\s+//;$msg=~s/\s+$//;   
      &shell("git commit -m '$msg'");
      &shell("git push");
      &shell("$SSH touch $GITPATH/${PRJ}.git");
    } else {
      print "error, no repository (.git) found";
    }

  }
       
  elsif ($cmd[0] eq 'edit' && $PRJ!~/^\s*$/) { ################################################### edit [number]

    $num=10;$num=$cmd[1] if ($cmd[1]=~/^\d+$/);
    if (-d "$DIR/.git") {
      &shell("git rebase -i HEAD~$num");
      &shell("git push --force");
    } else {
      print "error, no repository (.git) found";
    }

  }
       
  elsif ($cmd[0] eq 'archive' && $PRJ!~/^\s*$/) { ############################################ archive [comment]
    $comment='';$comment.="$cmd[$_] " for (1..$#cmd);$comment=~s/\s+$//;
    $comment=~s/[\,\;\:\\\/\=\'\"\|\?\>\<\-]+/ /g;
    $comment=~s/\s{2}/ /g;
    $comment=~s/\s+/\_/g;

    my(@z)=localtime(time());$z[4]+=1;$z[5]-=100;$z[6]+=1;
    $z=sprintf("%02d%02d%02d.%02d%02d",$z[3],$z[4],$z[5],$z[2],$z[1]);

    if ($comment=~/^\s*$/) {
      &shell(sprintf("$SSH 'cp -r ${PRJ}.git archive/${PRJ}_$z.git'"));
      &shell(sprintf("$SSH 'cd archive;tar cvzf ${PRJ}_$z.git.tar.gz ${PRJ}_$z.git'"));
      &shell("$SSH 'rm -rf archive/${PRJ}_$z.git'");
    } else {
      &shell(sprintf("$SSH cp -r ${PRJ}.git archive/${PRJ}_${z}_$comment.git"));
      &shell(sprintf("$SSH 'cd archive;tar cvzf ${PRJ}_${z}_$comment.git.tar.gz ${PRJ}_${z}_$comment.git'"));
      &shell("$SSH 'rm -rf archive/${PRJ}_${z}_$comment.git'");
    }
  }

  elsif ($cmd[0] eq 'init' && $PRJ!~/^\s*$/) { ############################################################ init
    
    &shell("perl $DIR/push.pl") if (-f "$DIR/push.pl");
    if ($REPO=~/^\s*$/ || yesno('overwrite existing repository?',0)) {
      &shell("$SSH 'rm -rf $GITPATH/${PRJ}.git'");
      &shell("$SSH 'mkdir $GITPATH/${PRJ}.git;chdir $GITPATH/${PRJ}.git;git --bare init'");

      if (!-f ".gitignore" || yesno('overwrite existing .gitignore?',0)) {
        open(OUT,"> .gitignore");
        print OUT &gitignore();
        close(OUT);
      }

      &shell("rm -rf .git");
      &shell("git init");
      &shell("git remote add origin $URL/${PRJ}.git/");
      &shell("git add .");
      &shell("git commit -m 'initial commit [$IP,$USER]'");
      &shell("git push -u origin master");
    }
  }
  
  elsif ($cmd[0] eq 'login') { ########################################################################### login
    &cmd("$SSH");
  }
  
  elsif ($cmd[0] eq 'cd' && $cmd[1]!~/^\./) { ############################################################### cd
    $PRJ='';
    $PRJ=$cmd[1] if (-d "$BASE/$cmd[1]");
  }
   
  elsif ($cmd[0] eq 'checkout' && $PRJ!~/^\s*$/) { ###################################### checkout [git options]
    &shell("git checkout $cmd[1]");
  }
  
  elsif ($cmd[0] eq 'clone') { ###################################################################### clone [-a]
    $prj=$PRJ if ($PRJ!~/^\s*$/);
    $prj=$cmd[1] if ($cmd[1]!~/^\s*$/);

    $DIR="$BASE";chdir($DIR);
    
    my($path)=".";my($path)="./archive" if ($OPT{a});
    my($found)=0;
    open(LS,"$SSH /bin/ls $path |");
    while(<LS>) {
      if ($OPT{a}) {
        $found++ if (/^$prj\.git.tar.gz$/);
      } else {
        $found++ if (/^$prj\.git$/);
      }
    }
    close(LS);

    if ($found==1) {
      if (-d "$DIR/$prj") {
        if (yesno('overwrite existing directory?',0)) {
          &cmd("rm -rf $DIR/$prj") 
        } else {
          goto exitclone;
        }
      }
    
      &shell("git clone $URL/$prj.git") if (!$OPT{a});
      if ($OPT{a}) {
        &cmd("$SSH 'cd archive;tar xvzf $prj.git.tar.gz'");
        &cmd("git clone $URL/archive/$prj.git");
        &cmd("$SSH 'rm -rf archive/$prj.git'");
      }
    } else {
      print  "repository not found, try 'list [-a]'";
    }

    $PRJ=$prj if (-d "$BASE/$prj"); 
    exitclone:;
  }
  
  elsif ($cmd[0] eq 'cloneall') { ##################################################################### cloneall
    $prj=$PRJ if ($PRJ!~/^\s*$/);
    $prj=$cmd[1] if ($cmd[1]!~/^\s*$/);

    $DIR="$BASE";chdir($DIR);
    
    my($path)=".";my($path)="./archive" if ($OPT{a});
    my($found)=0;
    open(LS,"$SSH /bin/ls $path |");
    while(<LS>) {
      if (/^(.*)\.git$/) {
        print "-$1-\n";
        &shell("git clone $URL/$1.git");

      }
    }
    close(LS);
  }
  
  elsif ($cmd[0] eq 'open' || $cmd[0] eq 'view'){ ######################################################### open
    $prj=$PRJ if ($PRJ!~/^\s*$/);
    $prj=$cmd[1] if ($cmd[1]!~/^\s*$/);

    if (-d "$BASE/$prj") {
      $DIR="$BASE/$prj";chdir($DIR);

      $xws='';$xprj='';opendir(DIR,$DIR);
      while($file=readdir(DIR)) {
        $xws=$file if ($file=~/.xcworkspace$/);
        $xprj=$file if ($file=~/.xcodeproj$/);
      }
      close(DIR);
      
      $xprj='' if ($xws!~/^\s*$/ && $xprj!~/^\s*$/);
      
      &shell("open $xws") if ($xws!~/^\s*$/ && -d "$DIR/$xws");
      &shell("open $xprj") if ($xprj!~/^\s*$/ && -d "$DIR/$xprj");
      
      &shell("coda $DIR") if ($xws=~/^\s*$/ && $xprj=~/^\s*$/);
     
      $PRJ=$prj if ($cmd[0] eq 'open'); 

    } else {
      print "not found\n";
    }
  }
  
  elsif ($cmd[0] eq 'count') { ##################################################################### count lines
    &count($DIR);
  }

  elsif ($cmd[0] eq 'do') { ############################################################### execute extra script
    &shell("perl $DIR/do.$cmd[1].pl") if (-f "$DIR/do.$cmd[1].pl");
  }

  elsif ($cmd[0] eq 'tag') {                                                                        # manage tag
    if ($cmd[1] eq 'add' && $cmd[2]!~/^\s*$/) {
      &shell("git tag -a '$cmd[2]' -m '$cmd[2]'");
      &shell("git push origin '$cmd[2]'");
    } elsif ($cmd[1] eq 'checkout' && $cmd[2]!~/^\s*$/) {
      &shell("git checkout 'tags/$cmd[2]'");
    } elsif ($cmd[1] eq 'delete' && $cmd[2]!~/^\s*$/) {
      &shell("git tag -d '$cmd[2]'");
      &shell("git push origin ':refs/tags/$cmd[2]'");
    } else {
      if ($cmd[1]!~/^\s*$/) {
        print "unkown tag subcommand\n";
      } else {
        &shell("git tag -l --format=\"%(taggerdate:short): %(refname:short)\"");
      }      
    }
  }

  elsif ($cmd[0] eq 'help') { ############################################################################# help

print <<"ENDOFHELP";

mgsh (git shell) $VERSION $INFO, builtin commands:
  
  cd [project]             change project
  open [project]           open project 
  init                     make new repository from current directory
  push [comment]           push changes to git server
  edit [number]            edit last [number] commits (default is 10)
  clone [-a] <repository>  clone repository from git server (-a for archive)
  cloneall>                clone all repository from git server
  checkout [git options]   forward checkout to git
  log                      show log 
  archive [comment]        archive current repository  
  list [-a] [pattern]      list repositories on git server (-a for archive)
  show <repository>        show repository log on git server
  dist <path>              cp changed files to other directory/repository
  login                    connect to git server
  do <name>                execute extra script with name do.<name>.pl
  count                    count lines in project
  tag                      show tags
  tag add <tag>            add tag
  tag checkout <tag>       checkout tag
  tag delete <tag>         delete tag  

ENDOFHELP
  
  }
  
  else { ###################################################################### forward unknown command to shell
    &shell($cmd) if ($cmd!~/^\s*$/);
  }
  
}

######################################################################################################## SUPPORT

sub shell() {                                                                                            # shell
  my($cmd)=@_;
  chdir $DIR;
  system($cmd);
}

sub cmd() {                                                                                    # execute command
  my($cmd,$quiet)=@_;
  print "$cmd\n" if (!$quiet);
  system($cmd);
}

sub readline {                                                                                        # readline
  my($prompt,$input,$mode)=@_; use Term::ReadLine;
  BEGIN{ $ENV{PERL_RL} = 'Perl o=0' };
  
  if (!$term) {
    $term = new Term::ReadLine 'term';
    open(IN,"$BASE/.mgsh_history");
    while(<IN>) {
      chomp();
      push(@history,$_);
    }
    close(IN);
    $term->SetHistory(@history);
  }
    
  my($txt)=$term->readline($prompt,$input);

  my(@history)=$term->GetHistory();
  shift(@history) while($#history>=10); 
  open(OUT,"> $BASE/.mgsh_history");
  print OUT "$_\n" for (@history);
  close(OUT);

  return $txt;
}

sub yesno {                                                                              # request yes/no answer
  my($prom,$def)=@_;
  my($ans);
  if ($def) {;$ans=&getkey("$prom Y/n ? ");}
  else {;$ans=&getkey("$prom y/N ? ");}
  if ($ans=~/^\s*$/) {
    if ($def) {;return(1);}
    else {;return(0);}
  }
  if ($ans=~/^\s*y\s*$/i || $ans=~/^\s*yes\s*$/i) {
    return 1;
  }
  return 0;
}

sub getkey {                                                                             # get key from keyboard
  $|=1;
  print "$_[0]";
  system "stty", '-icanon', '-echo', 'eol', "\001";
  $key = getc(STDIN);$key=~s/[\r\n\t]//g;
  system "stty", 'icanon', 'echo', 'eol', "^@";
  print "$key\n";
  return $key;
}

sub count() {                                                                           # count lines in project
  my($dir)=@_;
  use File::Find;
  $lines=0;
  find(\&counting,$dir);
  sub counting {
    $f=$File::Find::name;
    if (-f $f) {
      if ($f=~/\.[hmc]$/ || $f=~/\.xib$/ || $f=~/\.lm$/ || $f=~/\.ym$/ || $f=~/\.pl$/ || $f=~/\.php$/ ||
          (-T $f && $f=~/\/bin\//)) {
        open(WC,"/usr/bin/wc -l '$f' |");
        while(<WC>) {;
          $lines+=$1 if (/^\s*(\d*)\s+$f/);
        }
        close(WC);
      }
    
    }
  }
  print "$lines lines total in $PRJ\n";
}

sub repolog() {                                                                            # show repository log
  my($FH)=@_;
  my($o)='';my($os)='';my($oss)='';my($n)=0;my($ns)=0;
  my($z)='';my($zs)='';my($zss)='';
  
  while(<$FH>) {
    chomp();
    @tmp=split('  ');
    $t=str2time($tmp[1]);
    my(@z)=localtime($t);$z[4]+=1;$z[5]+=1900;$z[6]+=1;
      
    if (time()-$t<86400) {
      $z=sprintf("                  %02d\:%02d",$z[2],$z[1]);
      $zs=sprintf("     %02d\:%02d",$z[2],$z[1]);
      $zss=sprintf("%02d\:%02d",$z[2],$z[1]);
    } elsif (time()-$t<86400*7) {
      $z=sprintf("             %s, %02d\:%02d",$D[$z[6]-1],$z[2],$z[1]);
      $zs=sprintf("%s, %02d\:%02d",$D[$z[6]-1],$z[2],$z[1]);
      $ns++;
    } else {
      $z=sprintf("%s, %02d %s %4d, %02d\:%02d",$D[$z[6]-1],$z[3],$M[$z[4]-1],$z[5],$z[2],$z[1]);
      $n++;
    } 
      
    $o.="$tmp[0]  $z  $tmp[2]\n";
    $os.="$tmp[0]  $zs  $tmp[2]\n";
    $oss.="$tmp[0]  $zss  $tmp[2]\n";
  }
    
  if ($n==0 && $ns==0) {
    print $oss;
  } elsif ($n==0 && $ns>0) {
    print $os;
  } else {
    print $o;
  }
}

sub gitignore() {
return << "ENDOFGITIGNORE";
.DS_Store
.AppleDouble
.LSOverride
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
ENDOFGITIGNORE
}


############################################################################################################ END
