Auschecken alter Dateien MIT originalen Zeitstempeln zum Erstellen / Ändern

Antworten:

45

Ich glaube, dass die einzigen in der Git-Datenbank aufgezeichneten Zeitstempel der Autor sind und Zeitstempel festschreiben. Ich sehe keine Option für Git, um den Zeitstempel der Datei so zu ändern, dass er mit dem letzten Commit übereinstimmt, und es ist sinnvoll, dass dies nicht das Standardverhalten ist (da Makefiles sonst nicht richtig funktionieren würden).

Sie können ein Skript schreiben, um das Änderungsdatum Ihrer Dateien auf den Zeitpunkt des letzten Commits festzulegen. Es könnte ungefähr so ​​aussehen:

IFS="
"
for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso -- "$FILE")
    TIME=$(date -j -f '%Y-%m-%d %H:%M:%S %z' "$TIME" +%Y%m%d%H%M.%S)
    touch -m -t "$TIME" "$FILE"
done
Dietrich Epp
quelle
10
Es gibt mehrere Probleme mit diesem Snippet: 1 - Es schlägt fehl, wenn in Dateinamen Leerzeichen vorhanden sind. 2 - Kann bei Projekten mit mehr als einigen tausend Dateien fehlschlagen. 3 - Leistung ist absolut miserabel jedes mittelgroße Projekt mit ein paar tausend Commits (auch mit wenigen Dateien)
MestreLion
10
+1 Vielleicht funktioniert es nicht für jeden möglichen Fall, aber es ist eine gute einfache Antwort.
QWERTY9967
5
Ist die Frage des OP nicht, wie die Zeitstempel der Originaldatei geändert werden sollen, nicht der Festschreibungszeitstempel für die Dateien?
BT
15
Das Entwerfen eines VCS um Make ist kurzsichtig. Ich denke das ist ein Fehler von Git. Es macht also wirklich keinen Sinn, dass es kein Standardverhalten ist. Make-Dateien sollten für den Dateiinhalt und nicht für Zeitstempel ausgeführt werden. Das Hashing der Datei und das Überprüfen, ob der Hash mit dem übereinstimmt, was Sie erstellt haben, ist viel robuster.
BT
4
Ich stimme BT und Teilen Ihres Kommentars Dietrich zu. Was BT mit dem OP gemeint hat, ist, dass Ihre Antwort es nicht wirklich erlaubt, die ursprüngliche Zeit der Datei beizubehalten. Stattdessen werden sie durch die ursprüngliche Checkout-Zeit ersetzt. Nicht das gleiche ... Also , ich glaube , er sagte deutlich Ihr Beitrag sachlichen Fehler enthält. Und ich kann sehen, woher die Entscheidung kam, keine Zeitstempel zu speichern, wie Sie zeigen. Ich denke auch, dass BT ein bisschen auf diese Argumentation zurückgreift. Dem stimme ich wieder zu - keine guten Gründe, es überhaupt nicht zu können. Jedes andere VCS kann das.
Cregox
57

JA , Metastore oder Git-Cache-Meta können solche (Meta-) Informationen speichern! Git allein kann es ohne Tools von Drittanbietern nicht. Metastore oder Git-Cache-Meta können beliebige Dateimetadaten für eine Datei speichern.

Dies ist beabsichtigt, da Metastore oder Git-Cache-Meta genau für diesen Zweck vorgesehen sind und Backup-Dienstprogramme und Synchronisierungstools unterstützen.

(Tut mir leid, nur ein bisschen Spaß an Jakubs Antwort)

BT
quelle
8
Du hast sogar seine All-Caps nachgeahmt! Wenn Sie auch Fettdruck anwenden, werden Sie sicher noch mehr positive Stimmen erhalten. ;-)
Michael Scheper
1
Ich bin ein bisschen verärgert, vor allem, weil diese beiden Tools (nach einigem Hin und Her) den Ball unter macOS auf spektakuläre Weise fallen lassen. Sie sind unter Linux nicht portierbar. git-cache-meta basiert auf findder -printfErweiterung von GNU , und ich bin mir fast sicher, dass Metastore (ein C-Projekt) noch mehr Arbeit ist, um portabel zu machen. Sehr unglücklich. Ich werde hier zurück posten, wenn ich herausfinde, dass sich diese Situation ändert.
Steven Lu
39

