Was entspricht den Use-Commit-Zeiten für Git?

97

Ich benötige die Zeitstempel der Dateien auf meinem lokalen Server und auf meinem Server, um synchron zu sein. Dies wird mit Subversion erreicht, indem use-commit-times = true in der Konfiguration festgelegt wird, sodass die letzte Änderung jeder Datei zum Zeitpunkt des Festschreibens erfolgt.

Jedes Mal, wenn ich mein Repository klone, möchte ich, dass die Zeitstempel der Dateien widerspiegeln, wann sie zuletzt im Remote-Repository geändert wurden, und nicht, wann ich das Repo geklont habe.

Gibt es eine Möglichkeit, dies mit git zu tun?

Ben W.
quelle
Im Rahmen meines Bereitstellungsprozesses lade ich Assets (Bilder, Javascript-Dateien und CSS-Dateien) auf ein CDN hoch. Jeder Dateiname wird mit dem zuletzt geänderten Zeitstempel versehen. Es ist wichtig, dass ich nicht bei jeder Bereitstellung alle meine Assets ablaufe. (Ein weiterer Nebeneffekt von Use-Commit-Zeiten ist, dass ich diesen Prozess auf meinem lokalen Server ausführen kann und weiß, dass mein Server auf dieselben Dateien verweist, aber das ist nicht so wichtig.) Wenn ich anstelle eines Git-Klons einen git fetch gefolgt von einem git reset --hard von meinem Remote-Repo, das für einen einzelnen Server funktionieren würde, aber nicht für mehrere Server, da die Zeitstempel auf jedem Server unterschiedlich wären.
Ben W
@ BenW: git annexkönnte nützlich sein, um Bilder zu verfolgen
jfs
Sie können überprüfen, was geändert wurde, indem Sie die IDs überprüfen. Sie versuchen, Zeitstempel für das Dateisystem so zu gestalten, dass sie dasselbe bedeuten wie Zeitstempel für VCS. Sie bedeuten nicht dasselbe.
Bis zum

Antworten:

25

Ich bin nicht sicher, ob dies für ein DVCS geeignet wäre (wie in "Distributed" VCS).

Die große Diskussion hatte bereits 2007 stattgefunden (siehe diesen Thread)

Und einige von Linus 'Antworten waren nicht besonders begeistert von der Idee. Hier ist ein Beispiel:

Es tut mir Leid. Wenn Sie nicht sehen, wie FALSCH es ist, einen Datenstempel auf etwas zurückzusetzen, das ein einfaches "make" ergibt. miscompile Ihren Quellbaum, weiß ich nicht , was defintiion von „falsch“ über Sie sprechen.
Es ist falsch.
Es ist dumm.
Und es ist völlig undurchdringlich zu implementieren.


(Hinweis: kleine Verbesserung: Nach dem Auschecken werden die Zeitstempel aktueller Dateien nicht mehr geändert (Git 2.2.2+, Januar 2015): "git checkout - wie kann ich Zeitstempel beim Wechseln von Zweigen beibehalten?" .)


Die lange Antwort war:

Ich denke, Sie sind viel besser dran, wenn Sie stattdessen mehrere Repositorys verwenden, wenn dies häufig vorkommt.

Das Spielen mit Zeitstempeln wird im Allgemeinen nicht funktionieren. Es wird Ihnen nur garantieren, dass "make" auf eine wirklich schlechte Weise verwirrt wird und nicht genug neu kompiliert , anstatt neu zu kompilieren zu viel zu .

Git macht es sehr einfach, auf viele verschiedene Arten "den anderen Zweig auszuchecken".

Sie können ein triviales Skript erstellen, das eine der folgenden Aktionen ausführt (von trivial bis exotisch):

  • Erstelle einfach ein neues Repo:
    git clone old new
    cd new
    git checkout origin/<branch>

und da bist du. Die alten Zeitstempel sind in Ihrem alten Repo in Ordnung, und Sie können im neuen Repo arbeiten (und kompilieren), ohne den alten überhaupt zu beeinflussen.

