tag remove git

remove - git tag commit id



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

Necesito que las marcas de tiempo de los archivos en mi local y en mi servidor estén sincronizados. 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 confirmó.

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

¿Hay alguna manera de hacer esto con git?


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


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 lógica de Linus de que las marcas de tiempo sean dañinas solo porque "confunde a la make " es coja :

  • make clean es suficiente para solucionar cualquier problema.

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

  • Solo hay daño si aplica las marcas de tiempo. No habría daño en almacenarlos en el repositorio. Aplicarlos podría ser una opción simple de " --with-timestamps para git checkout y amigos ( clone , pull etc.), a discreción del usuario .

Tanto Bazar 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 pequeña ganancia (sin tener que volver a compilar todo) que es específica de un subconjunto de proyectos, git como DVCS general se paralizó , se pierde parte de la información de los archivos y, como dijo Linus, es INFEABLE hazlo ahora. Triste .

Dicho esto, ¿puedo ofrecer 2 enfoques?

1 - http://repo.or.cz/w/metastore.git , por David Härdeman. Trata de hacer lo que debería haber hecho un git desde el principio : almacena los metadatos (no solo las marcas de tiempo) en el repositorio al comprometerse (a través del enlace precompromiso), y los vuelve a aplicar al tirar (también a través de los ganchos).

2 - Mi humilde versión de un script que utilicé antes para generar tarballs de lanzamiento. Como se menciona en otras respuestas, el enfoque es un poco diferente : para aplicar para cada archivo, 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 escueta del guión. Para el uso real, recomiendo encarecidamente una de las versiones más robustas 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

El rendimiento es bastante impresionante, incluso para proyectos de monstruo wine , git o incluso 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


Esta solución debería ejecutarse bastante rápido. Establece los tiempos de committer y los tiempos de autor a veces. No utiliza módulos, por lo que debe 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;


Estoy trabajando en un proyecto donde guardo un clon de mi repositorio para utilizarlo con implementaciones basadas en rsync . Yo uso ramas para apuntar a diferentes entornos y el proceso de git checkout hace que cambien las modificaciones del archivo.

Habiendo aprendido que git no proporciona una forma de verificar los archivos y conservar las marcas de tiempo, me encontré con el comando git log --format=format:%ai --name-only . en otra pregunta de SO: enumere las fechas de último compromiso para una gran cantidad de archivos, rápidamente .

Ahora estoy usando la siguiente secuencia de comandos para touch mis archivos y directorios de proyecto para que mi implementación con rsync sea ​​más fácil de modificar:

#!/usr/bin/env php <?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); }


La siguiente secuencia de comandos incorpora las sugerencias -n 1 y HEAD , funciona en la mayoría de los entornos que no son de Linux (como Cygwin) y se puede ejecutar en un proceso de compra 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 haya nombrado la secuencia de comandos /path/to/templates/hooks/post-checkout y / o /path/to/templates/hooks/post-update , puede ejecutarla en un repositorio existente a través de:

git clone git://path/to/repository.git cd repository /path/to/templates/hooks/post-checkout


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

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

Y algunas de las respuestas de Linus no fueron demasiado entusiastas con la idea. Aquí hay una muestra:

Lo siento. Si no ves cómo es INCORRECTO establecer un sello de fecha a algo que hará que un simple "hacer" comprenda incorrectamente tu árbol fuente, no sé qué definición de "incorrecto" estás hablando.
Está incorrecto.
Es estúpido.
Y es totalmente INFELIBLE de implementar.

(Nota: pequeña mejora: después de la finalización de la compra, 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 cuando cambio de sucursales?" ).

La respuesta larga fue:

Creo que está mucho mejor usando solo repositorios múltiples, si esto es algo común.

Jugar con marcas de tiempo no va a funcionar en general. Simplemente te garantizará que "make" se confunde de una manera realmente mala y no recompila lo suficiente en lugar de recompilar demasiado .

Git hace que sea posible hacer tu tarea de "verificar la otra ramificación" muy fácilmente, de muchas maneras diferentes.

Podría crear algún script trivial que haga cualquiera de los siguientes (desde el más trivial hasta el más exótico):

  • solo crea un nuevo repositorio:

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

    y ahí estás. Las viejas marcas de tiempo están bien en su repo antiguo, y puede trabajar (y compilar) en el nuevo, sin afectar al viejo.

    Usa los indicadores "-n -l -s" a "git clone" para básicamente hacer esto instantáneo. Para muchos archivos (por ejemplo, grandes repos como kernel), no va a ser tan rápido como solo cambiar ramas, pero tener una segunda copia del árbol de trabajo puede ser bastante poderosa.

  • haga lo mismo con solo una bola de alquitrán, si quiere

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

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

  • acostúmbrate a " git show ", y solo mira los archivos individuales.
    Esto es realmente realmente útil a veces. Usted acaba de hacer

    git show otherbranch:filename

    en una ventana de xterm, y mira el mismo archivo en tu rama actual en otra ventana. En particular, esto debería ser trivial para hacer con los editores de secuencias de comandos (es decir, GNU emacs), donde debería ser posible tener básicamente un "modo directo" para otras ramas dentro del editor, usando esto. Por lo que sé, el modo emacs git ya ofrece algo como esto (no soy un usuario de emacs)

  • y en el ejemplo extremo de esa cosa de "directorio virtual", al menos había alguien trabajando en un plugin de git para FUSE, es decir, literalmente solo podías tener directorios virtuales que mostraran todas tus sucursales.

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

Linus


Si, sin embargo, desea utilizar las horas de confirmación para las marcas de tiempo al momento de pagar, intente utilizar 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 causará un retraso bastante grande para verificar repositorios grandes (donde grande significa gran cantidad de archivos, no archivos de gran tamaño).


Tomé la respuesta de Giel y, en lugar de utilizar un script de anzuelo post-commit, lo traduje en mi script de implementación personalizado.

Actualización : también eliminé uno | head -n | head -n siguiendo la sugerencia de @eregon, y agregando 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


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 funcione en las instalaciones de Windows en realidad no es una tarea sencilla) y rápido. Se parece a la solución de David Hardeman, que decidí no usar debido a la falta de documentación (del repositorio no pude obtener una idea de lo que hace exactamente su código).

Esta solución almacena mtimes en un archivo .mtimes en el repositorio de git, los actualiza en consecuencia en commits (jsut selectivamente los mtimes de archivos en etapas) y los aplica en el proceso de pago. Funciona incluso con las versiones cygwin / mingw de git (pero puede que necesite copiar algunos archivos de cygwin estándar en la carpeta de git)

La solución consta de 3 archivos:

  1. mtimestore - secuencia de comandos del núcleo que proporciona la opción 3 -a (guardar todo) para la inicialización en repositorio ya existente (funciona con archivos modificados por git)), -s (para guardar cambios por etapas) y -r para restaurarlos. Esto en realidad viene en 2 versiones: bash one (portátil, agradable, fácil de leer / modificar) yc (desordenado pero rápido, porque mingw bash es terriblemente lento, lo que hace imposible usar la solución bash en grandes proyectos).
  2. pre-commit hook
  3. gancho después de la salida

pre cometido:

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

post-checkout

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

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

// edit - versión c ++ actualizada:

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

// edita see github para la versión actualizada