NEIN , Git speichert solche (Meta-) Informationen einfach nicht , es sei denn, Sie verwenden Tools von Drittanbietern wie Metastore oder Git-Cache-Meta. Der einzige Zeitstempel, der gespeichert wird, ist die Zeit, zu der Patch / Änderung erstellt wurde (Autorenzeit), und die Zeit, zu der das Commit erstellt wurde (Committer-Zeit).

Dies ist beabsichtigt, da Git ein Versionskontrollsystem ist, kein Sicherungsdienstprogramm oder Synchronisationstool.

Jakub Narębski
quelle
Gibt es Metastore Build für Win32? oder sollte man Skripte / Hooks für Windows neu erstellen? Franklt, ich brauche keine anderen attrs, nur mtime
Arioch 'The
8
Ich denke, Ihre Antwort lautet tatsächlich "JA! Metastore oder Git-Cache-Meta können dies für Sie tun!" Ich denke, es ist der Unterschied zwischen defätistischen und optimistischen Attiutierten.
BT
3
Wie ich gehört habe, sind Basar und Quecksilber auch "Versionskontrollsysteme", die Metainformationen speichern. Daran ist nichts auszusetzen .
Cregox
Klarstellung: Git speichert zwei Zeitstempel für jede Datei: das Autorendatum (was Jakub meiner Meinung nach mit "Zeit-Patch" meint) und das Committer-Datum. Ersteres ist der Zeitpunkt, zu dem die Datei zum ersten Mal festgeschrieben wurde, und letzteres ist der Zeitpunkt, zu dem die Datei zuletzt festgeschrieben wurde.
Michael Scheper
4
"Das ist beabsichtigt, da Git ein Versionskontrollsystem ist, kein Sicherungsdienstprogramm oder Synchronisationstool." Das ist ein keine Folge : Das Ignorieren von Metadaten ( insbesondere Datumsangaben, die eng mit Versionen zusammenhängen) hat nichts damit zu tun, ein VCS oder ein Backup-Tool zu sein. Außerdem weist jedes VCS eine große inhärente Überschneidung von Funktionen mit Sicherungswerkzeugen auf: Beide bemühen sich, wichtige frühere Zustände beizubehalten. Schließlich ignoriert selbst Git nicht alle Metadaten (z. B. verfolgt es das ausführende Bit), obwohl es sich um ein VCS handelt. Es ist immer noch ist durch Design, aber nur aus einem anderen Grunde: Git exklusiven Fokus auf Inhalt.
Gr.
13

UPDATE : TL; DR: git selbst speichert keine Originalzeiten, aber einige Lösungen umgehen dies durch verschiedene Methoden. git-restore-mtimeIst einer von ihnen:

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

Ubuntu / Debian: sudo apt install git-restore-mtime
Fedora / RHEL / CentOS:sudo yum install git-tools

Siehe meine andere Antwort für weitere Details

Vollständiger Haftungsausschluss: Ich bin der Autor von git-tools


Dieses Python-Skript kann hilfreich sein: Für jede Datei wird der Zeitstempel des letzten Commits angewendet, bei dem die Datei geändert wurde:

Unten finden Sie eine wirklich nackte Version des Skripts. Für den tatsächlichen Gebrauch empfehle ich dringend eine der robusteren Versionen oben:

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

Alle Versionen analysieren das vollständige Protokoll, das von einem einzelnen erstellt wurde git whatchanged Befehl Dies ist hunderte Male schneller als das Lopping für jede Datei. Unter 4 Sekunden für Git (24.000 Commits, 2.500 Dateien) und weniger als 1 Minute für Linux-Kernel (40.000 Dateien, 300.000 Commits)

