Qual è l'equivalente di use-commit-time per git?


97

Ho bisogno che i timestamp dei file sul mio locale e sul mio server siano sincronizzati. Ciò si ottiene con Subversion impostando use-commit-times = true nella configurazione in modo che l'ultima modifica di ogni file sia quella in cui è stato eseguito il commit.

Ogni volta che clono il mio repository, voglio che i timestamp dei file riflettano l'ultima volta che sono stati modificati nel repository remoto, non quando ho clonato il repository.

C'è un modo per farlo con git?


Come parte del processo di distribuzione, carico le risorse (immagini, file javascript e file css) su un CDN. Ogni nome di file viene aggiunto con l'ultimo timestamp modificato. È importante che non scadano tutte le mie risorse ogni volta che eseguo la distribuzione. (Un altro effetto collaterale di use-commit-time è che posso eseguire questo processo sul mio locale e sapere che il mio server farà riferimento agli stessi file, ma non è così importante.) Se invece di fare un clone git, ho fatto un git fetch seguito da git reset --hard dal mio repository remoto, che funzionerebbe per un singolo server, ma non per più server poiché i timestamp su ciascuno sarebbero diff.
Ben W

@BenW: git annexpotrebbe essere utile per tenere traccia delle immagini
jfs

Puoi controllare cosa è cambiato controllando gli ID. Stai cercando di fare in modo che i timestamp del filesystem abbiano la stessa cosa dei timestamp vcs. Non significano la stessa cosa.
jthill

Risposte:


25

Non sono sicuro che sarebbe appropriato per un DVCS (come in VCS "distribuito")

L'enorme discussione era già avvenuta nel 2007 (vedi questo thread)

E alcune delle risposte di Linus non erano molto entusiaste dell'idea. Ecco un esempio:

Mi dispiace. Se non vedi come sia SBAGLIATO reimpostare un datestamp su qualcosa che renderà un semplice "make" compilare in modo errato il tuo albero dei sorgenti, non so di quale definizione di "sbagliato" stai parlando.
È sbagliato.
È STUPIDO.
Ed è totalmente INFEASIBILE da implementare.


(Nota: piccolo miglioramento: dopo un checkout, i timestamp dei file aggiornati non vengono più modificati (Git 2.2.2+, gennaio 2015): "git checkout - come posso mantenere i timestamp quando si cambia ramo?" .)


La risposta lunga è stata:

Penso che tu stia molto meglio usando invece più repository, se questo è qualcosa di comune.

Fare confusione con i timestamp non funzionerà in generale. Ti garantirà solo che "make" viene confuso in un modo davvero pessimo, e non si ricompila abbastanza invece di ricompilare troppo .

Git rende possibile fare la tua cosa "controlla l'altro ramo" molto facilmente, in molti modi diversi.

Potresti creare uno script banale che esegue una delle seguenti operazioni (che vanno dal banale al più esotico):

  • basta creare un nuovo repository:
    git clone old new
    cd new
    git checkout origin/<branch>

e ci sei. I vecchi timestamp vanno bene nel tuo vecchio repository e puoi lavorare (e compilare) in quello nuovo, senza influenzare affatto quello vecchio.

Usa i flag "-n -l -s" su "git clone" per renderlo praticamente istantaneo. Per molti file (es. Grandi repository come il kernel), non sarà veloce come cambiare ramo, ma avere una seconda copia dell'albero funzionante può essere abbastanza potente.

  • fai la stessa cosa con solo una palla di catrame, se vuoi
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

che è davvero abbastanza veloce, se vuoi solo un'istantanea.

  • abituati a " git show" e guarda i singoli file.
    Questo è effettivamente molto utile a volte. Devi solo farlo
    git show otherbranch:filename

in una finestra xterm e guarda lo stesso file nel tuo ramo corrente in un'altra finestra. In particolare, questo dovrebbe essere banale a che fare con gli editor scriptable (cioè GNU emacs), dove dovrebbe essere possibile fondamentalmente avere un'intera "modalità dired" per altri rami all'interno dell'editor, usando questo. Per quanto ne so, la modalità git di emacs offre già qualcosa di simile (non sono un utente di emacs)

  • e nell'esempio estremo di quella cosa "directory virtuale", c'era almeno qualcuno che lavorava su un plugin git per FUSE, cioè potresti letteralmente avere solo directory virtuali che mostrano tutti i tuoi rami.

