¿Cuál es el equivalente de use-commit-times para git?

97

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

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 forma de hacer esto con git?

Ben W
fuente
Como parte de mi proceso de implementación, subo activos (imágenes, archivos javascript y archivos css) a un CDN. Cada nombre de archivo se adjunta con la última marca de tiempo modificada. Es importante que no expire todos mis activos cada vez que despliegue. (Otro efecto secundario de use-commit-times es que puedo hacer este proceso en mi local y sé que mi servidor hará referencia a los mismos archivos, pero eso no es tan importante). Si en lugar de hacer un clon de git, hice un git fetch seguido de un git reset --duro desde mi repositorio remoto, que funcionaría para un solo servidor, pero no para varios servidores, ya que las marcas de tiempo en cada uno serían diff.
Ben W
@BenW: git annexpodría ser útil para realizar un seguimiento de las imágenes
jfs
Puede comprobar qué ha cambiado comprobando las identificaciones. Está tratando de hacer que las marcas de tiempo del sistema de archivos signifiquen lo mismo que las marcas de tiempo de vcs. No significan lo mismo.
Jthill

Respuestas:

25

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 algunas de las respuestas de Linus no estaban muy interesadas en la idea. Aquí hay una muestra:

Lo siento. Si no ve cómo es INCORRECTO volver a establecer un sello de fecha en algo que hará que un simple "make" compile mal su árbol de fuentes, no sé de qué definición de "incorrecto" está hablando.
Está incorrecto.
Es estúpido.
Y es totalmente INFEASIBLE de implementar.


(Nota: pequeña mejora: después de un 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 sucursal?" ).


La respuesta larga fue:

Creo que es mucho mejor usar varios repositorios en su lugar, si esto es algo común.

En general, jugar con las marcas de tiempo no funcionará. Simplemente le garantizará que "make" se confunde de una manera realmente mala y no se recompila lo suficiente en lugar de recompilar demasiado .

Git hace que sea posible hacer su "comprobación de la otra ramificación" muy fácilmente, de muchas formas diferentes.

Puede crear un script trivial que haga cualquiera de las siguientes cosas (desde lo trivial hasta lo más exótico):

  • solo 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 antiguo repositorio, y puede trabajar (y compilar) en el nuevo, sin afectar el antiguo en absoluto.

Use las banderas "-n -l -s" para "git clone" para hacer esto básicamente instantáneo. Para muchos archivos (por ejemplo, repositorios grandes como el kernel), no será tan rápido como 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 -)

que es bastante rápido, si solo quieres una instantánea.

  • acostumbrarse a " git show" y mirar archivos individuales.
    Esto es realmente útil a veces. Tu solo haces
    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 en el caso de editores con script (es decir, GNU emacs), donde debería ser posible básicamente tener 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 de git para FUSE, es decir, literalmente, podría tener directorios virtuales mostrando todas sus ramas.

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

Linus

VonC
fuente
5
Convenido. No debería confundir un DVCS con un sistema de distribución. gites un DVCS, para manipular el código fuente que se construyó en su producto final. Si desea un sistema de distribución, ya sabe dónde encontrarlo rsync.
Randal Schwartz
14
Hm, tendré que confiar en su argumento de que es inviable. Si es incorrecto o estúpido es otro asunto. Verifico mis archivos usando una marca de tiempo y los subo a un CDN, por lo que es importante que las marcas de tiempo reflejen cuándo se modificó realmente el archivo, no cuándo se eliminó por última vez del repositorio.
Ben W
3
@Ben W: la "respuesta de Linus" no está aquí para decir que está mal en su situación particular. Está ahí solo como recordatorio de que un DVCS no es adecuado para ese tipo de característica (preservación de la marca de tiempo).
VonC
15
@VonC: Dado que otros DVCS modernos como Bazaar y Mercurial manejan bien las marcas de tiempo, prefiero decir que " git no es adecuado para ese tipo de función". Si "un" DVCS debería tener esa característica es discutible (y creo firmemente que sí).
MestreLion
10
Esta no es una respuesta a la pregunta, sino una discusión filosófica sobre los méritos de hacer esto en un sistema de control de versiones. Si a la persona le hubiera gustado eso, habría preguntado: "¿Cuál es la razón por la que git no usa el tiempo de confirmación para el tiempo de modificación de los archivos?"
thomasfuchs
85

