Verificación de archivo antiguo CON marcas de tiempo originales creadas / modificadas

Respuestas:

45

Creo que las únicas marcas de tiempo registradas en la base de datos de Git son las marcas de tiempo de autor y de confirmación. No veo una opción para que Git modifique la marca de tiempo del archivo para que coincida con la confirmación más reciente, y tiene sentido que este no sea el comportamiento predeterminado (porque si lo fuera, Makefiles no funcionaría correctamente).

Puede escribir un script para establecer la fecha de modificación de sus archivos a la hora de la confirmación más reciente. Podría verse algo como esto:

IFS="
"
for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso -- "$FILE")
    TIME=$(date -j -f '%Y-%m-%d %H:%M:%S %z' "$TIME" +%Y%m%d%H%M.%S)
    touch -m -t "$TIME" "$FILE"
done
Dietrich Epp
fuente
10
Hay varios problemas con este fragmento: 1: falla si hay espacios en los nombres de archivo; 2 - Puede fallar para proyectos que tengan más de unos pocos miles de archivos; 3 - el rendimiento es absolutamente miserable en cualquier proyecto de tamaño mediano con unos pocos miles de confirmaciones (incluso con pocos archivos)
MestreLion
10
+1 tal vez no funcione para todos los casos posibles, pero es una buena respuesta simple.
qwerty9967
5
¿No es la pregunta del OP cómo preservar las marcas de tiempo modificadas del archivo original, no ajustar la marca de tiempo de confirmación a los archivos?
BT
15
Diseñar un VCS alrededor de Make es miope. Creo que esto es culpa de Git. Entonces, realmente no tiene sentido que no sea un comportamiento predeterminado. Los archivos Make deben ejecutarse en el contenido de los archivos, no en las marcas de tiempo. Hacer un hash del archivo y ver si el hash coincide con lo que creó es mucho más sólido.
BT
4
Estoy de acuerdo con BT y partes de su comentario Dietrich. Lo que BT quiso decir sobre el OP es que su respuesta realmente no permite mantener la hora original del archivo. En cambio, los reemplaza con la hora de pago original. No es lo mismo ... Entonces , creo que claramente dijo que tu publicación contiene errores de hecho. Y puedo ver de dónde vino la decisión de no almacenar marcas de tiempo, como usted señala. También creo que BT está despotricando un poco sobre ese razonamiento allí. Con lo que estoy de acuerdo con BT nuevamente, no hay buenas razones para no poder hacerlo en absoluto. Todos los demás VCS pueden hacerlo.
cregox
56

, metastore o git-cache-meta pueden almacenar dicha (meta) información. Git por sí solo, sin herramientas de terceros, no puede. Metastore o git-cache-meta pueden almacenar cualquier metadato de archivo para un archivo.

Eso es por diseño, ya que metastore o git-cache-meta están pensados ​​para ese mismo propósito, además de admitir utilidades de respaldo y herramientas de sincronización.

(Lo siento, solo un pequeño giro divertido en la respuesta de Jakub)

BT
fuente
8
¡Incluso imitaste sus mayúsculas! Si aplica la negrita también, estoy seguro de que obtendrá aún más votos a favor. ;-)
Michael Scheper
1
Así que estoy un poco molesto principalmente porque ambas herramientas (después de profundizar un poco en ellas) dejan caer la pelota de manera espectacular en macOS. Son completamente no portátiles fuera de Linux. git-cache-meta se basa en findla -printfextensión de GNU , y estoy casi seguro de que metastore (siendo un proyecto de C) es aún más complicado de hacer portátil. Muy desafortunado. Volveré a publicar aquí si descubro que esta situación cambia.
Steven Lu
40

NO , Git simplemente no almacena dicha (meta) información , a menos que utilice herramientas de terceros como metastore o git-cache-meta. La única marca de tiempo que se almacena es la hora en que se creó el parche / cambio (hora del autor) y la hora en que se creó la confirmación (hora del confirmador).

Eso es por diseño, ya que Git es un sistema de control de versiones, no una utilidad de respaldo o herramienta de sincronización.

