Versiones automáticas al cambiar el archivo (modificar / crear / eliminar)


16

Estoy buscando una implementación (en Linux) de un mecanismo que versione de forma automática y transparente cualquier cambio en un directorio (recursivamente). Esto pretende ser una adición (posiblemente un reemplazo si todas las características solicitadas están disponibles) a las versiones estándar (SVN, git, ...)

Un producto en MS Windows que hace esto es AutoVer (para tener una mejor idea de los requisitos). Me encantaría tener algo así pero dirigido a Linux en un entorno no gráfico.

Vi que hay algunos intentos de tener esta funcionalidad en Linux, la más cercana que encontré es la autoversión en Subversion, pero no es obvio implementarla en entornos existentes (servidores donde, por ejemplo, los archivos de configuración son locales).

Tal vez algo trabajando con inotify?

Gracias de antemano por cualquier puntero! WoJ


relacionado: flashbake
Dan D.


¿Hay algún requisito especial sobre qué software utiliza? Porque si solo está buscando rastrear los cambios que realiza manualmente (editando archivos), Eclipse tiene esta característica incorporada, se llama "historial local".
Stefan Seidel

@StefanSeidel No soy el iniciador del tema, pero preferiría una solución sin IDE.
Michael Pankov

Respuestas:


5

1. Método de uso general usando bazar e inotify

Esto no he sido probado por mí, pero encontré esta escritura que hace uso de bzr(bazar) y inotifywaitpara monitorear un directorio y controlar la versión de los archivos en él usando bazar.

Este script hace todo el trabajo de observar el directorio en busca de cambios:

#!/bin/bash

# go to checkout repository folder you want to watch
cd path/to/www/parent/www
# start watching the directory for changes recusively, ignoring .bzr dir
# comment is made out of dir/filename
# no output is shown from this, but wrinting a filename instead of /dev/null 
# would allow logging
inotifywait –exclude \.bzr -r -q -m -e CLOSE_WRITE \
    –format=”bzr commit -m ‘autocommit for %w/%f’” ./ | \
    sh  2>/dev/null 1>&2 &
# disown the pid, so the inotify thread will get free from parent process
# and will not be terminated with it
PID=`ps aux | grep inotify | grep CLOSE_WRITE | grep -v grep | awk ‘{print $2}’`
disown $PID

# this is for new files, not modifications, optional
inotifywait –exclude \.bzr -r -q -m -e CREATE \
    –format=”bzr add *; bzr commit -m ‘new file added %w/%f’” ./ | \
    sh  2>/dev/null 1>&2 &
PID=`ps aux | grep inotify | grep CREATE | grep -v grep | awk ‘{print $2}’`
disown $PID

exit 0;

2. Gestión / etc.

Para el caso especial de administrar el directorio de su sistema /etc, puede usar la aplicación etckeeper .

etckeeper es una colección de herramientas para permitir que / etc se almacene en un repositorio git, mercurial, darcs o bzr. Se conecta a apt (y otros gestores de paquetes, incluidos yum y pacman-g2) para confirmar automáticamente los cambios realizados en / etc durante las actualizaciones de paquetes. Realiza un seguimiento de los metadatos de archivos que los sistemas de control de revisión normalmente no son compatibles, pero eso es importante para / etc, como los permisos de / etc / shadow. Es bastante modular y configurable, a la vez que es fácil de usar si comprende los conceptos básicos de trabajar con el control de revisión.

Aquí hay un buen tutorial para comenzar a usarlo.

3. Usando git e incron

Esta técnica hace uso de gity incron. Para este método, debe hacer lo siguiente:

A. Hacer un repositorio

% mkdir $HOME/git
% cd $HOME/git
% git init

B. Crear un $HOME/bin/git-autocommitguión

#!/bin/bash

REP_DIR="$HOME/git"       # repository directory
NOTIFY_DIR="$HOME/srv"    # directory to version

cd $REP_DIR
GIT_WORK_TREE=$NOTIFY_DIR /usr/bin/git add .
GIT_WORK_TREE=$NOTIFY_DIR /usr/bin/git commit -a -m "auto"

C. Agregar una entrada a incrontab

% sudo incrontab -e $HOME/srv IN_MODIFY,IN_CREATE,IN_MOVED_FROM,IN_MOVED_TO $HOME/bin/git-autocommit

4. Usando Flashbake

Otra opción es usar una herramienta como Flashbake . Flashbake es el sistema de control de versiones que Cory Doctorow (de la fama de BoingBoing) usa para escribir sus libros.

Flashbake usa git debajo del capó para rastrear los cambios, pero está en algún lugar entre hacer copias de seguridad automáticas y usar un sistema de control de versiones simple.

Cory quería que la versión llevara avisos, instantáneas de dónde estaba en el momento en que ocurrió una confirmación automática y lo que estaba pensando. Rápidamente dibujé un script de Python para obtener la información contextual que quería y comencé a hackear un script de shell para manejar git, usando la salida del script de Python para el comentario de confirmación cuando un trabajo cron invocaba el contenedor de shell.

