Utiliser un VCS pour gérer des fichiers (système)

etckeeper, c'est joli mais ça ne fonctionne que pour /etc sauf à créer plusieurs dépôts par système. De plus, toutes les solutions connues recourent à (au moins) un répertoire .foo.

On va donc :

  • créer un utilisateur vcs (groupe vcs) avec pour répertoire /var/vcs
  • dans /var/vcs, une copie des fichiers tels qu'ils existent
  • /var/vcs est un dépôt (ici Git).

Pour initialiser, utiliser le script suivant :

#!/bin/ksh
#
# $Id: 0737ef5ddc4cddf4ab801276207f5140a4b8e2c5 $
#
PATH=/bin:/sbin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin:/usr/local/scripts:/usr/local/bin
export PATH
 
addr="netadmin@local.net"
repo="/var/vcs"
users_home="/root /users/pc"
log="/var/log/$(basename $0).log"
 
groupadd -g 85 vcs
useradd -g vcs -d /var/vcs -m -c "VCS user" -M 0700 -u 85 -s /bin/ksh vcs
 
rm -f ~vcs/.profile && touch ~vcs/.profile
echo "# $0 $(date)" >> ~vcs/.profile
echo "export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/pkg/bin:/usr/pkg/sbin:\$HOME/bin" >> ~vcs/.profile
echo "export EDITOR=vi" >> ~vcs/.profile
echo "export PAGER=less" >> ~vcs/.profile
echo "umask 0027" >> ~vcs/.profile
 
touch $log
chmod 0600 $log
 
ls -A > .gitignore
echo ".lesshst" >> .gitignore
echo "*.sw[a-z]" >> .gitignore
echo "*,v" >> .gitignore
 
cd $repo || exit 1
git init
 
printf "\twhitespace = -trailing-space,-space-before-tab\n" >> $repo/.git/config
printf "[apply]\n" >> $repo/.git/config
printf "\twhitespace = fix\n" >> $repo/.git/config
 
git config user.name "VCS User"
git config user.email "vcs@$(hostname).local.net"
git config core.editor $(which vi)
git config core.sharedRepository "umask"
 
git add .gitignore
$hostname > $repo/.git/description
 
umask 0027
 
echo "XXX --------- $(pwd)"
cd $repo
cp /boot.cfg .
cp -r /etc .
mkdir -p usr/pkg && cp -r /usr/pkg/etc usr/pkg
git add .
git commit -a -m 'system files in /etc & /usr/pkg/etc added'
 
chown -R vcs:vcs $repo
 
echo "==> repo:host-$(hostname).git"

Ensuite, on versionne les fichiers après chaque modification avec :

#!/usr/pkg/bin/perl
#
# $Id: 780073bd6f1bccc789a01bf3bd466f02bdcbc7cf $
 
use Cwd;
use Getopt::Std;
use File::Basename;
use File::Copy;
use File::Path;
use File::Spec;
use File::Temp;
use Text::Wrap;
 
$ENV{'PATH'} = "/bin:/usr/bin:/usr/pkg/bin:/usr/local/scripts:/usr/local/bin";
 
$ENV{PAGER} = "less" if ( not defined $ENV{PAGER} );
 
my $fqdn = "local.net";
my $repo = "/var/vcs";
my %args;
 
sub usage {
    print
"$0: [ -t #1234 ][ -c \"bla bla\" ] | [ -l ] [ -D ] [ -d ] file1 [ file2 ... ]\n";
    print
      "\t\t-t #1234: numero de ticket, ajoute au commentaire lors du commit\n";
    print
      "\t\t-c \"bla bla\": commentaire, ajoute au commentaire lors du commit\n";
    print "\t\t-l: TODO liste les revisions du fichier\n";
    print "\t\t-D: do diff(1)\n";
    print "\t\t-d: debug messages\n";
}
 
#
# -------------------- affiche un diff(1) --------------------
#
sub do_diff {
    my @files = @_;
    my $error = 0;
 
    foreach my $f (@files) {
        my ( $real_dir, $fn ) = locate_file($f);
        if (   ( not -f "$repo/$real_dir/$fn" )
            || ( not -f "/$real_dir/$fn" ) )
        {
            warn "XXX fichier '$repo/$real_dir/$fn' inexistant $!\n";
            $error++;
        }
        else {
            printf "--------------- /$real_dir/$fn\n";
            system
"diff -E -w -B -N -s -u $repo/$real_dir/$fn /$real_dir/$fn | $ENV{PAGER}";
        }
    }
    return $error;
}
 