e sono sicuro che una qualsiasi delle precedenti sia alternative migliori rispetto ai giochi con timestamp dei file.

Linus


5
Concordato. Non dovresti confondere un DVCS con un sistema di distribuzione. gitè un DVCS, per manipolare il codice sorgente che verrà integrato nel prodotto finale. Se vuoi un sistema di distribuzione, sai dove trovarlo rsync.
Randal Schwartz

14
Hm, dovrò fidarmi della sua tesi secondo cui non è fattibile. Che sia sbagliato o stupido, però, è un'altra questione. Eseguo la versione dei miei file utilizzando un timestamp e li carico su un CDN, motivo per cui è importante che i timestamp riflettano quando il file è stato effettivamente modificato, non quando è stato estratto l'ultima volta dal repository.
Ben W

3
@ Ben W: la "risposta di Linus" non è qui per dire che è sbagliata nella tua situazione particolare. È lì solo per ricordare che un DVCS non è adatto per quel tipo di funzionalità (conservazione del timestamp).
VonC

15
@VonC: Dal momento che altri DVCS moderni come Bazaar e Mercurial gestiscono perfettamente i timestamp, preferirei dire che " git non è adatto per quel tipo di funzionalità". Se "un" DVCS dovesse avere quella caratteristica è discutibile (e credo fortemente che lo abbiano).
MestreLion

10
Questa non è una risposta alla domanda, ma una discussione filosofica sui meriti di farlo in un sistema di controllo della versione. Se alla persona fosse piaciuto, avrebbe chiesto: "Qual è il motivo per cui git non usa il tempo di commit per il tempo di modifica dei file?"
thomasfuchs

85

Se, tuttavia, vuoi davvero usare i tempi di commit per i timestamp durante il check-out, prova a utilizzare questo script e posizionalo (come eseguibile) nel file $ 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

Si noti tuttavia che questo script causerà un ritardo piuttosto elevato per l'estrazione di archivi di grandi dimensioni (dove grande significa grande quantità di file, non grandi dimensioni di file).


55
+1 per una risposta effettiva, invece di dire semplicemente "Non farlo"
DanC

4
| head -n 1dovrebbe essere evitato in quanto genera un nuovo processo, -n 1poiché git rev-liste git logpuò essere utilizzato invece.
eregon

3
È meglio NON leggere righe con `...`e for; vedi Perché non leggi righe con "per" . Andrei per git ls-files -ze while IFS= read -r -d ''.
musiphil

2
È possibile una versione per Windows?
Ehryk

2
invece di ciò git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1che puoi fare git show --pretty=format:%ai -s "$(get_file_rev "$1")", causa la generazione di molti meno dati dal showcomando e dovrebbe ridurre il sovraccarico.
Scott Chamberlain

80

AGGIORNAMENTO : La mia soluzione è ora impacchettata in Debian / Ubuntu / Mint, Fedora, Gentoo e possibilmente altre distribuzioni:

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

IMHO, non memorizzare i timestamp (e altri metadati come autorizzazioni e proprietà) è una grande limitazione di git.

La logica di Linus secondo cui i timestamp sono dannosi solo perché "confonde make" è debole :

  • make clean è sufficiente per risolvere eventuali problemi.

  • Si applica solo ai progetti che utilizzano make, principalmente C / C ++. È completamente discutibile per script come Python, Perl o documentazione in generale.

  • C'è solo un danno se applichi i timestamp. Non ci sarebbe alcun danno a conservarli in repo. Applicarli potrebbe essere --with-timestampsun'opzione semplice per git checkoute amici ( clone, pullecc.), In discrezione dell'utente .

Sia Bazaar che Mercurial memorizzano i metadati. Gli utenti possono applicarli o meno durante il check-out. Ma in git, poiché i timestamp originali non sono nemmeno disponibili nel repo, non esiste tale opzione.

Quindi, per un guadagno molto piccolo (non dover ricompilare tutto) che è specifico per un sottoinsieme di progetti, gitpoiché un DVCS generale è stato paralizzato , alcune informazioni sui file vengono perse e, come ha detto Linus, è INFEASIBILE da fare ora.Triste .