Sin embargo, si realmente desea usar los 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 esta secuencia de comandos provocará una demora bastante grande para verificar repositorios grandes (donde grande significa una gran cantidad de archivos, no grandes tamaños de archivo).

Giel
fuente
55
+1 para obtener una respuesta real, en lugar de simplemente decir "No hagas eso"
DanC
4
| head -n 1debe evitarse, ya que genera un nuevo proceso, -n 1para git rev-listy git logse puede utilizar en su lugar.
eregon
3
Es mejor NO leer líneas con `...`y for; consulte Por qué no lee líneas con "para" . Yo iría por git ls-files -zy while IFS= read -r -d ''.
musiphil
2
¿Es posible una versión de Windows?
Ehryk
2
en lugar de lo git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1que puede hacer git show --pretty=format:%ai -s "$(get_file_rev "$1")", hace que el showcomando genere muchos menos datos y debería reducir la sobrecarga.
Scott Chamberlain
79

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

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

sudo apt install git-restore-mtime  # Debian/Ubuntu/Mint
yum install git-tools               # Fedora/ RHEL / CentOS
emerge dev-vcs/git-tools            # Gentoo

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

El razonamiento de Linus de que las marcas de tiempo sean dañinas solo porque "confunden make" es poco convincente :

  • make clean es 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.

  • Solo hay daño si aplica las marcas de tiempo. No haría ningún daño almacenarlos en repositorio. Aplicarlos podría ser una --with-timestampsopción sencilla para git checkouty amigos ( clone, pulletc.), a discreción del usuario .

Tanto Bazaar como Mercurial almacenan metadatos. Los usuarios pueden aplicarlos o no al momento de pagar. 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 una ganancia muy pequeña (no tener que volver a compilar todo) que es específica de un subconjunto de proyectos, gitya que un DVCS general se paralizó , se pierde parte de la información de los archivos y, como dijo Linus, es INFEASIBLE hacerlo Es ahora. Sad .

Dicho esto, ¿puedo ofrecer 2 enfoques?

1 - http://repo.or.cz/w/metastore.git , por 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 versión humilde de un script que usé antes para generar tarballs 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 el uso real, recomiendo encarecidamente la versión completa anterior:

#!/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

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
fuente
2
Pero git hace marcas de tiempo de las tiendas, etc. Simplemente no establece las marcas de tiempo por defecto. Basta con mirar a la salidagit ls-files --debug
Ross Smith II
9
@RossSmithII: git ls-filesopera en el directorio y el índice de trabajo, por lo que no significa que realmente almacene esa información en el repositorio. Si almacenara, recuperar (y aplicar) mtime sería trivial.
MestreLion
13
"El razonamiento de Linus de que las marcas de tiempo sean dañinas solo porque" confunden a make "es poco convincente". De acuerdo al 100%, ¡un DCVS no debería saber ni preocuparse por el código que contiene! Nuevamente, esto muestra las trampas de intentar reutilizar herramientas escritas para casos de uso específicos en casos de uso generales. Mercurial es y siempre será una opción superior porque fue diseñado, no evolucionado.
Ian Kemp
6
@davec De nada, me alegro de que haya sido útil. La versión completa en github.com/MestreLion/git-tools ya maneja Windows, Python 3, nombres de ruta no ASCII, etc. El script anterior es solo una prueba de concepto funcional, evítelo para uso en producción.
MestreLion
3
Tus argumentos son válidos. Espero que alguien con algo de influencia haga una solicitud de mejora para que git tenga tu opción sugerida --with-timestamps.
weberjn
12

Tomé la respuesta de Giel y en lugar de usar un script de enlace posterior a la confirmación, lo trabajé en mi script de implementación personalizado.

Actualización : también | head -neliminé uno siguiendo la sugerencia de @ eregon y agregué soporte para archivos con espacios en ellos:

# Adapted to use HEAD rather than the new commit ref
get_file_rev() {
    git rev-list -n 1 HEAD "$1"
}

# Same as Giel's answer above
update_file_timestamp() {
    file_time=`git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1`
    sudo touch -d "$file_time" "$1"
}

# Loop through and fix timestamps on all files in our CDN directory
old_ifs=$IFS
IFS=$'\n' # Support files with spaces in them
for file in $(git ls-files | grep "$cdn_dir")
do
    update_file_timestamp "${file}"