MestreLion
quelle
2
Ihre andere ähnliche Antwort ist viel besser als diese!
Cregox
$ python ./git-restore-mtime Traceback (most recent call last): File "./git-restore-mtime", line 122, in <module> 'git rev-parse --show-toplevel --git-dir')).split('\n')[:2] TypeError: Type str doesn't support the buffer APIWürde es Ihnen etwas ausmachen, uns zu sagen, welche Version von Python benötigt wird? Ich benutze 3.3.3
Rolf
@Cawas: Danke ... ich denke. Aber der Code in beiden Antworten ist identisch, deshalb bin ich mir nicht sicher, warum Sie denken, dass der andere besser ist. Der einzige Unterschied ist, dass man sich über Git lustig macht. Was für diese Frage etwas relevant war, aber nicht für diese.
MestreLion
1
@Rolf: Ich habe Python 2.7 verwendet, und es scheint, dass der Code in Python 3 angepasst werden muss, danke für den Hinweis. Der Grund ist: strIn Python 2 entspricht dies bytestringPython 3, während strPython 3 unicodePython 2 entspricht. Können Sie dieses Problem bitte unter github.com/MestreLion/git-tools/issues melden ?
MestreLion
Es ist nicht nur das "Geschwätz". Dort erklären Sie auch viel detaillierter und damit klarer, was der Code tut.
Cregox
6

Dies hat er für mich auf Ubuntu ausgetrickst (dem OSX das "-j" -Flag am Datum (1) fehlt)

for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso $FILE)
    TIME2=`echo $TIME | sed 's/-//g;s/ //;s/://;s/:/\./;s/ .*//'`
    touch -m -t $TIME2 $FILE
done 
eludom
quelle
4

Ich habe schon seit einiger Zeit mit Git- und Datei-Zeitstempeln gekämpft.

Habe einige deiner Ideen getestet und meine eigenen schrecklich großen und vorgänger- / ramlastigen Skripte erstellt, bis ich (in einem Git-Wiki) ein Skript in Perl gefunden habe, das fast das tut, was ich wollte. https://git.wiki.kernel.org/index.php/ExampleScripts

Und ich wollte in der Lage sein, die letzte Änderung von Dateien basierend auf den Festschreibungsdaten beizubehalten.

Nach einigen Anpassungen kann das Skript das Erstellungs- und Änderungsdatum von 200.000 Dateien in ca. 2-3 Minuten ändern .

#!/usr/bin/perl
my %attributions;
my $remaining = 0;

open IN, "git ls-tree -r --full-name HEAD |" or die;
while (<IN>) {
    if (/^\S+\s+blob \S+\s+(\S+)$/) {
        $attributions{$1} = -1;
    }
}
close IN;

$remaining = (keys %attributions) + 1;
print "Number of files: $remaining\n";
open IN, "git log -r --root --raw --no-abbrev --date=raw --pretty=format:%h~%cd~ |" or die;
while (<IN>) {
    if (/^([^:~]+)~([^~]+)~$/) {
        ($commit, $date) = ($1, $2);
    } elsif (/^:\S+\s+1\S+\s+\S+\s+\S+\s+\S\s+(.*)$/) {
        if ($attributions{$1} == -1) {
            $attributions{$1} = "$date";
            $remaining--;

            utime $date, $date, $1;
            if ($remaining % 1000 == 0) {               
                print "$remaining\n";
            }
            if ($remaining <= 0) {
                break;
            }
        }
    }
}
close IN;

Angenommen, Ihre Repositorys verfügen nicht über mehr als 10.000 Dateien. Die Ausführung sollte Sekunden dauern, damit Sie sie an die Kasse, den Pull oder andere grundlegende Git-Hooks anschließen können.

Lukasz Kruszyna
quelle
2

Hier ist meine Lösung, die Pfade berücksichtigt, die Leerzeichen enthalten:

#! /bin/bash

IFS=$'\n'
list_of_files=($(git ls-files | sort))
unset IFS

for file in "${list_of_files[@]}"; do
  file_name=$(echo $file)

  ## When you collect the timestamps:
  TIME=$(date -r "$file_name" -Ins)

  ## When you want to recover back the timestamps:
  touch -m -d $TIME "$file_name"
done