Recursos


3
inotifywait + "git local" = gitwatch.sh, mira aquí: github.com/nevik/gitwatch/blob/master/gitwatch.sh
diyism


3

Creo que estás en el camino correcto inotify. Este artículo detalla su uso básico en un caso similar al suyo. Sugeriría usarlo directamente o compilar una utilidad de nivel de núcleo como fschange . Esto es una molestia, pero puede vincular la detección de cambios a uno git commito similar.

Ambas soluciones tienen el problema de confiar en soluciones de terceros algo imperfectas. Si no le importa ensuciarse las manos, NodeJS proporciona una excelente instalación multiplataforma ( fs.watch ) para este propósito exacto. Un tutorial básico en la observación de los cambios en los archivos de NodeJS se puede encontrar aquí . En unas pocas docenas de líneas o menos, podría escribir algo que observe un directorio de archivos, y luego se despliegue (a través de child_process ) y ejecute un git commito similar (o incluso incremente manualmente un índice de archivo de versión, si le gusta el roll-your- enfoque propio).

fs.watchestá respaldado por inotifyLinux, pero es mucho más intuitivo de usar. Hay otros proyectos de NodeJS que envuelven esa funcionalidad de observación de archivos en varios niveles de conveniencia, como este o este .


Todavía no es una solución lista y, bueno, probablemente iría con Python inotify. Pero gracias.
Michael Pankov

3

inotify (2) en Linux no podrá ver un árbol grande, pero el sistema de archivos fusible (montado en una ubicación separada) probablemente podría manejarlo, traduciendo las solicitudes del sistema de archivos a llamadas svn o git, o cambiando los metadatos svn / git directamente.

Esta es una idea muy interesante, pero no había oído hablar de ninguna implementación existente.


Digamos que solo tengo un par de archivos.
Michael Pankov

0

Tal guión no es difícil de escribir.

Mi control de versiones favorito es git.

El siguiente script debería hacerlo:

#!/bin/sh
git add .
git commit -am "my automatic commit"

o haga que verifique periódicamente su directorio, o si su editor es programable, llame después de guardar.

Pero si lo hace así, podría tener sentido excluir archivos grandes y tal vez algunos "inútiles" como los autoguardados.


Sí, sé que una solución basada en cron es fácil de implementar. Sin embargo, estoy buscando algo que se ejecute en guardar, sin importar el mecanismo de guardar. Esta es también la razón por la que mencioné autoversionninf en svn y también inotify en mi pregunta.
WoJ

0

