giovedì 7 giugno 2012

Time Archive

Se siete abituati a sviluppare software, conoscerete sicuramente i vari strumenti di controllo della versione, RVS, CVS, SVN eccetera.

Un sistema di controllo della versione consente di lavorare in gruppo sullo stesso progetto, gestendo le versioni e mantenendo l'intero storico degli aggiornamenti.

Questi strumenti sono concepiti per programmatori e con scopi precisi, pertanto lavorano sulle modifiche ai sorgenti testuali e sono anche piuttosto difficili da gestire, visto che di solito si basano su strutture client/server, richiedono un setup eccetera.

Mi sono chiesto se è possibile implementare un sistema un po`più semplice che serva a scopi più generali.

Per esempio, se si mantiene ordinato il proprio lavoro, suddividendo i vari progetti in cartelle, aggiungendo documenti di varia natura, può essere interessante poter mantenere uno storico di ogni precedente situazione, gestire dei trunk o dei branch in modo intelligente e portabile.

Sia su MAC che nelle ultime versioni di Windows vi sono strumenti per recuperare vecchie versioni dello stesso documento e sono più che altro connesse al sistema di backup.

La mia idea però è più modesta, a me serve una specie di archiviatore, in grado di memorizzare in archivi standard, tutta la storia di una cartella, meglio se multi-piattaforma, in modo da poter trasferire facilmente l'intero storico copiandolo come un normale archivio, su una chiavetta o un disco.

Così ho scritto uno scriptino di bash che ho chiamato timerachive e che fa più o meno quello che ho detto.

Oltre che la bash, timearchive usa alcuni comandi tipici dei sistemi Unix like, oltre a tre componenti fondamentali, il tar, gzip e xdelta3.

Mentre dei primi due avrete probabilmente sentito parlare da qualche parte, xdelta3 è abbastanza sconosciuto ma in realtà è un moderno sistema di Patch binaria, ossia un programma in grado di eseguire la "delta encoding" cioè ricavare le differenze da due files binari A e B , e generare un file C, in modo che avendo B e C si possa riottenere A.

La caratteristica interessante di xdelta3 è quello di generare files in formato vcdiff che pur non essendo anch'esso molto noto, è uno standard descritto in rfc3284 .

Il comando timearchive 

Lo script timearchive  l'ho inserito in /usr/local/bin del mio sistema Linux , giusto perché è il posto più logico dove metterlo, così è a disposizione direttamente come ogni altro comando, lanciato da solo dà :

timearchive
Bad paramenter, please use U,T,R or X.

e quindi ci presenta 4 diverse modalità d'uso :
  1. Update
  2. Test
  3. Rollback
  4. eXtract
Vediamoli nel dettaglio e spieghiamo indicativamente cosa fanno.

Update

Immaginiamo di avere una cartella planning e di volerla archiviare con timearchive, possiamo lanciare il comando :

timearchive U planning.tar planning "Prima versione di test"

U è il comando, planning.tar, l'archivio da creare che è un tar appunto, planning è la cartella da memorizzare e poi segue una riga di testo per indicare il contenuto che ci può essere utile per fare i famosi branch o trunk. Con un tar tvf , vediamo il contenuto:

tar tvf planning.tar

-rw-r--r-- user/user    2020 2012-06-07 04:27 20120607042743.tar.gz
-rw-r--r-- user/user      23 2012-06-07 04:27 20120607042743.revision


Ebbene nel tar abbiamo due files, il primo è contenuto compresso che nel nostro caso è solo 2K (è una cartella molto piccola) e l'altra è la descrizione.

Entrambi i files sono registrati con quella strana sequenza di numeri, che in realtà è la data in formato ordinabile YYYYMMDDhhmmss e che è il nostro codice automatico di versione.

Interessante è aggiungere un piccolo file all'interno della cartella e rilanciare il comando identico :

timearchive U planning.tar planning "Aggiunto piccolo file"

tar tvf planning.tar
 
-rw-r--r-- user/user      23 2012-06-07 04:27 20120607042743.revision
-rw-r--r--
user/user      89 2012-06-07 04:32 20120607042743.vcdiff
-rw-r--r--
user/user    2193 2012-06-07 04:32 20120607043221.tar.gz
-rw-r--r--
user/user      22 2012-06-07 04:32 20120607043221.revision

In questo caso è partito xdelta3 che è stato applicato sulla versione decompressa (ma non scompattata) del file tar.gz e sul nuovo tar prodotto per l'occasione. Vediamo che il file che prima era tar.gz è divenuto vcdiff ed è diventato solo di 89 bytes, il vecchio tar.gz è stato rimosso ed è stato inserito al suo posto il nuovo contenuto.

Rimuovendo il file appena aggiunto ed eseguendo ancora lo stesso comando, eseguiamo lo stesso identico algoritmo dell'ultima volta e otteniamo :

timearchive U planning.tar planning "Tolto piccolo file"


tar tvf planning.tar

-rw-r--r-- user/user      23 2012-06-07 04:27 20120607042743.revision
-rw-r--r--
user/user      89 2012-06-07 04:32 20120607042743.vcdiff
-rw-r--r--
user/user      22 2012-06-07 04:32 20120607043221.revision
-rw-r--r--
user/user      58 2012-06-07 04:39 20120607043221.vcdiff
-rw-r--r--
user/user    2193 2012-06-07 04:39 20120607043911.tar.gz
-rw-r--r--
user/user      19 2012-06-07 04:39 20120607043911.revision

e così via.

Test

Il tar tvf è stato solo un sistema per farvi capire come funziona l'archiviazione e quanto pulito sia il risultato, tuttavia noi da quell'insieme di files inutili facciamo fatica a ricavarci qualcosa, per questo usiamo il semplicissimo comando di test che mette le cose sotto una luce completamente differente :

timearchive T planning.tar

20120607042743  Prima versione di test
20120607043221  Aggiunto piccolo file
20120607043911  Tolto piccolo file


Il codice della versione è quindi la data e in fianco la descrizione. Quel codice è comodo per il copia e incolla e lo useremo sia per eXtract che per Rollback.

eXtract

Vogliamo ricavare la versione a cui avevamo aggiunto quel famoso piccolo file, il comando di test ci dice che è la numero 20120607043221 , creiamo una cartella temporanea, giusto per non usare il . e quindi aggiungere i files alla planning corrente e diamo il comando :

timearchive X planning.tar temp 20120607043221

dove temp è ovviamente la cartella.

il risultato è che nella cartella temp, troveremo una cartella planning corredata anche del famoso piccolo file, nonostante l'avessimo cancellato, ma cosa è accaduto ?

Semplicemente è stato estratto il tar.gz , cioè 20120607043911.tar.gz e sono state applicate in sequenza tutte le patch fino ad arrivare a quella col codice corretto, risalendo quindi alla corretta versione. In questo l'unica patch era 20120607043221.vcdiff che ci ha riportato direttamente alla penultima versione, se fossimo stati alla decima versione e avessimo chiesto la seconda, sarebbero state applicate 8 patch.

In questo modo è possibile ripristinare qualsiasi documento intermedio.

Ovviamente l'archivio non è modificato durante l'estrazione, per farlo c'è Rollback.

Rollback

Rollback inizialmente non c'era e l'ho aggiunto in seguito perché in alcuni pacchetti avevo eseguito una serie di Update errati.

Siccome eXtract, non modifica in alcun modo l'archivio, per tornare indietro si può usare Rollback, quindi se vogliamo tornare alla versione col file aggiunto possiamo applicare allo stesso modo di eXtract, il comando :

timearchive R planning.tar 20120607043221 

Il risultato è un archivio così composto :

timearchive T planning.tar
 

20120607042743  Prima versione di test
20120607043221  Aggiunto piccolo file


In cui è scomparsa l'ultima versione.

Interessante è esaminarlo con tar tvf :

tar tvf planning.tar
-rw-r--r--
user/user      23 2012-06-07 04:27 20120607042743.revision
-rw-r--r--
user/user      89 2012-06-07 04:32 20120607042743.vcdiff
-rw-r--r--
user/user      22 2012-06-07 04:32 20120607043221.revision
-rw-r--r--
user/user    2174 2012-06-07 05:01 20120607043221.tar.gz

Il rollback lavora come l'extract cancellando di volta in volta i files e poi reinserisce il risultato patchato come ultimo update, sempre senza scompattare i tar ovviamente.

Lo script

Dopo avervi spiegato come funziona, e i comandi principali eccovi ovviamente l'intero script, se state usando un sistema tipo Posix,  inseritelo in un file di testo di nome timearchive, e rendetelo eseguibile, altrimenti inventatevi qualcosa.

Spero vi torni utile, divertitevi !
 
#!/bin/bash

function repatch
{
  fil1=`tempfile`
  fil2=`tempfile`
  fil3=`tempfile`
  while read l
  do
    t=${l#*.}
    if [ "$t" != "revision" ]
    then
      if [ "$t" == "tar.gz" ]
      then
        tar xf $1 $l -O | gzip -d > $fil1
      else
        tar xf $1 $l -O >$fil2
        xdelta3 -f -d -s $fil1 $fil2 $fil3
        cp $fil3 $fil1
      fi
      tr=${l%%.*}
      if [ "$tr" == "$3" ]
      then
         cd $2
         tar xfps $fil1
         break
      fi
    fi
  done
  rm $fil1
  rm $fil2
  rm $fil3
}

function rollback
{
  fil1=`tempfile`
  fil2=`tempfile`
  fil3=`tempfile`
  tmpdir=`mktemp -d`
  while read l
  do
    t=${l#*.}
    if [ "$t" != "revision" ]
    then
      if [ "$t" == "tar.gz" ]
      then
        tar xf $1 $l -O | gzip -d > $fil1
        tar --delete --file $1 $l
      else
        tar xf $1 $l -O >$fil2
        xdelta3 -f -d -s $fil1 $fil2 $fil3
        cp $fil3 $fil1
        tar --delete --file $1 $l
      fi
      tr=${l%%.*}
      if [ "$tr" == "$3" ]
      then
         cd $tmpdir
         gzip --best <$fil1 >$tr.tar.gz
         tar rf $1 $tr.tar.gz
         break
      fi
    else
      tar --delete --file $1 $l         
    fi
  done
  cd $2
  rm $tmpdir/*
  rmdir $tmpdir
  rm $fil1
  rm $fil2
  rm $fil3
}


function showcontent
{
  while read l
  do
    if [ ${l##*\.} != "revision" ]
    then
      echo -n ${l%%\.*}"  "
      tar xf $1 ${l%%\.*}.revision -O
    fi
  done
}


function intcontent
{
  while read l
  do
    if [ ${l##*\.} != "revision" ]
    then
      echo ${l%%\.*}
    fi
  done
}


case $1 in

U)
  dirfull=$3
  if [ "${dirfull:0:1}" != "/" ]
  then
    dirfull=`pwd`/$dirfull
  fi
  if [ ! -d  $dirfull ]
  then
    echo "$3 must be an existing directory"
    exit
  fi
  archfull=$2
  if [ "${archfull:0:1}" != "/" ]
  then
    archfull=`pwd`/$archfull
  fi

  if [ -e $archfull ]
  then
    last=`tar -tf $archfull | grep "\.tar.gz"`
    if [ -z "$last" ]
    then
      echo "The archive does not contain any tar.gz file"
      exit
    fi
    # Creates Working Directory
    tmpdir=`mktemp -d`
    # Get Current Date
    todate=`date +%Y%m%d%H%M%S`
    tar xf $archfull $last -O | gzip -d >$tmpdir/oldimage
    tar --delete --file $archfull $last
    # Create the new archive
    cd ${dirfull%/*}
    tar cfps $tmpdir/$todate.tar ${dirfull##*/}
    # Get Old DIFF name
    olddiff=${last%%.tar.gz}.vcdiff
    # Delta Analysis
    cd $tmpdir
    xdelta3 -S djw -s $todate.tar oldimage $olddiff
    tar rf $archfull $olddiff
    gzip --best $todate.tar
    tar rf $archfull $todate.tar.gz
    echo $4 >$tmpdir/$todate.revision
    tar -C $tmpdir -rf $archfull $todate.revision
    cd ..
    rm $tmpdir/*
    rmdir $tmpdir
  else
    # Creates Working Directory
    tmpdir=`mktemp -d`
    # Get Current Date
    todate=`date +%Y%m%d%H%M%S`
    cd ${dirfull%/*}
    tar cfps $tmpdir/$todate.tar ${dirfull##*/}
    cd $tmpdir
    gzip --best $todate.tar
    tar cf $archfull $todate.tar.gz
    echo $4 >$tmpdir/$todate.revision
    tar -C $tmpdir -rf $archfull $todate.revision
    cd ..
    rm $tmpdir/*
    rmdir $tmpdir
  fi

;;

X)
  dirfull=$3
  if [ "${dirfull:0:1}" != "/" ]
  then
    dirfull=`pwd`/$dirfull
  fi
  if [ ! -d  $dirfull ]
  then
    echo "$3 must be an existing directory"
    exit
  fi
  archfull=$2
  if [ "${archfull:0:1}" != "/" ]
  then
    archfull=`pwd`/$archfull
  fi
  if [ ! -e $archfull ]
  then
     echo "Archive not found"
     exit
  fi
  if [ -z "$4" ]
  then
    last=`tar tf $archfull | grep "\.tar.gz"`
    fil=`tempfile`
    tar xvf $archfull $last -O > $fil
    cd $dirfull
    tar xvzfps $fil
    rm $fil
  else
    testtar=`tar tf $archfull| intcontent | grep "^$4$" `
    if [ -z "$testtar" ]
    then
       echo "Record not found"
       exit
    fi
    tar tf $archfull | sort -r | repatch "$archfull" "$dirfull" $4
  fi
;;

R)
  archfull=$2
  dirfull=`pwd`
  if [ "${archfull:0:1}" != "/" ]
  then
    archfull=`pwd`/$archfull
  fi
  if [ ! -e $archfull ]
  then
     echo "Archive not found"
     exit
  fi
  if [ -z "$3" ]
  then
     echo "Missing rollback record"
     exit
  fi   
  testtar=`tar tf $archfull| intcontent | grep "^$3$" `
  if [ -z "$testtar" ]
  then
   echo "Record not found"
   exit
  fi
  tar tf $archfull | sort -r | rollback "$archfull" "$dirfull" $3
 
;;


T)
    tar tf $2 | sort | showcontent $2
;;

*)
  echo Bad paramenter, please use U,T,R or X.
;;
esac

Nessun commento:

Posta un commento