Beachten Sie, dass dies nicht die Zeit in git logAnspruch nimmt, die gemeldet wird, sondern die vom System gemeldete Zeit. Wenn Sie die Zeit seit dem Festschreiben der Dateien benötigen, verwenden Sie git logstattdessen die Lösungdate -r

Lilian A. Moraru
quelle
2

Native Git verfügt nicht über die Funktionalität, kann jedoch durch Hook-Skripte oder Tools von Drittanbietern erreicht werden.

Ich habe es versucht metastore. Es ist sehr schnell, aber ich mag die Notwendigkeit der Installation nicht und dass Metadaten nicht im Nur-Text-Format gespeichert werden. git-cache-metaist ein einfaches Tool, das ich ausprobiert habe, aber es ist für große Repos extrem langsam (für ein Repo mit Zehntausenden von Dateien dauert das Aktualisieren der Metadatendatei Minuten) und kann plattformübergreifende Kompatibilitätsprobleme aufweisen.setgitpermsund andere Ansätze haben auch ihre Mängel, die ich nicht mag.

Endlich habe ich ein Hook-Skript für diesen Job erstellt: git-store-meta . Es hat eine sehr leichte Abhängigkeit (* nix shell ,, sortundperl , die von git und optional benötigt wird chown, chgrpund touch), so dass für eine Plattform, auf der git ausgeführt werden kann, keine zusätzliche Leistung installiert werden muss, was für ein Repo mit Zehntausenden wünschenswert ist Bei Dateien dauert das Aktualisieren der Metadatendatei <10 Sekunden (obwohl das Erstellen länger dauert), das Speichern von Daten im Nur-Text-Format und das Anpassen der zu "speichern" oder "geladenen" Metadaten ist anpassbar .

Es hat gut für mich funktioniert. Versuchen Sie dies, wenn Sie mit Metastore, Git-Cache-Meta und anderen Ansätzen nicht zufrieden sind.

Danny Lin
quelle
2

Ich hoffe, Sie schätzen die Einfachheit:

# getcheckin - Retrieve the last committed checkin date and time for
#              each of the files in the git project.  After a "pull"
#              of the project, you can update the timestamp on the
#              pulled files to match that date/time.  There are many
#              that believe that this is not a good idea, but
#              I found it useful to get the right source file dates
#
#              NOTE: This script produces commands suitable for
#                    piping into BASH or other shell
# License: Creative Commons Attribution 3.0 United States
# (CC by 3.0 US)

##########
# walk back to the project parent or the relative pathnames don't make
# sense
##########
while [ ! -d ./.git ]
do
    cd ..
done
echo "cd $(pwd)"
##########
# Note that the date format is ISO so that touch will work
##########
git ls-tree -r --full-tree HEAD |\
    sed -e "s/.*\t//" | while read filename; do
    echo "touch --date=\"$(git log -1 --date=iso --format="%ad" -- "$filename")\" -m $filename" 
done
Sailnfool
quelle
(Zu Ihrer Information, der Header-Kommentar enthält eine unbeabsichtigte doppelte Negation, die Sie möglicherweise auch in Ihrem Original korrigieren möchten: "Es gibt viele, die nicht glauben, dass dies keine gute Idee ist.")
Sz.
1

Für die Windows-Umgebung habe ich in Delphi 10.1 Berlin eine kleine (schnelle und schmutzige) EXE-Datei geschrieben, die alle Dateidaten im Quellbaum in der Datei .gitfilattr sammelt und sie erneut auf den überprüften Quellbaum anwenden kann.

Natürlich teile ich den Code in GitHub:

https://github.com/michaschumann/gitfiledates/blob/master/gitFileDates.dpr

Ich verwende es in meinem Build-System, das auf GitLab-Läufern basiert.

MichaSchumann
quelle
1

In meiner (und der anderer) Interpretation des OP gibt es einige Unklarheiten darüber, ob dies die Festschreibungszeit oder etwas anderes bedeutet. Unter der Annahme, dass dies Festschreibungszeit bedeutet, funktioniert dieser einfache Einzeiler unter Linux (basierend auf dem Antwortausschnitt von Dietrich Epp ):