done
IFS=$old_ifs
Alex Dean
fuente
Gracias Daniel, es útil saberlo
Alex Dean
el comando --abbrev-commites superfluo git showdebido a que --pretty=format:%aise usa (el hash de confirmación no es parte de la salida) y | head -n 1podría reemplazarse con el uso de la -sbandera paragit show
Elan Ruusamäe
1
@ DanielS.Sterling: %aies la fecha del autor, formato similar a ISO 8601 , para uso estricto de iso8601 %aI: git-scm.com/docs/git-show
Elan Ruusamäe
4

nos vimos obligados a inventar otra solución, porque necesitábamos tiempos de modificación específicos y no tiempos de compromiso, y la solución también tenía que ser portátil (es decir, conseguir que Python funcionara en las instalaciones git de Windows no es una tarea sencilla) y rápida. Se parece a la solución de David Hardeman, que decidí no usar debido a la falta de documentación (del repositorio no pude hacerme una idea de qué hace exactamente su código).

Esta solución almacena mtimes en un archivo .mtimes en el repositorio de git, los actualiza en consecuencia en las confirmaciones (jsut selectivamente los mtimes de los archivos en etapas) y los aplica al finalizar la compra. Funciona incluso con las versiones cygwin / mingw de git (pero es posible que deba copiar algunos archivos de cygwin estándar en la carpeta de git)

La solución consta de 3 archivos:

  1. mtimestore: secuencia de comandos central que proporciona 3 opciones -a (guardar todo: para la inicialización en un repositorio ya existente (funciona con archivos git-versed)), -s (para guardar cambios en etapas) y -r para restaurarlos. En realidad, esto viene en 2 versiones: una bash (portátil, agradable, fácil de leer / modificar) y la versión c (una desordenada pero rápida, porque mingw bash es terriblemente lento, lo que hace imposible usar la solución bash en proyectos grandes).
  2. gancho de compromiso previo
  3. gancho posterior al pago

pre cometido:

#!/bin/bash
mtimestore -s
git add .mtimes

post-pago

#!/bin/bash
mtimestore -r

mtimestore - bash:

#!/bin/bash

function usage 
{
  echo "Usage: mtimestore (-a|-s|-r)"
  echo "Option  Meaning"
  echo " -a save-all - saves state of all files in a git repository"
  echo " -s save - saves mtime of all staged files of git repository"
  echo " -r restore - touches all files saved in .mtimes file"
  exit 1
}

function echodate 
{
  echo "$(stat -c %Y "$1")|$1" >> .mtimes
}

IFS=$'\n'

while getopts ":sar" optname
do
  case "$optname" in
    "s")
      echo "saving changes of staged files to file .mtimes"
      if [ -f .mtimes ]
      then
        mv .mtimes .mtimes_tmp
        pattern=".mtimes"
        for str in $(git diff --name-only --staged)
        do
          pattern="$pattern\|$str"
        done
        cat .mtimes_tmp | grep -vh "|\($pattern\)\b" >> .mtimes
      else
        echo "warning: file .mtimes does not exist - creating new"
      fi

      for str in $(git diff --name-only --staged)
      do
        echodate "$str" 
      done
      rm .mtimes_tmp 2> /dev/null
      ;;
    "a")
      echo "saving mtimes of all files to file .mtimes"
      rm .mtimes 2> /dev/null
      for str in $(git ls-files)
      do
        echodate "$str"
      done
      ;;
    "r")
      echo "restorim dates from .mtimes"
      if [ -f .mtimes ]
      then
        cat .mtimes | while read line
        do
          timestamp=$(date -d "1970-01-01 ${line%|*} sec GMT" +%Y%m%d%H%M.%S)
          touch -t $timestamp "${line##*|}"
        done
      else
        echo "warning: .mtimes not found"
      fi
      ;;
    ":")
      usage
      ;;
    *)
      usage
      ;;
esac

mtimestore - c ++

#include <time.h>
#include <utime.h>
#include <sys/stat.h>
#include <iostream>
#include <cstdlib>
#include <fstream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <ctime>
#include <map>


void changedate(int time, const char* filename)
{
  try
  {
    struct utimbuf new_times;
    struct stat foo;
    stat(filename, &foo);

    new_times.actime = foo.st_atime;
    new_times.modtime = time;
    utime(filename, &new_times);
  }
  catch(...)
  {}
}

bool parsenum(int& num, char*& ptr)
{
  num = 0;
  if(!isdigit(*ptr))
    return false;
  while(isdigit(*ptr))
  {
    num = num*10 + (int)(*ptr) - 48;
    ptr++;
  }
  return true;
}