Verwenden Sie die Flags "-n -l -s", um "git clone" zu erstellen, um dies im Grunde genommen sofort zu machen. Für viele Dateien (z. B. große Repos wie den Kernel) ist es nicht so schnell wie nur das Wechseln von Zweigen, aber eine zweite Kopie des Arbeitsbaums kann sehr leistungsfähig sein.

  • Machen Sie dasselbe mit nur einem Teerball, wenn Sie möchten
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

Das ist wirklich ziemlich schnell, wenn Sie nur einen Schnappschuss wollen.

  • gewöhne dich an " git show" und schaue dir nur einzelne Dateien an.
    Dies ist manchmal wirklich sehr nützlich. Du tust es einfach
    git show otherbranch:filename

in einem xterm-Fenster und sehen Sie sich dieselbe Datei in Ihrem aktuellen Zweig in einem anderen Fenster an. Dies sollte insbesondere bei skriptfähigen Editoren (dh GNU-Emacs) trivial sein, bei denen es grundsätzlich möglich sein sollte, einen vollständigen "Dired-Modus" für andere Zweige innerhalb des Editors zu verwenden. Soweit ich weiß, bietet der Emacs-Git-Modus bereits so etwas (ich bin kein Emacs-Benutzer).

  • und im extremen Beispiel dieses "virtuellen Verzeichnisses" arbeitete zumindest jemand an einem Git-Plugin für FUSE, dh Sie könnten buchstäblich nur virtuelle Verzeichnisse haben, in denen alle Ihre Zweige angezeigt werden.

und ich bin sicher, dass eine der oben genannten Alternativen besser ist als das Spielen von Spielen mit Dateizeitstempeln.

Linus

VonC
quelle
5
Einverstanden. Sie sollten ein DVCS nicht mit einem Verteilungssystem verwechseln. gitist ein DVCS zum Bearbeiten von Quellcode, der in Ihr Endprodukt integriert wird. Wenn Sie ein Vertriebssystem wünschen, wissen Sie, wo Sie es finden können rsync.
Randal Schwartz
14
Hm, ich muss seinem Argument vertrauen, dass es nicht machbar ist. Ob es falsch oder dumm ist, ist eine andere Sache. Ich versioniere meine Dateien mit einem Zeitstempel und lade sie auf ein CDN hoch. Deshalb ist es wichtig, dass die Zeitstempel angeben, wann die Datei tatsächlich geändert wurde, und nicht, wann sie zuletzt aus dem Repo abgerufen wurde.
Ben W
3
@ Ben W: Die "Antwort von Linus" ist nicht hier, um zu sagen, dass sie in Ihrer speziellen Situation falsch ist . Es dient nur zur Erinnerung daran, dass ein DVCS für diese Art von Funktion (Zeitstempelerhaltung) nicht gut geeignet ist.
VonC
15
@VonC: Da andere moderne DVCS wie Bazaar und Mercurial Zeitstempel problemlos verarbeiten, würde ich eher sagen, dass " Git für diese Art von Funktion nicht gut geeignet ist". Ob "ein" DVCS diese Funktion haben sollte , ist umstritten (und ich bin der festen Überzeugung , dass dies der Fall ist).
MestreLion
10
Dies ist keine Antwort auf die Frage, sondern eine philosophische Diskussion über die Vorzüge eines Versionskontrollsystems. Wenn der Person das gefallen hätte, hätte sie gefragt: "Was ist der Grund, warum Git die Festschreibungszeit nicht für die geänderte Zeit von Dateien verwendet?"
Thomasfuchs
85

Wenn Sie jedoch beim Auschecken wirklich Festschreibungszeiten für Zeitstempel verwenden möchten, verwenden Sie dieses Skript und platzieren Sie es (als ausführbare Datei) in der Datei $ 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

Beachten Sie jedoch, dass dieses Skript eine große Verzögerung beim Auschecken großer Repositorys verursacht (wobei groß eine große Anzahl von Dateien bedeutet, keine große Dateigröße).

Giel
quelle
55
+1 für eine tatsächliche Antwort, anstatt nur "Tu das nicht" zu
sagen
4
| head -n 1sollte vermieden werden, da es einen neuen Prozess erzeugt, -n 1für git rev-listund git logkann stattdessen verwendet werden.
Oregon
3
Es ist besser, KEINE Zeilen mit `...`und zu lesen for. Siehe Warum Sie keine Zeilen mit "für" lesen . Ich würde für git ls-files -zund gehen while IFS= read -r -d ''.
Musiphil
2
Ist eine Windows-Version möglich?
Ehryk
2
Anstatt dass git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1Sie dies tun können git show --pretty=format:%ai -s "$(get_file_rev "$1")", werden vom showBefehl viel weniger Daten generiert, und der Overhead sollte reduziert werden.
Scott Chamberlain
79