git ls-files | xargs -I{} bash -c 'touch "{}" --date=@$(git log -n1 --pretty=format:%ct -- "{}")'

Es gibt jedoch differenziertere Antworten (einschließlich Git-Hooks), die von einem Kommentar zur ursprünglichen Frage von Cregox verknüpft sind.

mza
quelle
lol das hat eine große Anzahl von Dateien in meiner Kasse mit dem Namen--date=@foo
mxcl
0

Mit GNU-Tools.

s=$(git ls-files  | wc -l); 
git ls-files -z  |
 xargs -0 -I{} -n1 bash -c \
"git log --date=format:%Y%m%d%H%M.%S '--pretty=format:touch -m -t %cd \"{}\"%n' -n1 -- {}"|
 pv -l -s$s |
 parallel -n1 -j8

 967  0:00:05 [ 171 /s] [=====================================>  ] 16% 

.

$ git --version ; xargs --version | sed 1q ; ls --version | sed 1q;
  parallel --version  | sed 1q;  pv --version | sed 1q; sh --version | sed 1q 
git version 2.13.0
xargs (GNU findutils) 4.6.0
ls (GNU coreutils) 8.25
GNU parallel 20150522
pv 1.6.0 - Copyright 2015 Andrew Wood <[email protected]>
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Ярослав Рахматуллин
quelle
Die Parallele scheint nicht viel zu bewirken, wahrscheinlich ein fs-Engpass. YMMV
Ярослав Рахматуллин
0

In CentOS 7 haben Sie /usr/share/doc/rsync-*/support/git-set-file-timesund in Debian (und Derivaten) das gleiche Skript /usr/share/doc/rsync/scripts/git-set-file-times.gz, das Original stammt von Eric Wong und ist hier https://yhbt.net/git-set-file-times .

Es funktioniert schneller als andere hier erwähnte Beispiele, und es ist möglicherweise praktischer, es bereits in Ihrer Linux-Distribution zu haben.

Ivan Baldo
quelle
0

Hier ist meins.

Ein bisschen schneller als einige andere, da ich nicht für jede gefundene Datei 'get log' aufrufe; Rufen Sie stattdessen einmal 'git log' auf und wandeln Sie diese Ausgabe in Touch-Befehle um.

Es wird Fälle geben, in denen zu viele aufgelistete Dateien in einem Commit vorhanden sind, um in einen einzelnen Shell-Befehlspuffer zu passen. Führen Sie "getconf ARG_MAX" aus, um die maximale Länge eines Befehls in Byte anzuzeigen. Bei meiner Debian-Installation sind es 2 MB, was ausreichend ist.

# set file last modification time to last commit of file
git log --reverse --date=iso --name-only | \
  grep -vE "^(commit |Merge:|Author:|    |^$)" | \
  grep -B 1 "^[^D][^a][^t][^e][^:][^ ]" | \
  grep -v "^\-\-" | \
  sed "s|^\(.*\)$|\"\1\"|;s|^\"Date: *\(.*\)\"$|~touch -c -m -d'\1'|" | \
  tr '~\n' '\n ' | \
  sh -

Beschreibung nach Zeile:

  • früheste Liste der Commits und Dateinamen
  • Filtern Sie nicht benötigte Commit / Merge / Author-Zeilen heraus
  • Filtern Sie Zeilen aus, die mit einem Doppelstrich beginnen
  • Befehl sed (stream-edit) a) doppeltes Anführungszeichen an Zeilen voranstellen / anhängen und b) "Datum :. " durch ~ touch -c -m -d ersetzen . (Die Touch-Befehlsoptionen sind -c = nicht erstellen, wenn es nicht vorhanden ist, -m = Änderungszeit der Datei ändern und -d = das angegebene Datum / die angegebene Uhrzeit verwenden)
  • Übersetzen Sie die Zeichen tilda (~) und newline (\ n) in newline bzw. space
  • Leiten Sie den resultierenden Strom von Textzeilen in eine Shell.

In Bezug auf die Geschwindigkeit werden 5 Sekunden 1700 für 6500 Dateien in 700 Verzeichnissen festgeschrieben.

jmullee
quelle