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
(groupevcs
) 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 utilisergit(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