//splits line into numeral and text part - return numeral into time and set ptr to the position where filename starts
bool parseline(const char* line, int& time, char*& ptr)
{
  if(*line == '\n' || *line == '\r')
    return false;
  time = 0;
  ptr = (char*)line;
  if( parsenum(time, ptr))
  { 
    ptr++;
    return true;
  }
  else
    return false;
}

//replace \r and \n (otherwise is interpretted as part of filename)
void trim(char* string)
{
  char* ptr = string;
  while(*ptr != '\0')
  {
    if(*ptr == '\n' || *ptr == '\r')
      *ptr = '\0';
    ptr++;
  }
}


void help()
{
  std::cout << "version: 1.4" << std::endl;
  std::cout << "usage: mtimestore <switch>" << std::endl;
  std::cout << "options:" << std::endl;
  std::cout << "  -a  saves mtimes of all git-versed files into .mtimes file (meant to be done on intialization of mtime fixes)" << std::endl;
  std::cout << "  -s  saves mtimes of modified staged files into .mtimes file(meant to be put into pre-commit hook)" << std::endl;
  std::cout << "  -r  restores mtimes from .mtimes file (that is meant to be stored in repository server-side and to be called in post-checkout hook)" << std::endl;
  std::cout << "  -h  show this help" << std::endl;
}

void load_file(const char* file, std::map<std::string,int>& mapa)
{

  std::string line;
  std::ifstream myfile (file, std::ifstream::in);

  if(myfile.is_open())
  {
      while ( myfile.good() )
      {
        getline (myfile,line);
        int time;
        char* ptr;
        if( parseline(line.c_str(), time, ptr))
        {
          if(std::string(ptr) != std::string(".mtimes"))
            mapa[std::string(ptr)] = time;
        }
      }
    myfile.close();
  }

}

void update(std::map<std::string, int>& mapa, bool all)
{
  char path[2048];
  FILE *fp;
  if(all)
    fp = popen("git ls-files", "r");
  else
    fp = popen("git diff --name-only --staged", "r");

  while(fgets(path, 2048, fp) != NULL)
  {
    trim(path);
    struct stat foo;
    int err = stat(path, &foo);
    if(std::string(path) != std::string(".mtimes"))
      mapa[std::string(path)]=foo.st_mtime;
  }
}

void write(const char * file, std::map<std::string, int>& mapa)
{
  std::ofstream outputfile;
  outputfile.open(".mtimes", std::ios::out);
  for(std::map<std::string, int>::iterator itr = mapa.begin(); itr != mapa.end(); ++itr)
  {
    if(*(itr->first.c_str()) != '\0')
    {
      outputfile << itr->second << "|" << itr->first << std::endl;   
    }
  }
  outputfile.close();
}

int main(int argc, char *argv[])
{
  if(argc >= 2 && argv[1][0] == '-')
  {
    switch(argv[1][1])
    {
      case 'r':
        {
          std::cout << "restoring modification dates" << std::endl;
          std::string line;
          std::ifstream myfile (".mtimes");
          if (myfile.is_open())
          {
            while ( myfile.good() )
            {
              getline (myfile,line);
              int time, time2;
              char* ptr;
              parseline(line.c_str(), time, ptr);
              changedate(time, ptr);
            }
            myfile.close();
          }
        }
        break;
      case 'a':
      case 's':
        {
          std::cout << "saving modification times" << std::endl;

          std::map<std::string, int> mapa;
          load_file(".mtimes", mapa);
          update(mapa, argv[1][1] == 'a');
          write(".mtimes", mapa);
        }
        break;
      default:
        help();
        return 0;
    }
  } else
  {
    help();
    return 0;
  }

  return 0;
}
  • tenga en cuenta que los ganchos se pueden colocar en el directorio de plantillas para automatizar su ubicación

se puede encontrar más información aquí https://github.com/kareltucek/git-mtime-extension. Hay información desactualizada en http://www.ktweb.cz/blog/index.php?page=page&id=116

// editar - versión de c ++ actualizada:

  • Ahora la versión de C ++ mantiene el orden alfabético -> menos conflictos de fusión.
  • Se deshizo de las feas llamadas al sistema ().
  • Se eliminó $ git update-index --refresh $ del gancho posterior a la compra. Causa algunos problemas con la reversión bajo el git de tortuga y, de todos modos, no parece ser muy importante.
  • Nuestro paquete de Windows se puede descargar en http://ktweb.cz/blog/download/git-mtimestore-1.4.rar

