¿Cuál es el equivalente de los "tiempos de confirmación de uso" de Subversion para Git?

Resuelto Ben W asked hace 14 años • 11 respuestas

Necesito que las marcas de tiempo de los archivos en mi sistema local y en mi servidor estén sincronizadas. Esto se logra con Subversion configurando use-commit-times=true en la configuración para que la última modificación de cada archivo sea cuando se confirmó.

Cada vez que clono mi repositorio, quiero que las marcas de tiempo de los archivos reflejen cuándo se cambiaron por última vez en el repositorio remoto, no cuándo cloné el repositorio.

¿Hay alguna manera de hacer esto con Git?

Ben W avatar Dec 27 '09 04:12 Ben W
Aceptado

ACTUALIZACIÓN : Mi solución ahora está empaquetada en Debian , Ubuntu , Linux Mint , Fedora , Gentoo Linux , Arch Linux y posiblemente en otras distribuciones:

https://github.com/MestreLion/git-tools#install

apt install git-restore-mtime  # Debian, Ubuntu, Linux Mint
yum install git-tools          # Fedora, Red Hat Enterprise Linux (RHEL), CentOS
emerge dev-vcs/git-tools       # Gentoo Linux
pacman -S git-tools-git        # Arch Linux

En mi humilde opinión, no almacenar marcas de tiempo (y otros metadatos como permisos y propiedad) es una gran limitación de Git.

La justificación de Linus de que las marcas de tiempo son dañinas sólo porque "confunden make" es poco convincente :

  • make cleanes suficiente para solucionar cualquier problema.

  • Se aplica solo a proyectos que utilizan make, principalmente C/C++. Es completamente discutible para scripts como Python, Perl o documentación en general.

  • Sólo hay daño si aplicas las marcas de tiempo. No haría ningún daño almacenarlos en un repositorio. Aplicarlos podría ser una --with-timestampsopción sencilla para git checkouty amigos ( clone, pull, etc.), a criterio del usuario .

Tanto Bazaar como Mercurial almacenan metadatos. Los usuarios pueden aplicarlos o no al realizar el pago. Pero en Git, dado que las marcas de tiempo originales ni siquiera están disponibles en el repositorio, no existe tal opción.

Entonces, para obtener una ganancia muy pequeña (no tener que volver a compilar todo) que es específica de un subconjunto de proyectos, Git como DVCS general quedó paralizado , se perdió parte de la información de los archivos y, como dijo Linus, no es factible hazlo ahora. Triste .

Dicho esto, ¿puedo ofrecer dos enfoques?

1 - http://repo.or.cz/w/metastore.git , de David Härdeman. Intenta hacer lo que Git debería haber hecho en primer lugar : almacena metadatos (no solo marcas de tiempo) en el repositorio cuando se confirma (a través de un gancho de confirmación previa) y los vuelve a aplicar al extraer (también a través de ganchos).

2 - Mi humilde versión de un script que usé antes para generar archivos comprimidos de lanzamiento. Como se mencionó en otras respuestas, el enfoque es un poco diferente : aplicar para cada archivo la marca de tiempo de la confirmación más reciente donde se modificó el archivo.

  • git-restore-mtime , con muchas opciones, admite cualquier diseño de repositorio y se ejecuta en Python 3.

A continuación se muestra una versión realmente básica del script, como prueba de concepto, en Python 2.7. Para uso real, recomiendo encarecidamente la versión completa anterior:

#!/usr/bin/env python
# Bare-bones version. Current directory must be top-level of work tree.
# Usage: git-restore-mtime-bare [pathspecs...]
# By default update all files
# Example: to only update only the README and files in ./doc:
# git-restore-mtime-bare README doc

import subprocess, shlex
import sys, os.path

filelist = set()
for path in (sys.argv[1:] or [os.path.curdir]):
    if os.path.isfile(path) or os.path.islink(path):
        filelist.add(os.path.relpath(path))
    elif os.path.isdir(path):
        for root, subdirs, files in os.walk(path):
            if '.git' in subdirs:
                subdirs.remove('.git')
            for file in files:
                filelist.add(os.path.relpath(os.path.join(root, file)))

mtime = 0
gitobj = subprocess.Popen(shlex.split('git whatchanged --pretty=%at'),
                          stdout=subprocess.PIPE)
for line in gitobj.stdout:
    line = line.strip()
    if not line: continue

    if line.startswith(':'):
        file = line.split('\t')[-1]
        if file in filelist:
            filelist.remove(file)
            #print mtime, file
            os.utime(file, (mtime, mtime))
    else:
        mtime = long(line)

    # All files done?
    if not filelist:
        break

El rendimiento es bastante impresionante, incluso para proyectos monstruosos wine, gito incluso para el kernel de Linux:

Bash
# 0.27 seconds
# 5,750 log lines processed
# 62 commits evaluated
# 1,155 updated files