Jakub Narębski
fuente
¿Hay compilación de metastore para win32? ¿O debería volver a crear scripts / ganchos para Windows? Franklt, no necesito otros attrs, solo mtime
Arioch 'El
7
Creo que tu respuesta es en realidad "¡SÍ! ¡Metastore o git-cache-meta pueden hacer esto por ti!" Supongo que es la diferencia entre actitudes derrotistas y optimistas.
BT
2
Además, como escuché, bazaar y mercurial también son "sistemas de control de versiones" que almacenan metainformación. No hay nada de malo en hacerlo.
Cregox
Aclaración: Git mantiene dos marcas de tiempo para cada archivo: la fecha del autor (que creo que es lo que Jakub quiere decir con 'parche de tiempo') y la fecha del autor. La primera es la hora a la que se confirmó por primera vez el archivo y la última es la hora a la que se confirmó más recientemente.
Michael Scheper
4
"Eso es por diseño, ya que Git es un sistema de control de versiones, no una utilidad de respaldo o una herramienta de sincronización". Eso es una incongruencia : ignorar los metadatos ( especialmente las fechas, que están íntimamente relacionadas con las versiones) no tiene nada que ver con ser un VCS o una herramienta de respaldo. Además, cada VCS tiene una gran superposición inherente de funcionalidad con las herramientas de respaldo: ambos se esfuerzan por preservar estados pasados ​​importantes. Finalmente, incluso Git no ignora todos los metadatos (por ejemplo, rastrea el bit de ejecución), a pesar de ser un VCS. Sin embargo, todavía lo es por diseño, solo por una razón diferente: el enfoque exclusivo de Git en el contenido.
Sz.
13

ACTUALIZACIÓN : TL; DR: git en sí mismo no guarda los tiempos originales, pero algunas soluciones evitan esto mediante varios métodos. git-restore-mtimees uno de ellos:

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

Ubuntu / Debian: sudo apt install git-restore-mtime
Fedora / RHEL / CentOS:sudo yum install git-tools

Vea mi otra respuesta para más detalles

Descargo de responsabilidad completo: soy el autor de git-tools


Este script de Python puede ayudar: para cada archivo se aplica la marca de tiempo de la confirmación más reciente donde se modificó el archivo:

A continuación se muestra una versión realmente básica del guión. Para el uso real, sugiero encarecidamente una de las versiones más sólidas anteriores:

#!/usr/bin/env python
# Bare-bones version. Current dir 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

Todas las versiones analizan el registro completo generado por un solo git whatchangedcomando, que es cientos de veces más rápido que recortar cada archivo. Menos de 4 segundos para git (24.000 confirmaciones, 2.500 archivos) y menos de 1 minuto para el kernel de Linux (40.000 archivos, 300.000 confirmaciones)

MestreLion
fuente
2
¡Tu otra respuesta similar es mucho mejor que esta!
cregox
$ python ./git-restore-mtime Traceback (most recent call last): File "./git-restore-mtime", line 122, in <module> 'git rev-parse --show-toplevel --git-dir')).split('\n')[:2] TypeError: Type str doesn't support the buffer API¿Le importaría decirnos qué versión de Python se necesita? Estoy usando 3.3.3
Rolf
@Cawas: Gracias ... supongo. Pero el código en ambas respuestas es idéntico, por lo que no estoy seguro de por qué cree que la otra es mejor. La única diferencia es que algunos despotrican sobre git. Lo cual era algo pertinente para esa pregunta, pero no para esta.
MestreLion
1
@Rolf: Usé Python 2.7, y parece que el código necesita algunos ajustes en Python 3, gracias por señalarlo. La razón es: stren Python 2 es el equivalente de bytestringen Python 3, mientras que stren Python 3 es unicodeen Python 2. ¿Puede informar este problema en github.com/MestreLion/git-tools/issues ?
MestreLion
No es solo la "perorata". Allí también explicas lo que hace el código con mucho más detalle y, por tanto, con claridad.
cregox
6

Esto me hizo un truco en ubuntu (que carece del indicador "-j" de OSX en la fecha (1))

for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso $FILE)
    TIME2=`echo $TIME | sed 's/-//g;s/ //;s/://;s/:/\./;s/ .*//'`
    touch -m -t $TIME2 $FILE
done 
eludom
fuente
4

He estado peleando con git y marcas de tiempo de archivos durante algún tiempo.

Probé algunas de tus ideas e hice mis propios scripts tremendamente grandes y predecesores / ram, hasta que encontré (en algún wiki de git) un script en perl que hace casi lo que quería. https://git.wiki.kernel.org/index.php/ExampleScripts

Y lo que quería es poder conservar la última modificación de archivos basada en fechas de confirmación.

Entonces, después de un reajuste, el script puede cambiar la fecha de creación y modificación de 200k archivos en aproximadamente 2-3 minutos .