#
# -------------------- le fichier reel --------------------
#
sub locate_file {
    my $f = shift;
 
    my $dir = dirname $f;
    my $fn  = basename $f;
 
    my $real_dir = Cwd::realpath($dir);
    $real_dir =~ s/^\/*//;
    return ( $real_dir, $fn );
}
 
getopts( "t:c:dleD", \%args );
warn "XXX ------------ DEBUG VERSION\n" if ( $args{d} );
 
#
# -------------------- verification des arguments --------------------
#
if ( not $args{l} ) {
    if (   ( not defined $args{D} )
        && ( length $args{t} == 0 )
        && ( length $args{c} == 0 ) )
    {
        warn "XXX A minima indiquer un commentaire (-c \"bla bla bla\")"
          . "ou un ticket (-t 1234) ou faire un diff(1)\n";
    }
}
 
#
# -------------------- generation du commentaire --------------------
#
my $comment = "";
$comment .= "[tkt #$args{t}]" if ( defined $args{t} );
$comment .= " " if ( ( defined $args{c} ) && ( defined $args{t} ) );
$comment .= "$args{c}" if ( defined $args{c} );
 
my $log = File::Temp->new( UNLINK => 0 )
  or die "Can't create temporary file: $!\n";
my $commit_log = $log->filename();
 
if ( defined $args{e} ) {
    my $tmp = File::Temp->new()
      or die "Can't create temporary file: $!\n";
    my $editor = $ENV{'EDITOR'};
    $editor = "vi" if $editor eq qq{};
 
    my $commit_tmp = $tmp->filename();
 
    print $tmp $comment;
    print $tmp "\n\n";
    system("$editor $commit_tmp");
 
    if ( defined $args{d} ) {
        print "XXX fichier a la fin :\n'";
        print system("cat $commit_tmp");
        print "'\n";
        getc STDIN;
    }
 
    # XXX juste pour eviter textproc/p5-Text-Unaccent
    seek $commit_tmp, 0, 0;
    my @cmt;
    while (<$tmp>) {
        s/[<E0><E4><E2>]/a/g;
        s/[<E9><E8><EB><EA>]/e/g;
        s/[<EF><EE><EC>]/i/g;
        s/[<F4><F2>]/o/g;
        s/[<F9><FB>]/u/g;
        s/<E7>/c/g;
        push @cmt, $_;
    }
    close $tmp;
 
    if ( defined $args{d} ) {
        print "XXX commentaire a la fin :\n'@cmt'\n";
        getc STDIN;
    }
 
    $Text::Wrap::columns = 72;
    print $log wrap( '', '', @cmt );
 
    if ( defined $args{d} ) {
        system("cat $commit_log");
        getc STDIN;
    }
}
 
#
# -------------------- l'utilisateur... tout sauf root --------------------
#
my $user = "";
if ( ( defined $ENV{'USER'} ) && ( $ENV{'USER'} ne "root" ) ) {
    $user .= "$ENV{'USER'}";
}
elsif ( ( defined $ENV{'SUDO_USER'} ) && ( $ENV{'SUDO_USER'} ne "root" ) ) {
    $user .= "$ENV{SUDO_USER}";
}
elsif ( ( defined $ENV{'SU_FROM'} ) && ( $ENV{'SU_FROM'} ne "root" ) ) {
    $user .= "$ENV{'SU_FROM'}";
}
elsif ( ( defined $ENV{'LOGNAME'} ) && ( $ENV{'LOGNAME'} ne "root" ) ) {
    $user .= "$ENV{'LOGNAME'}";
}
else {
    $user = getlogin();
}
my @u = getpwnam($user);
$ENV{GIT_AUTHOR_NAME}  = "$u[6]";
$ENV{GIT_AUTHOR_EMAIL} = "$u[0]\@$fqdn";
#
# -------------------- traitement des fichiers --------------------
#
my @files = @ARGV;
if ( $#files < 0 ) {
    warn "XXX aucun fichier passe en argument\n";
    usage();
    exit 1;
}
else {
    my $all_files = "";
 
    # --------------- diff(1)
    if ( length $args{D} > 0 ) {
        my $error = do_diff(@files);
 
        # -D est incompatible avec -c / -t et -l
        $error == 0 ? exit 0 : exit 1;
    }
 
    # --------------- log
    if ( $args{l} ) {
        my $error = 0;
        foreach my $f (@files) {
            my ( $real_dir, $fn ) = locate_file($f);
 
            if (   ( not -f "$repo/$real_dir/$fn" )
                || ( not -f "/$real_dir/$fn" ) )
            {
                warn "XXX fichier '$repo/$real_dir/$fn' inexistant $!\n";
                $error++;
            }
            else {
                if ( $args{d} ) {
                    print "cd $repo ; git log $real_dir/$fn\n";
                }
 
                system "cd $repo ; git log $real_dir/$fn";
            }
        }
 
        # -l est incompatible avec -c et/ou -t
        $error == 0 ? exit 0 : exit 1;
    }
 
    foreach my $f (@files) {
        my ( $real_dir, $fn ) = locate_file($f);
        if ( not -f "/$real_dir/$fn" ) {
            warn "XXX fichier '$f' inexistant $!\n";
            exit 1;
        }
        else {
            mkpath "$repo/$real_dir" if ( not -d "$repo/$real_dir" );
 
            copy( "/$real_dir/$fn", "$repo/$real_dir/$fn" )
              or die "XXX echec de la copie de '$f': $!";
 
            # --------------- liste des fichiers a commiter
            $all_files .= " $real_dir/$fn";
 
            # --------------- git add
            $args{d}
              ? print "'git add $real_dir/$fn'\n"
              : system "cd $repo && git add $real_dir/$fn";
        }
    }
 
    do_diff(@files);
 
    # --------------- commit
    print "\n---- commit ----\n";
    if ( not defined $args{e} ) {
        system "cd $repo && git commit -m \"$comment\" $all_files";
    }
    else {
        system "cd $repo && git commit --file=$commit_log $all_files";
        close $log;
    }
}

À ce stade :

  • pour voir les modifications d'un fichier :
    $ sudo version -d /path/to/file
  • pour voir l'historique d'un fichier :
    $ sudo version -l /path/to/file
  • pour commiter les modifications d'un fichier :
    $ sudo version -t 4321 -c "my comment"  /path/to/file

    le commentaire du commit se résumera à : [tkt #4321] my comment, utile si on utilise un système de tickets

  • pour le reste, il faut passer root et utiliser git(1) as usual.

On peut améliorer encore et tous les jours lancer un ramasse-miettes :

#!/bin/ksh
#
# $Id: 463db40a0dda25d45f6a0254e9615c2b495e5988 $
 
export PATH=/bin:/usr/bin:/usr/pkg/bin:/usr/local/bin
 
repo="/var/vcs"
 
chmod 0700 $repo
cd $repo
 
printf "\ndaily import in $repo :\n"
printf "==========================\n"
# wrokgin file vs vcs
for f in $(git ls-files); do
        if [[ -f "/$f" ]]; then
                diff $f "/$f" 2>&1 >/dev/null
                if [[ $? -ne 0 ]]; then
                        cp "/$f" $f
                fi
        else
                echo "/$f vanished => git rm $f"
                git rm $f
        fi
done
 
# files in dirs
for dir in $(for f in $(git ls-files); do echo $(dirname $f); done | sort | uniq); do
        for file in $(ls /$dir); do
                if [[ -f /$dir/$file && ! -f $repo/$dir/$file ]]; then
                        cp /$dir/$file $repo/$dir/$file
                        git add $repo/$dir/$file
                fi
        done
done
echo "--------- status :"
git status
echo "--------- diff :"
git diff
echo "--------- commit :"
git commit -a -m "daily commit $(date '+%Y-%m-%d')"
git gc
git fsck --strict
 
printf "Repository $repo size :"
du -sh $repo
Ce site web utilise des cookies. En utilisant le site Web, vous acceptez le stockage de cookies sur votre ordinateur. Vous reconnaissez également que vous avez lu et compris notre politique de confidentialité. Si vous n'êtes pas d'accord, quittez le site.En savoir plus
  • blog/version.txt
  • Dernière modification : 2013/03/17 16:58
  • de pc