UPDATE : Meine Lösung ist jetzt in Debian / Ubuntu / Mint, Fedora, Gentoo und möglicherweise anderen Distributionen verpackt:

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 ist das Nicht-Speichern von Zeitstempeln (und anderen Metadaten wie Berechtigungen und Besitz) eine große Einschränkung von git.

Linus 'Begründung, Zeitstempel seien schädlich, nur weil sie "verwirren make", ist lahm :

  • make clean reicht aus, um Probleme zu beheben.

  • Gilt nur für Projekte, die makehauptsächlich C / C ++ verwenden. Es ist für Skripte wie Python, Perl oder Dokumentation im Allgemeinen völlig umstritten.

  • Es schadet nur, wenn Sie die Zeitstempel anwenden . Es würde nicht schaden, sie im Repo zu lagern . Anwenden von ihnen könnte eine einfache seine --with-timestampsOption für git checkoutund Freunde ( clone, pullusw.), auf den Benutzer setzen zu müssen .

Sowohl Bazaar als auch Mercurial speichern Metadaten. Benutzer können sie beim Auschecken anwenden oder nicht. Aber in Git gibt es keine solche Option , da die ursprünglichen Zeitstempel nicht einmal im Repo verfügbar sind .

Für einen sehr kleinen Gewinn (der nicht alles neu kompilieren muss), der für eine Teilmenge von Projekten spezifisch ist , gehen einige Informationen über Dateien verloren , gitda ein allgemeines DVCS verkrüppelt wurde , und wie Linus sagte, ist es UNMÖGLICH, dies zu tun es jetzt. Traurig .

Darf ich zwei Ansätze anbieten?

1 - http://repo.or.cz/w/metastore.git , von David Härdeman. Versucht, das zu tun, was git eigentlich hätte getan werden sollen : Speichert Metadaten (nicht nur Zeitstempel) beim Festschreiben im Repo (über den Pre-Commit-Hook) und wendet sie beim Ziehen erneut an (auch über Hooks).

2 - Meine bescheidene Version eines Skripts, das ich zuvor zum Generieren von Release-Tarballs verwendet habe. Wie in anderen Antworten erwähnt, ist der Ansatz etwas anders : Für jede Datei wird der Zeitstempel des letzten Commits angewendet , bei dem die Datei geändert wurde.

  • git-restore-mtime mit vielen Optionen unterstützt jedes Repository-Layout und läuft unter Python 3.

Im Folgenden finden Sie eine wirklich einfache Version des Skripts als Proof-of-Concept für Python 2.7. Für den tatsächlichen Gebrauch empfehle ich dringend die obige Vollversion:

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

Die Leistung ist ziemlich beeindruckend, auch für Monster - Projekte wine, gitoder sogar den Linux - Kernel:

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
quelle
2
Aber git tut Speicher Zeitstempel, etc. Es ist gesetzt einfach nicht die Zeitstempel standardmäßig. Schauen Sie sich die Ausgabe vongit ls-files --debug
Ross Smith II
9
@RossSmithII: git ls-filesarbeitet auf Arbeitsverzeichnis und Index, so dass es nicht tatsächlich bedeuten speichert , dass Informationen über den Repo. Wenn es gespeichert würde, wäre das Abrufen (und Anwenden) von mtime trivial.
MestreLion
13
"Linus 'Begründung, Zeitstempel seien schädlich, nur weil sie make verwirren, ist lahm" - zu 100% vereinbart, sollte ein DCVS den darin enthaltenen Code nicht kennen oder sich nicht darum kümmern! Dies zeigt erneut die Fallstricke beim Versuch, für bestimmte Anwendungsfälle geschriebene Tools in allgemeine Anwendungsfälle umzuwandeln. Mercurial ist und bleibt eine überlegene Wahl, da es entworfen und nicht weiterentwickelt wurde.
Ian Kemp
6
@ Davec Gern geschehen, froh, dass es nützlich war. Die Vollversion unter github.com/MestreLion/git-tools verarbeitet bereits Windows, Python 3, Nicht-ASCII-Pfadnamen usw. Das obige Skript ist nur ein funktionierender Proof of Concept. Vermeiden Sie es für die Verwendung in der Produktion.
MestreLion
3
Ihre Argumente sind gültig. Ich würde hoffen, dass jemand mit einer gewissen Schlagkraft eine Verbesserungsanfrage für git stellt, um Ihre vorgeschlagene Option mit Zeitstempeln zu erhalten.
Weberjn
12