SparkleShare ( http://sparkleshare.org ) se basa en git e implementa una funcionalidad similar a Dropbox con control de versiones, pero debe configurar un servidor ssh (puede ser localhost).


Esto es torpe y requiere mucha configuración. Además, la funcionalidad de Dropbox no es necesaria.
Michael Pankov


0

También hay una forma de "pobre" de hacer esto usando solo rsync y un trabajo cron. Básicamente confía en la función de copia de seguridad de rsync y utiliza dos rutas separadas más un prefijo / sufijo para realizar un seguimiento de sus archivos.

Parece más o menos así: / usr / bin / rsync -a -A -X --backup --suffix = date +".%Y-%m-%d_%H-%M-%S"$ source_path $ backup_path

Resultado final: al cambiar un archivo llamado test_rsync en la ruta de origen después de la ejecución inicial, se creará un archivo llamado test_rsync.2017-02-09_11-00-01 en la ruta de respaldo.

Hay un montón de problemas con esto (funciona si solo tiene una cantidad decente de archivos y fallará por los cambios que ocurran entre dos ejecuciones consecutivas de rsync (1 minuto en mi caso)) pero puede ser suficiente para sus necesidades.

Si estamos hablando aquí sobre las acciones de samba, una lista de exclusión podría estar en orden, no he llegado a eso todavía, me temo.

Avísame si mejoras esto.


0

Aquí hay una secuencia de comandos de Python3 que hace VMS como el versionado automático de archivos usando una marca de tiempo añadida al nombre del archivo original cuando se guarda.

Puse un montón de comentarios en el script y ejecuté media docena de dichos scripts en mi máquina ubuntu con solo los directorios diferentes en cada versión diferente del script para que esté versionando simultáneamente varios directorios. No hay pena real para el rendimiento de las máquinas.

! / usr / bin / env python3

print ("VERSIÓN DE ARCHIVOS DE PROYECTO INICIADA") print ("version_creation.py") # coloca todo este código en el script de este nombre print ("ejecuta como ... 'python3 version_creation.py' desde la línea de comandos") print ("ctrl ' c 'para detener ") print (" ") print (" Para ejecutar el programa en segundo plano, escriba debajo de la línea de comando y luego cierre la ventana. ") print (" nohup python3 version_creation.py ") print (" .... to detenga el proceso vaya al menú / administración / monitor del sistema ... y elimine python3 ") print (" ") print (" Siempre guarde los archivos en el directorio 'ProjectFiles' y los archivos de versión ") print (" también se crearán en ese directorio . ") print (" ") print (" ") print (" ") print (" ")

import shutil import os tiempo de importación

--- establece el intervalo de tiempo para buscar nuevos archivos (en segundos) a continuación

- ¡este intervalo debe ser menor que el intervalo que aparecen los archivos nuevos!

t = 10

--- establece el directorio de origen (dr1) y el directorio de destino (dr2)

dr1 = "/ ruta / a / directorio_origen"

dr2 = "/ ruta / a / target_directory"

import glob import os

dr1 = "/ home / michael / ProjectFiles" # ambos originales y versiones se guardarán en este directorio

dr2 = "/ home / michael / ProjectFileVersions"

mientras cierto:

if os.listdir(dr1) == []:

print ("Vacío")

    n = 100
else:
    list_of_files = glob.glob(dr1+'/*')   # * means all if need specific format then *.csv
    latest_file_path = max(list_of_files, key=os.path.getctime)

print ("1 Latest_file_path =", latest_file_path)

    originalname = latest_file_path.split('/')[-1]

print ("2 originalname =", originalname)

    filecreation = (os.path.getmtime(latest_file_path))

print ("filecreation =", filecreation)

    now = time.time()
    fivesec_ago = now - 5 # Number of seconds

print ("fivesec_ago =", fivesec_ago)

    timedif = fivesec_ago - filecreation #time between file creation

print ("timedif =", timedif)

    if timedif <= 5: #if file created less than 5 seconds ago

        nameroot = originalname.split(".")[-0]
        print ("3 nameroot= ", nameroot)

        extension = os.path.splitext(originalname)[1][1:]
        print ("4 extension = ", extension)

        curdatetime = time.strftime('%Y%m%d-%H%M%S')
        print ("5 curdatetime = ", curdatetime)

        newassembledname = (nameroot + "_" + curdatetime + "." + extension)
        print ("6 newassembledname = ", newassembledname)



        source = dr1+"/"+originalname
        print ("7 source = ", source)

        target = dr1+"/"+newassembledname
        print ("8 target = ", target)

        shutil.copy(source, target)


    time.sleep(t)

compartir

el siguiente se colocó antes y funciona, pero me gusta el script de python anterior mucho mejor ... (he estado usando python durante aproximadamente 3 horas)

#!/usr/bin/env python3

print ("PROJECT FILES VERSIONING STARTED")
print ("projectfileversioning.py")
print ("run as..  'python3 projectfileversioning.py'       from command line")
print ("ctrl 'c'      to stop")
print (" ")
print ("To run program in background type below to command line and then close the window. ")
print ("nohup python3 projectfileversioning.py")
print ("....to stop process go menu/administration/system monitor... and kill python")
print (" ")
print ("Always save files to the 'ProjectFiles' directory and the file ")
print ("   will be redirected to the ProjectFileVersions where")
print ("   time stamped versions will also be created.")
print (" ")
print ("If you like you may then copy/move the versioned and original file from 'ProjectFileVersions' to ")
print ("any other directory you like.")

import shutil
import os
import time

#--- set the time interval to check for new files (in seconds) below 
#-   this interval should be smaller than the interval new files appear!
t = 10

#--- set the source directory (dr1) and target directory (dr2)
#dr1 = "/path/to/source_directory"
#dr2 = "/path/to/target_directory"

import glob
import os

dr1 = "/home/michael/ProjectFiles"
dr2 = "/home/michael/ProjectFileVersions"


while True:

    if os.listdir(dr1) == []:
        n = 100
    else:
        list_of_files = glob.glob(dr1+'/*')   # * means all if need specific format then *.csv
        latest_file_path = max(list_of_files, key=os.path.getctime)
        print ("1 Latest_file_path = ", latest_file_path)

        originalname = latest_file_path.split('/')[-1]
        print ("2 originalname = ", originalname)

        nameroot = originalname.split(".")[-0]
        print ("3 nameroot= ", nameroot)

        extension = os.path.splitext(originalname)[1][1:]
        print ("4 extension = ", extension)

        curdatetime = time.strftime('%Y%m%d-%H%M%S')
        print ("5 curdatetime = ", curdatetime)

        newassembledname = (nameroot + "_" + curdatetime + "." + extension)
        print ("6 newassembledname = ", newassembledname)




        source = dr1+"/"+originalname
        print ("7 source = ", source)

        target = dr2+"/"+originalname
        print ("8 target = ", target)

        shutil.copy(source, target)



        source = dr1+"/"+originalname
        print ("9 source = ", source)

        target = dr2+"/"+newassembledname
        print ("10 target = ", target)

        shutil.move(source, target)
        time.sleep(t)


#share
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.