Git
# 3.71 seconds
# 96,702 log lines processed
# 24,217 commits evaluated
# 2,495 updated files

Wine
# 13.53 seconds
# 443,979 log lines processed
# 91,703 commits evaluated
# 6,005 updated files

Linux kernel
# 59.11 seconds
# 1,484,567 log lines processed
# 313,164 commits evaluated
# 40,902 updated files
MestreLion avatar Nov 08 '2012 07:11 MestreLion

Sin embargo, si realmente desea utilizar tiempos de confirmación para las marcas de tiempo al realizar el pago, intente usar este script y colóquelo (como ejecutable) en el archivo $GIT_DIR/.git/hooks/post-checkout:

#!/bin/sh -e

OS=${OS:-`uname`}
old_rev="$1"
new_rev="$2"

get_file_rev() {
    git rev-list -n 1 "$new_rev" "$1"
}

if   [ "$OS" = 'Linux' ]
then
    update_file_timestamp() {
        file_time=`git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1`
        touch -d "$file_time" "$1"
    }
elif [ "$OS" = 'FreeBSD' ]
then
    update_file_timestamp() {
        file_time=`date -r "$(git show --pretty=format:%at --abbrev-commit "$(get_file_rev "$1")" | head -n 1)" '+%Y%m%d%H%M.%S'`
        touch -h -t "$file_time" "$1"
    }
else
    echo "timestamp changing not implemented" >&2
    exit 1
fi

IFS=`printf '\t\n\t'`

git ls-files | while read -r file
do
    update_file_timestamp "$file"
done

Sin embargo, tenga en cuenta que este script provocará un retraso bastante grande en la extracción de repositorios grandes (donde grande significa una gran cantidad de archivos, no tamaños de archivos grandes).

Giel avatar Jan 10 '2010 21:01 Giel

No estoy seguro de que esto sea apropiado para un DVCS (como en VCS "Distribuido")

La gran discusión ya había tenido lugar en 2007 (ver este hilo)

Y algunos de los que respondieron Linus no estaban muy interesados ​​en la idea. Aquí hay una muestra:

Lo lamento. Si no ve que es INCORRECTO establecer una marca de fecha en algo que hará que un simple "make" compile mal su árbol fuente, no sé de qué definición de "incorrecto" está hablando.
Está incorrecto.
Es estúpido.
Y es totalmente INFACTIBLE de implementar.


(Nota: pequeña mejora: después de realizar el pago, las marcas de tiempo de los archivos actualizados ya no se modifican (Git 2.2.2+, enero de 2015): "git checkout: ¿cómo puedo mantener las marcas de tiempo al cambiar de rama?" .)


La respuesta larga fue:

Creo que es mucho mejor usar múltiples repositorios, si esto es algo común.

Jugar con las marcas de tiempo no funcionará en general. Simplemente le garantizará que " make" se confunda de una manera realmente mala y no recompile lo suficiente en lugar de recompilar demasiado .

Git hace posible hacer lo de "verificar la otra rama" muy fácilmente, de muchas maneras diferentes.

Podrías crear algún script trivial que haga cualquiera de las siguientes cosas (desde lo trivial hasta lo más exótico):

  • simplemente crea un nuevo repositorio:

      git clone old new
      cd new
      git checkout origin/<branch>
    

y ahí estás. Las marcas de tiempo antiguas están bien en su repositorio anterior y puede trabajar (y compilar) en el nuevo, sin afectar el anterior en absoluto.

Utilice las banderas " -n -l -s" a " git clone" para que esto sea básicamente instantáneo. Para muchos archivos (por ejemplo, repositorios grandes como el kernel), no será tan rápido como simplemente cambiar de rama, pero tener una segunda copia del árbol de trabajo puede ser bastante poderoso.

  • haz lo mismo con solo una bola de alquitrán, si quieres

      git archive --format=tar --prefix=new-tree/ <branchname> |
              (cd .. ; tar xvf -)
    

lo cual es realmente bastante rápido, si sólo quieres una instantánea.

  • Acostúmbrese a " git show" y simplemente mire los archivos individuales.
    En realidad, esto es realmente útil a veces. tu solo hazlo

      git show otherbranch:filename
    

en una ventana de xterm y mire el mismo archivo en su rama actual en otra ventana.

En particular, esto debería ser trivial con editores de secuencias de comandos (es decir, GNU emacs), donde debería ser posible tener básicamente un "modo directo" completo para otras ramas dentro del editor, usando esto.
Por lo que sé, el modo git de emacs ya ofrece algo como esto (no soy un usuario de emacs)

  • y en el ejemplo extremo de ese "directorio virtual", había al menos alguien trabajando en un complemento git para FUSE, es decir, literalmente podría tener directorios virtuales que mostraran todas sus ramas.

y estoy seguro de que cualquiera de las anteriores son mejores alternativas que jugar juegos con marcas de tiempo de archivos.

Lino

VonC avatar Dec 26 '2009 22:12 VonC