Ich nahm Giels Antwort und arbeitete sie anstelle eines Hook-Skripts nach dem Festschreiben in mein benutzerdefiniertes Bereitstellungsskript ein.

Update : Ich habe auch einen | head -nfolgenden Vorschlag von @ eregon entfernt und Unterstützung für Dateien mit Leerzeichen hinzugefügt:

# 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
quelle
Danke Daniel, das ist hilfreich zu wissen
Alex Dean
Der Befehl --abbrev-commitist überflüssig, git showda --pretty=format:%aier verwendet wird (Commit-Hash ist nicht Teil der Ausgabe) und | head -n 1könnte durch die Verwendung von -sFlag to ersetzt werdengit show
Elan Ruusamäe
1
@ DanielS.Sterling: %aiist Autorendatum, ISO 8601- ähnliches Format, für strikte Verwendung von iso8601%aI : git-scm.com/docs/git-show
Elan Ruusamäe
4

Wir waren gezwungen, eine weitere Lösung zu erfinden, da wir spezielle Änderungszeiten und keine Festschreibungszeiten benötigten und die Lösung auch portabel sein musste (dh Python in Windows Git-Installationen zum Laufen zu bringen, ist wirklich keine einfache Aufgabe) und schnell. Es ähnelt der Lösung von David Hardeman, die ich aufgrund fehlender Dokumentation nicht verwendet habe (aus dem Repository konnte ich keine Vorstellung davon bekommen, was genau sein Code tut).

Diese Lösung speichert mtimes in einer Datei .mtimes im Git-Repository, aktualisiert sie bei Commits entsprechend (jsut selektiv die mtimes von bereitgestellten Dateien) und wendet sie beim Auschecken an. Es funktioniert sogar mit Cygwin / Mingw-Versionen von Git (möglicherweise müssen Sie jedoch einige Dateien aus Standard-Cygwin in den Ordner von Git kopieren).

Die Lösung besteht aus 3 Dateien:

  1. mtimestore - Kernskript mit 3 Optionen: -a (alle speichern - für die Initialisierung in bereits vorhandenen Repos (funktioniert mit git-versierten Dateien)), -s (zum Speichern von bereitgestellten Änderungen) und -r, um sie wiederherzustellen. Dies gibt es tatsächlich in zwei Versionen - einer Bash-Version (portabel, nett, einfach zu lesen / ändern) und einer C-Version (chaotisch, aber schnell, da Mingw-Bash schrecklich langsam ist, was es unmöglich macht, die Bash-Lösung für große Projekte zu verwenden).
  2. Pre-Commit-Hook
  3. Haken nach dem Auschecken

Pre-Commit:

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

nach dem Auschecken

#!/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;
}
  • Beachten Sie, dass Hooks im Vorlagenverzeichnis platziert werden können, um ihre Platzierung zu automatisieren

Weitere Informationen finden Sie hier https://github.com/kareltucek/git-mtime-extension Einige veraltete Informationen finden Sie unter http://www.ktweb.cz/blog/index.php?page=page&id=116

// edit - c ++ Version aktualisiert:

  • Jetzt behält die c ++ - Version die alphabetische Reihenfolge bei -> weniger Zusammenführungskonflikte.
  • Ich habe die hässlichen system () -Aufrufe losgeworden.
  • $ Git-Update-Index --refresh $ aus dem Post-Checkout-Hook gelöscht. Verursacht einige Probleme mit dem Zurücksetzen unter Schildkrötengit und scheint sowieso nicht sehr wichtig zu sein.
  • Unser Windows-Paket kann unter http://ktweb.cz/blog/download/git-mtimestore-1.4.rar heruntergeladen werden