Detto questo, posso offrire 2 approcci?

1 - http://repo.or.cz/w/metastore.git , di David Härdeman. Cerca di fare ciò che git avrebbe dovuto fare in primo luogo : memorizza i metadati (non solo i timestamp) nel repository durante il commit (tramite hook pre-commit) e li riapplica durante il pull (anche tramite hook).

2 - La mia modesta versione di uno script che ho usato prima per generare tarball di rilascio. Come accennato in altre risposte, l'approccio è leggermente diverso : applicare per ogni file il timestamp del commit più recente in cui il file è stato modificato.

  • git-restore-mtime , con molte opzioni, supporta qualsiasi layout di repository e funziona su Python 3.

Di seguito è riportata una versione davvero essenziale dello script, come prova di concetto, su Python 2.7. Per l'utilizzo effettivo, consiglio vivamente la versione completa sopra:

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

Le prestazioni sono piuttosto impressionanti, anche per i progetti mostruosi wine, gito anche per il kernel 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

2
Ma git lo fa timestamp negozio, ecc Semplicemente non imposta il timestamp per impostazione predefinita. Basta guardare l'output digit ls-files --debug
Ross Smith II

9
@RossSmithII: git ls-filesopera sulla directory e sull'indice di lavoro, quindi non significa che memorizzi effettivamente tali informazioni nel repository. Se memorizzasse, recuperare (e applicare) mtime sarebbe banale.
MestreLion

13
"La logica di Linus secondo cui i timestamp sono dannosi solo perché" confonde make "è zoppo" - concordato al 100%, un DCVS non dovrebbe conoscere o preoccuparsi del codice che contiene! Ancora una volta, questo mostra le insidie ​​del tentativo di riutilizzare strumenti scritti per casi d'uso specifici in casi d'uso generali. Mercurial è e sarà sempre una scelta superiore perché è stata progettata, non evoluta.
Ian Kemp

6
@davec Sei il benvenuto, contento che sia stato utile. La versione completa su github.com/MestreLion/git-tools gestisce già Windows, Python 3, nomi di percorso non ASCII, ecc. Lo script sopra è solo una prova di funzionamento, evitatelo per l'uso in produzione.
MestreLion

3
I tuoi argomenti sono validi. Spero che qualcuno con una certa influenza faccia una richiesta di miglioramento a git per avere l'opzione suggerita --with-timestamps.
weberjn

12

Ho preso la risposta di Giel e invece di usare uno script hook post-commit, l'ho lavorato nel mio script di distribuzione personalizzato.

Aggiornamento : ne ho anche rimosso uno | head -nseguendo il suggerimento di @ eregon e aggiunto il supporto per i file con spazi al loro interno:

# 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

Grazie Daniel, è utile sapere
Alex Dean

il comando --abbrev-commitè superfluo a git showcausa dell'utilizzo --pretty=format:%ai(l'hash del commit non fa parte dell'output) e | head -n 1potrebbe essere sostituito con l'uso del -sflag sugit show
Elan Ruusamäe

1
@ DanielS.Sterling: %aiè la data dell'autore, formato simile a ISO 8601 , per l' uso rigoroso di iso8601 %aI: git-scm.com/docs/git-show
Elan Ruusamäe

4

siamo stati costretti a inventare un'altra soluzione, perché avevamo bisogno di tempi di modifica specifici e non di tempi di commit, e la soluzione doveva anche essere portabile (cioè far funzionare Python nelle installazioni git di Windows non è un compito semplice) e veloce. Assomiglia alla soluzione di David Hardeman, che ho deciso di non utilizzare per mancanza di documentazione (dal repository non sono riuscito a farmi un'idea di cosa fa esattamente il suo codice).

Questa soluzione memorizza mtimes in un file .mtimes nel repository git, li aggiorna di conseguenza sui commit (jsut selettivamente i mtimes dei file staged) e li applica al checkout. Funziona anche con le versioni cygwin / mingw di git (ma potrebbe essere necessario copiare alcuni file dallo standard cygwin nella cartella di git)

