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
paragit 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.
- Funcionalidad principal , con --help, debug messages. Se puede ejecutar en cualquier lugar dentro del árbol de trabajo
- Bestia hecha y derecha , con muchas opciones. Admite cualquier diseño de repositorio.
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:
- 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).
- pre-commit hook
- 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