#!/usr/bin/perl
my %attributions;
my $remaining = 0;

open IN, "git ls-tree -r --full-name HEAD |" or die;
while (<IN>) {
    if (/^\S+\s+blob \S+\s+(\S+)$/) {
        $attributions{$1} = -1;
    }
}
close IN;

$remaining = (keys %attributions) + 1;
print "Number of files: $remaining\n";
open IN, "git log -r --root --raw --no-abbrev --date=raw --pretty=format:%h~%cd~ |" or die;
while (<IN>) {
    if (/^([^:~]+)~([^~]+)~$/) {
        ($commit, $date) = ($1, $2);
    } elsif (/^:\S+\s+1\S+\s+\S+\s+\S+\s+\S\s+(.*)$/) {
        if ($attributions{$1} == -1) {
            $attributions{$1} = "$date";
            $remaining--;

            utime $date, $date, $1;
            if ($remaining % 1000 == 0) {               
                print "$remaining\n";
            }
            if ($remaining <= 0) {
                break;
            }
        }
    }
}
close IN;

Suponiendo que sus repositorios no tendrán más de 10k archivos, esto debería tomar unos segundos en ejecutarse, por lo que puede conectarlo al checkout, pull u otros ganchos básicos de git.

Lukasz Kruszyna
fuente
2

Aquí está mi solución que toma en consideración las rutas que contienen espacios:

#! /bin/bash

IFS=$'\n'
list_of_files=($(git ls-files | sort))
unset IFS

for file in "${list_of_files[@]}"; do
  file_name=$(echo $file)

  ## When you collect the timestamps:
  TIME=$(date -r "$file_name" -Ins)

  ## When you want to recover back the timestamps:
  touch -m -d $TIME "$file_name"
done

Tenga en cuenta que esto no toma el tiempo que git loginforma, es el tiempo informado por el sistema. Si desea conocer el tiempo transcurrido desde que se comprometieron los archivos, utilice la git logsolución en lugar dedate -r

Lilian A. Moraru
fuente
2

Native git no tiene la funcionalidad, pero se puede lograr mediante scripts de gancho o herramientas de terceros.

Lo he intentado metastore. Es muy rápido, pero no me gusta la necesidad de instalar y que los metadatos no se almacenan en formato de texto sin formato. git-cache-metaes una herramienta simple que he probado, pero es extremadamente lenta para repositorios grandes (para un repositorio con decenas de miles de archivos, se necesitan minutos para actualizar el archivo de metadatos) y podría tener problemas de compatibilidad entre plataformas. setgitpermsy otros enfoques también tienen sus defectos que no me gustan.

Por fin hice un script de gancho para este trabajo: git-store-meta . Tiene una dependencia muy ligera (* nix shell`` sorty perl, que es requerido por git, y opcionalmente chown, chgrpy touch) para que no se tenga que instalar nada adicional para una plataforma que pueda ejecutar git, rendimiento deseable (para un repositorio con decenas de miles de archivos, se necesitan menos de 10 segundos para actualizar el archivo de metadatos; aunque más para crearlo), guarda los datos en formato de texto plano , y qué metadatos se "guardar" o "cargar" es personalizable .

Me ha funcionado bien. Pruebe esto si no está satisfecho con metastore, git-cache-meta y otros enfoques.

Danny Lin
fuente
2

Espero que aprecies la sencillez:

# getcheckin - Retrieve the last committed checkin date and time for
#              each of the files in the git project.  After a "pull"
#              of the project, you can update the timestamp on the
#              pulled files to match that date/time.  There are many
#              that believe that this is not a good idea, but
#              I found it useful to get the right source file dates
#
#              NOTE: This script produces commands suitable for
#                    piping into BASH or other shell
# License: Creative Commons Attribution 3.0 United States
# (CC by 3.0 US)

##########
# walk back to the project parent or the relative pathnames don't make
# sense
##########
while [ ! -d ./.git ]
do
    cd ..
done
echo "cd $(pwd)"
##########
# Note that the date format is ISO so that touch will work
##########
git ls-tree -r --full-tree HEAD |\
    sed -e "s/.*\t//" | while read filename; do
    echo "touch --date=\"$(git log -1 --date=iso --format="%ad" -- "$filename")\" -m $filename" 
done
tonto
fuente
(Para su información, hay una doble negación no intencionada en el comentario del encabezado, que es posible que también desee corregir en el original: "Hay muchos que no creen que esto no sea una buena idea".)
Sz.
1