La soluzione è composta da 3 file:

  1. mtimestore - script principale che fornisce 3 opzioni -a (salva tutto - per l'inizializzazione in un repository già esistente (funziona con file git-versed)), -s (per salvare le modifiche staged) e -r per ripristinarle. Questo in realtà è disponibile in 2 versioni: una bash (portatile, piacevole, facile da leggere / modificare) e la versione c (disordinata ma veloce, perché mingw bash è orribilmente lento, il che rende impossibile usare la soluzione bash su grandi progetti).
  2. hook pre-commit
  3. gancio post-checkout

pre-commit:

#!/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;
}
  • si noti che gli hook possono essere inseriti nella directory-template per automatizzare il loro posizionamento

maggiori informazioni possono essere trovate qui https://github.com/kareltucek/git-mtime-extension alcune informazioni obsolete sono a http://www.ktweb.cz/blog/index.php?page=page&id=116

// modifica - versione c ++ aggiornata:

  • Ora la versione c ++ mantiene l'ordine alfabetico -> meno conflitti di unione.
  • Eliminate le brutte chiamate system ().
  • Eliminato $ git update-index --refresh $ dall'hook post-checkout. Causa alcuni problemi con il ripristino sotto tortoise git, e comunque non sembra essere molto importante.
  • Il nostro pacchetto Windows può essere scaricato da http://ktweb.cz/blog/download/git-mtimestore-1.4.rar

// modifica vedi github per la versione aggiornata


1
Nota che, dopo un checkout, timestamp dei file up-to-date non sono più modificati (Git 2.2.2+, gennaio 2015): stackoverflow.com/a/28256177/6309
VonC

3

Il seguente script incorpora i suggerimenti -n 1e HEAD, funziona nella maggior parte degli ambienti non Linux (come Cygwin) e può essere eseguito in un checkout dopo il fatto:

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

Supponendo che tu abbia nominato lo script sopra /path/to/templates/hooks/post-checkoute / o /path/to/templates/hooks/post-update, puoi eseguirlo su un repository esistente tramite:

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

Ha bisogno di un'ultima riga: git update-index --refresh // Gli strumenti della GUI potrebbero fare affidamento su index e mostrare lo stato "sporco" a tutto il file dopo tale operazione. Vale a dire che succede in TortoiseGit per Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The

1
E grazie per il copione. Vorrei che tale script facesse parte del programma di installazione standard di Git. Non che ne abbia bisogno personalmente, ma i membri del team ritengono che il timestamp si ripeta come un banner rosso "stop" nell'adozione di VCS.
Arioch "Il

3

Questa soluzione dovrebbe essere eseguita abbastanza rapidamente. Imposta gli orari sui tempi dei committer e i tempi sui tempi degli autori. Non utilizza moduli, quindi dovrebbe essere ragionevolmente portatile.

#!/usr/bin/perl

# git-utimes: update file times to last commit on them
# Tom Christiansen <tchrist@perl.com>

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;

2

Ecco una versione ottimizzata delle soluzioni di shell sopra, con correzioni minori:

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

1

Ecco un metodo 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);
}

È simile alla risposta qui:

Qual è l'equivalente di use-commit-time per git?

crea un elenco di file come quella risposta, ma si basa git ls-files invece di limitarsi a guardare nella directory di lavoro. Questo risolve il problema dell'esclusione .gite risolve anche il problema dei file non tracciati. Inoltre, quella risposta fallisce se l'ultimo commit di un file era un merge commit, che ho risolto con git log -m. Come l'altra risposta, si fermerà una volta trovati tutti i file, quindi non sarà necessario leggere tutti i commit. Ad esempio con:

https://github.com/git/git

a partire da questo post doveva leggere solo 292 commit. Inoltre, ignora i vecchi file dalla cronologia secondo necessità e non toccherà un file che è già stato toccato. Infine sembra essere un po 'più veloce dell'altra soluzione. Risultati con git/gitrepo:

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

0

Ho visto alcune richieste per una versione di Windows, quindi eccola qui. Crea i seguenti due file:

C: \ Programmi \ 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: \ Programmi \ 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;
    }
}

Questo utilizza git whatchanged , quindi esegue tutti i file in un unico passaggio invece di chiamare git per ogni file.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.