// editar ver github para una versión actualizada

Karel Tucek
fuente
1
Tenga en cuenta que después de un pago, las marcas de tiempo de los archivos actualizados ya no se modifican (Git 2.2.2+, enero de 2015): stackoverflow.com/a/28256177/6309
VonC
3

El siguiente script incorpora las sugerencias -n 1y HEAD, funciona en la mayoría de los entornos que no son de Linux (como Cygwin) y se puede ejecutar en una caja después del hecho:

#!/bin/bash -e

OS=${OS:-`uname`}

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

if [ "$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    
    update_file_timestamp() {
        file_time=`git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1`
        touch -d "$file_time" "$1"
    }    
fi    

OLD_IFS=$IFS
IFS=$'\n'

for file in `git ls-files`
do
    update_file_timestamp "$file"
done

IFS=$OLD_IFS

git update-index --refresh

Suponiendo que nombró el script anterior /path/to/templates/hooks/post-checkouty / o /path/to/templates/hooks/post-update, puede ejecutarlo en un repositorio existente a través de:

git clone git://path/to/repository.git
cd repository
/path/to/templates/hooks/post-checkout
Ross Smith II
fuente
Necesita una última línea más: git update-index --refresh // Las herramientas GUI pueden depender del índice y mostrar el estado "sucio" de todo el archivo después de dicha operación. Es decir, eso sucede en TortoiseGit para Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'El
1
Y gracias por el guión. Desearía que ese script fuera parte del instalador estándar de Git. No es que lo necesite personalmente, pero los miembros del equipo simplemente sienten que la marca de tiempo vuelve a aparecer como un banner rojo de "parada" en la adopción de VCS.
Arioch 'El
3

Esta solución debería ejecutarse con bastante rapidez. Establece tiempos para confirmar tiempos y mtimes para tiempos de autor. No utiliza módulos, por lo que debería ser razonablemente portátil.

#!/usr/bin/perl

# git-utimes: update file times to last commit on them
# Tom Christiansen <[email protected]>

use v5.10;      # for pipe open on a list
use strict;
use warnings;
use constant DEBUG => !!$ENV{DEBUG};

my @gitlog = ( 
    qw[git log --name-only], 
    qq[--format=format:"%s" %ct %at], 
    @ARGV,
);

open(GITLOG, "-|", @gitlog)             || die "$0: Cannot open pipe from `@gitlog`: $!\n";

our $Oops = 0;
our %Seen;
$/ = ""; 

while (<GITLOG>) {
    next if /^"Merge branch/;

    s/^"(.*)" //                        || die;
    my $msg = $1; 

    s/^(\d+) (\d+)\n//gm                || die;
    my @times = ($1, $2);               # last one, others are merges

    for my $file (split /\R/) {         # I'll kill you if you put vertical whitespace in our paths
        next if $Seen{$file}++;             
        next if !-f $file;              # no longer here

        printf "atime=%s mtime=%s %s -- %s\n", 
                (map { scalar localtime $_ } @times), 
                $file, $msg,
                                        if DEBUG;

        unless (utime @times, $file) {
            print STDERR "$0: Couldn't reset utimes on $file: $!\n";
            $Oops++;
        }   
    }   

}
exit $Oops;
tchrist
fuente
2

Aquí hay una versión optimizada de las soluciones de shell anteriores, con correcciones menores:

#!/bin/sh

if [ "$(uname)" = 'Darwin' ] ||
   [ "$(uname)" = 'FreeBSD' ]; then
   gittouch() {
      touch -ch -t "$(date -r "$(git log -1 --format=%ct "$1")" '+%Y%m%d%H%M.%S')" "$1"
   }
else
   gittouch() {
      touch -ch -d "$(git log -1 --format=%ci "$1")" "$1"
   }
fi

git ls-files |
   while IFS= read -r file; do
      gittouch "$file"
   done
vszakats
fuente
1

Aquí hay un método con PHP:

<?php
$r = popen('git ls-files', 'r');
$n_file = 0;

while (true) {
   $s_gets = fgets($r);
   if (feof($r)) {
      break;
   }
   $s_trim = rtrim($s_gets);
   $m_file[$s_trim] = false;
   $n_file++;
}

$r = popen('git log -m -z --name-only --relative --format=%ct .', 'r');

while ($n_file > 0) {
   $s_get = fgets($r);
   $s_trim = rtrim($s_get);
   $a_name = explode("\x0", $s_trim);
   $s_unix = array_pop($a_name);
   foreach ($a_name as $s_name) {
      if (! array_key_exists($s_name, $m_file)) {
         continue;
      }
      if ($m_file[$s_name]) {
         continue;
      }
      touch($s_name, $n_unix);
      $m_file[$s_name] = true;
      $n_file--;
   }
   $n_unix = (int)($s_unix);
}

Es similar a la respuesta aquí:

¿Cuál es el equivalente de use-commit-times para git?

crea una lista de archivos como esa respuesta, pero se basa en en git ls-files lugar de simplemente buscar en el directorio de trabajo. Esto resuelve el problema de la exclusión .gity también resuelve el problema de los archivos sin seguimiento. Además, esa respuesta falla si la última confirmación de un archivo fue una combinación de confirmación, con la que resolví git log -m. Al igual que la otra respuesta, se detendrá una vez que se encuentren todos los archivos, por lo que no tiene que leer todas las confirmaciones. Por ejemplo con:

https://github.com/git/git

a partir de esta publicación, solo tenía que leer 292 confirmaciones. También ignora los archivos antiguos del historial según sea necesario, y no tocará un archivo que ya se haya tocado. Finalmente parece ser un poco más rápido que la otra solución. Resultados con git/gitrepositorio:

PS C:\git> Measure-Command { git-touch.php }
TotalSeconds      : 3.4215134
Steven Penny
fuente
0

Vi algunas solicitudes para una versión de Windows, así que aquí está. Cree los siguientes dos archivos:

C: \ Archivos de programa \ Git \ mingw64 \ share \ git-core \ templates \ hooks \ post-checkout

#!C:/Program\ Files/Git/usr/bin/sh.exe
exec powershell.exe -NoProfile -ExecutionPolicy Bypass -File "./$0.ps1"

C: \ Archivos de programa \ Git \ mingw64 \ share \ git-core \ templates \ hooks \ post-checkout.ps1

[string[]]$changes = &git whatchanged --pretty=%at
$mtime = [DateTime]::Now;
[string]$change = $null;
foreach($change in $changes)
{
    if($change.Length -eq 0) { continue; }
    if($change[0] -eq ":")
    {
        $parts = $change.Split("`t");
        $file = $parts[$parts.Length - 1];
        if([System.IO.File]::Exists($file))
        {
            [System.IO.File]::SetLastWriteTimeUtc($file, $mtime);
        }
    }
    else
    {
        #get timestamp
        $mtime = [DateTimeOffset]::FromUnixTimeSeconds([Int64]::Parse($change)).DateTime;
    }
}

Esto utiliza git whatchanged , por lo que se ejecuta a través de todos los archivos en una sola pasada en lugar de llamar a git para cada archivo.

Cerebro2000
fuente
0

Estoy trabajando en un proyecto en el que guardo un clon de mi repositorio para usarlo con rsyncimplementaciones basadas. Utilizo ramas para apuntar a diferentes entornos y git checkouthace que las modificaciones del archivo cambien.

Habiendo aprendido que git no proporciona una forma de verificar archivos y preservar las marcas de tiempo, encontré el comando git log --format=format:%ai --name-only .en otra pregunta SO: Enumere las últimas fechas de confirmación para una gran cantidad de archivos, rápidamente .

Ahora estoy usando la siguiente secuencia de comandos para touchlos archivos y directorios de mi proyecto para que mi implementación rsyncsea ​​más fácil de diferenciar:

<?php
$lines = explode("\n", shell_exec('git log --format=format:%ai --name-only .'));
$times = array();
$time  = null;
$cwd   = isset($argv[1]) ? $argv[1] : getcwd();
$dirs  = array();

foreach ($lines as $line) {
    if ($line === '') {
        $time = null;
    } else if ($time === null) {
        $time = strtotime($line);
    } else {
        $path = $cwd . DIRECTORY_SEPARATOR . $line;
        if (file_exists($path)) {
            $parent = dirname($path);
            $dirs[$parent] = max(isset($parent) ? $parent : 0, $time);
            touch($path, $time);
        }
    }
}

foreach ($dirs as $dir => $time) {
    touch($dir, $time);
}
Andrew Mackrodt
fuente