Para el entorno de Windows, escribí un pequeño (rápido y sucio) EXE en Delphi 10.1 Berlín que recopila todas las fechas de archivo en el árbol de fuentes en el archivo .gitfilattr y puede aplicarlas nuevamente en el árbol de fuentes comprobado.

Por supuesto que comparto el código en GitHub:

https://github.com/michaschumann/gitfiledates/blob/master/gitFileDates.dpr

Lo uso en mi sistema de compilación basado en corredores de GitLab.

MichaSchumann
fuente
1

Hay cierta ambigüedad en mi (y en la de otros) interpretación del OP sobre si esto significa el tiempo de compromiso o algo más, pero asumiendo que significa tiempo de compromiso, entonces esta simple frase funcionará en Linux (según el fragmento de respuesta de Dietrich Epp ):

git ls-files | xargs -I{} bash -c 'touch "{}" --date=@$(git log -n1 --pretty=format:%ct -- "{}")'

Pero hay respuestas más sofisticadas (incluidos los ganchos de git) vinculadas desde un comentario a la pregunta original de cregox.

mza
fuente
lol, esto arrojó una gran cantidad de archivos en mi pago con el nombre--date=@foo
mxcl
0

Con herramientas GNU.

s=$(git ls-files  | wc -l); 
git ls-files -z  |
 xargs -0 -I{} -n1 bash -c \
"git log --date=format:%Y%m%d%H%M.%S '--pretty=format:touch -m -t %cd \"{}\"%n' -n1 -- {}"|
 pv -l -s$s |
 parallel -n1 -j8

 967  0:00:05 [ 171 /s] [=====================================>  ] 16% 

.

$ git --version ; xargs --version | sed 1q ; ls --version | sed 1q;
  parallel --version  | sed 1q;  pv --version | sed 1q; sh --version | sed 1q 
git version 2.13.0
xargs (GNU findutils) 4.6.0
ls (GNU coreutils) 8.25
GNU parallel 20150522
pv 1.6.0 - Copyright 2015 Andrew Wood <[email protected]>
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Ярослав Рахматуллин
fuente
El paralelismo no parece hacer mucho, probablemente un cuello de botella fs. YMMV
Ярослав Рахматуллин
0

En CentOS 7 tiene /usr/share/doc/rsync-*/support/git-set-file-timesy en Debian (y derivados) el mismo script /usr/share/doc/rsync/scripts/git-set-file-times.gz, el original es de Eric Wong y está aquí https://yhbt.net/git-set-file-times .

Funciona más rápido que otros ejemplos mencionados aquí y puede que le resulte más útil tenerlo ya en su distribución de Linux.

Iván Baldo
fuente
0

Aquí está el mío.

Un poco más rápido que otros, ya que no estoy llamando a 'obtener registro' para cada archivo encontrado; en su lugar, llamar a 'git log' una vez y transformar esa salida en comandos táctiles.

Habrá casos en los que haya demasiados archivos enumerados en una confirmación para que quepan en un solo búfer de comando de shell; ejecute "getconf ARG_MAX" para ver la longitud máxima de un comando en bytes - en mi instalación de Debian, es 2MB, que es suficiente.

# set file last modification time to last commit of file
git log --reverse --date=iso --name-only | \
  grep -vE "^(commit |Merge:|Author:|    |^$)" | \
  grep -B 1 "^[^D][^a][^t][^e][^:][^ ]" | \
  grep -v "^\-\-" | \
  sed "s|^\(.*\)$|\"\1\"|;s|^\"Date: *\(.*\)\"$|~touch -c -m -d'\1'|" | \
  tr '~\n' '\n ' | \
  sh -

descripción por línea:

  • primera lista de confirmaciones y nombres de archivo
  • filtrar líneas de confirmación / fusión / autor innecesarias
  • filtrar las líneas que comienzan con guiones dobles
  • comando sed (stream-edit) a) anteponer / agregar comillas dobles a las líneas, yb) reemplazar "Fecha:. " con ~ touch -c -m -d. (las opciones del comando táctil son -c = no crear si no existe, -m = cambiar la hora de modificación del archivo y -d = usar la fecha / hora proporcionada)
  • traducir tilda (~) y nueva línea (\ n) caracteres a nueva línea y espacio, respectivamente
  • canalice el flujo resultante de líneas de texto en un shell.

En términos de velocidad, 5 segundos 1700 se confirman para 6500 archivos en 700 directorios.

jmullee
fuente