// Bearbeiten siehe Github für aktuelle Version

Karel Tucek
quelle
1
Beachten Sie, dass nach dem Auschecken die Zeitstempel der aktuellen Dateien nicht mehr geändert werden (Git 2.2.2+, Januar 2015): stackoverflow.com/a/28256177/6309
VonC
3

Das folgende Skript enthält die -n 1und HEADVorschläge, funktioniert in den meisten Nicht-Linux-Umgebungen (wie Cygwin) und kann nachträglich an einer Kasse ausgeführt werden:

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

Angenommen, Sie haben das obige Skript benannt /path/to/templates/hooks/post-checkoutund / oder /path/to/templates/hooks/post-updatekönnen Sie es in einem vorhandenen Repository ausführen über:

git clone git://path/to/repository.git
cd repository
/path/to/templates/hooks/post-checkout
Ross Smith II
quelle
Es braucht noch eine letzte Zeile: git update-index --refresh // GUI-Tools sind möglicherweise auf den Index angewiesen und zeigen nach einem solchen Vorgang der gesamten Datei den Status "Dirty" an. Das passiert nämlich in TortoiseGit für Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The
1
Und danke für das Drehbuch. Ich wünschte, ein solches Skript wäre Teil des Git-Standardinstallationsprogramms. Nicht, dass ich es persönlich brauche, aber die Teammitglieder fühlen sich beim erneuten Stempeln des Zeitstempels als rotes "Stopp" -Banner bei der VCS-Einführung.
Arioch 'Der
3

Diese Lösung sollte ziemlich schnell laufen. Es werden die Zeiten für die Committer-Zeiten und die Zeiten für die Autorenzeiten festgelegt. Es verwendet keine Module und sollte daher einigermaßen portabel sein.

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

Hier ist eine optimierte Version der oben genannten Shell-Lösungen mit geringfügigen Korrekturen:

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

Hier ist eine Methode mit 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 ist ähnlich der Antwort hier:

Was entspricht den Use-Commit-Zeiten für Git?

Es erstellt eine Dateiliste wie diese Antwort, aber es erstellt aus, git ls-files anstatt nur im Arbeitsverzeichnis zu suchen. Dies löst das Problem des Ausschlusses .gitund das Problem der nicht verfolgten Dateien. Diese Antwort schlägt auch fehl, wenn das letzte Festschreiben einer Datei ein Zusammenführungs-Festschreiben war, mit dem ich es gelöst habegit log -m . Wie die andere Antwort wird sie angehalten, sobald alle Dateien gefunden wurden, sodass nicht alle Commits gelesen werden müssen. Zum Beispiel mit:

https://github.com/git/git

Zum Zeitpunkt dieser Veröffentlichung mussten nur 292 Commits gelesen werden. Außerdem werden alte Dateien aus dem Verlauf nach Bedarf ignoriert und eine bereits berührte Datei wird nicht berührt. Schließlich scheint es etwas schneller zu sein als die andere Lösung. Ergebnisse mit git/gitRepo:

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

Ich habe einige Anfragen für eine Windows-Version gesehen, also hier ist es. Erstellen Sie die folgenden zwei Dateien:

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

Hierbei wird git whatchanged verwendet , sodass alle Dateien in einem Durchgang durchlaufen werden , anstatt git für jede Datei aufzurufen.

Brain2000
quelle
0

Ich arbeite an einem Projekt, in dem ein Klon meines Repositorys für die Verwendung mit rsyncbasierten Bereitstellungen aufbewahrt wird. Ich verwende Zweige, um auf verschiedene Umgebungen abzuzielen, und git checkoutbewirkt, dass sich die Dateimodifikationen ändern.

Nachdem ich erfahren hatte, dass git keine Möglichkeit bietet, Dateien auszuchecken und Zeitstempel beizubehalten, stieß ich git log --format=format:%ai --name-only .in einer anderen SO-Frage auf den Befehl : Listet die letzten Festschreibungsdaten für eine große Anzahl von Dateien schnell auf .

Ich verwende jetzt das folgende Skript für touchmeine Projektdateien und -verzeichnisse, damit meine Bereitstellung mit rsynceinfacher zu unterscheiden ist:

<?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
quelle