Перевірка старого файлу З оригінальними мітками створення / модифікації часу


82

Чи є спосіб дізнатися або отримати оригінальні мітки часу створення / модифікації?


1
це сторінка чиста, але і питання , і більшість проголосували відповідь, в основному дублюються: stackoverflow.com/questions/1964470 / ...
cregox


Відповіді:


45

Я вважаю, що єдиними позначками часу, записаними в базі даних Git, є позначки часу автора та коміту. Я не бачу можливості для Git змінити позначку часу файлу відповідно до останнього коміту, і має сенс, що це не буде поведінкою за замовчуванням (бо якби це було, Makefiles не працювали б належним чином).

Ви можете написати сценарій, щоб встановити дату модифікації ваших файлів до часу останнього коміту. Це може виглядати приблизно так:

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

10
У цього фрагмента є кілька проблем: 1 - він не працює, якщо в іменах файлів є пробіли; 2 - може виявитися невдалим для проектів, що містять більше кількох тисяч файлів; 3 - продуктивність абсолютно жалюгідна для будь-якого середнього проекту з кількома тисячами
комітів

10
+1, можливо, це працює не у всіх можливих випадках, але це гарна проста відповідь.
qwerty9967

5
Чи не питання OP, як зберегти оригінальний файл зміненими мітками часу, а не закріпити мітку часу фіксації до файлів?
BT

15
Розробка VCS навколо Make є короткозорою. Я думаю, що це провина Git. Тож насправді не має сенсу, що це не поведінка за замовчуванням. Зробити файли повинні працювати на вмісті файлів, а не мітки часу. Хешування файлу та перевірка відповідності хешу тому, що ви створили, є набагато надійнішою.
BT

4
Я погоджуюсь з BT та частинами Вашого коментаря Дітріха. Що BT мав на увазі щодо OP, це те, що ваша відповідь насправді не дозволяє зберегти початковий час файлу. Натомість він замінює їх початковим часом оформлення замовлення. Не те саме ... Отже , я думаю, він чітко сказав, що у вашій публікації містяться фактичні помилки. І я бачу, звідки взялося рішення не зберігати мітки часу, як ви вказуєте. Я також думаю, що BT трохи посилається на ці міркування. На що я знову погоджуюсь з BT - не вагомі причини для того, щоб взагалі не змогти це зробити. Кожен інший VCS може це зробити.
cregox

57

ТАК , metastore або git-cache-meta можуть зберігати таку (мета-) інформацію! Git сам по собі, без сторонніх інструментів, не може. Metastore або git-cache-meta можуть зберігати будь-які метадані файлу для файлу.

Це за задумом, оскільки метастор або git-cache-meta призначені саме для цієї мети, а також підтримують утиліти резервного копіювання та засоби синхронізації.

(Вибачте лише трохи веселого розкручування відповіді Якуба)


8
Ви навіть імітували його все-шапки! Якщо ви також застосуєте жирний шрифт, я впевнений, ви отримаєте ще більше голосів. ;-)
Майкл Шепер

1
Тож я трохи змішаний, головним чином тому, що обидва ці інструменти (після певного заглиблення в них) вражаюче скидають м’яч на macOS. Вони повністю не переносяться з Linux. ГИТ-кеш-мета покладається на GNU find«s -printfрозширення, і я майже впевнений , що metastore (будучи C проект) ще більше роботи , щоб зробити портативний. Зовсім прикро. Я відправлю сюди повідомлення, якщо з’ясую, що ситуація змінилася.
Стівен Лу

39

НІ , Git просто не зберігає таку (мета-) інформацію , якщо ви не використовуєте сторонні інструменти, такі як metastore або git-cache-meta. Єдиною міткою часу, яка зберігається, є час виправлення / зміни часу (час автора) і час коміту (час комітера).

Це за задумом, оскільки Git - це система контролю версій, а не утиліта резервного копіювання чи інструмент синхронізації.


чи існує побудова метамагазину для win32? чи слід заново створювати сценарії / хуки для Windows? Franklt, мені не потрібні інші атрибути, лише mtime
Arioch

8
Я думаю, що ваша відповідь насправді "ТАК! Metastore або git-cache-meta можуть це зробити за вас!" Я думаю, це різниця між пораженими та оптимістами.
BT

3
Плюс, як я чув, Bazaar та Mercurial - це також "системи контролю версій", які зберігають метаінформацію. У цьому немає нічого поганого .
cregox

Пояснення: Git зберігає дві позначки часу для кожного файлу: дату автора (що, на мою думку, Якуб має на увазі під «патчем часу») і дату комітера. Перший - це час, коли файл був вперше зафіксований, а другий - час, коли файл був нещодавно зафіксований.
Майкл Шепер

4
"Це за задумом, оскільки Git - це система контролю версій, а не утиліта резервного копіювання або інструмент синхронізації." Це не послідовно : ігнорування метаданих ( особливо дат, які тісно пов’язані з версіями) не має нічого спільного з тим, щоб бути VCS або інструментом резервного копіювання. Крім того, кожному VCS притаманне велике перекриття функціональних можливостей із засобами резервного копіювання: вони обидва прагнуть зберегти важливі минулі стани. Нарешті, навіть Git не ігнорує всі метадані (наприклад, він відстежує виконуваний біт), незважаючи на те, що він є VCS. Він по- , як і раніше є конструкцією, хоча, тільки з іншої причини: виняткову увагу Git і на утримання.
Sz.

13

ОНОВЛЕННЯ : TL; DR: сам git не економить початковий час, але деякі рішення обходять це різними методами.git-restore-mtimeє одним з них:

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

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

Дивіться мою іншу відповідь для отримання більш докладної інформації

Повна відмова: Я автор git-tools


Цей сценарій python може допомогти: для кожного файлу застосовується позначка часу останнього коміту, де файл було змінено:

Нижче - справді гола версія сценарію. Для фактичного використання я настійно рекомендую одну з найбільш надійних версій вище:

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

Усі версії аналізують повний журнал, згенерований однією git whatchangedкомандою, що в сотні разів швидше, ніж лопінг для кожного файлу. Менше 4 секунд для git (24 000 комітів, 2500 файлів) і менше 1 хвилини для ядра Linux (40 000 файлів, 300 000 комітів)


2
Ваша інша подібна відповідь набагато краща за цю!
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 APIНе могли б ви сказати нам, яка версія Python потрібна? Я використовую 3.3.3
Рольф

@Cawas: Дякую ... Гадаю. Але код в обох відповідях однаковий, тому я не впевнений, чому ви вважаєте, що інший краще. Єдина відмінність - це деякі висловлювання щодо git. Що було дещо доречним для цього питання, але не для цього.
MestreLion

1
@Rolf: Я використовував Python 2.7, і, здається, коду потрібно трохи налаштувати в Python 3, дякую за вказівку. Причина в тому, що strв Python 2 еквівалент bytestringPython 3, тоді як strу Python 3 - unicodePython 2. Чи можете ви повідомити про цю проблему на github.com/MestreLion/git-tools/issues ?
MestreLion

Справа не лише в "гаморі". Там ви також пояснюєте, що робить код, набагато детальніше і, отже, чіткість.
cregox

6

Це він обдурив мене в ubuntu (в якому бракує прапорця "-j" OSX на дату (1))

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 

4

Я вже деякий час стикаюся з мітками часу git і file.

Перевірив деякі ваші ідеї та створив власні страшенно величезні та важкі сценарії попередників / оперативної пам'яті, доки я не знайшов (на деяких git wiki) сценарій у perl, який робить майже те, що я хотів. https://git.wiki.kernel.org/index.php/ExampleScripts

І я хотів, щоб мати можливість зберегти останню модифікацію файлів на основі дат комітів.

Отже, після деякої корекції сценарій може змінити дату створення та модифікації 200 тис . Файлів приблизно за 2-3 хв .

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

Якщо припустити, що у ваших сховищах не буде файлів більше 10 тис., Це може зайняти секунди, щоб ви могли підключити його до каси, тягнути або інших основних хуків git.


2

Ось моє рішення, яке враховує шляхи, що містять пробіли:

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

Зверніть увагу, що це не займає час, який git logзвітує, це час, про який повідомляє система. Якщо вам потрібен час з моменту, коли файли були записані, використовуйте git logрішення замістьdate -r


2

Рідний git не має функціональних можливостей, але його можна досягти за допомогою скриптових скриптів або сторонніх інструментів.

Я пробував metastore. Це дуже швидко, але мені не подобається необхідність встановлення та те, що метадані не зберігаються у форматі простого тексту. git-cache-metaце простий інструмент, який я спробував, але надзвичайно повільний для великих репозитаріїв (для репозиторію з десятками тисяч файлів оновлення файлу метаданих займає хвилини) і може мати проблеми з сумісністю між платформами.setgitpermsта інші підходи також мають свої недоліки, які мені не подобаються.

Нарешті я створив скрипт для цієї роботи: git-store-meta . Він має дуже легку залежність (* nix shell,, sortі perl, що вимагається git, і, за бажанням chown, chgrpі touch), так що для платформи, яка може запускати git, не потрібно встановлювати нічого додаткового, бажана продуктивність (для репо з десятками тисяч файлів, оновлення файлу метаданих займає <10 секунд; хоча і довше для створення), зберігає дані у форматі звичайного тексту , а які метадані, які потрібно «зберегти» чи «завантажити», можна налаштувати .

Це добре в мене спрацювало. Спробуйте це, якщо вас не влаштовують metastore, git-cache-meta та інші підходи.


2

Сподіваюся, ви оціните простоту:

# 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

(FYI, є ненавмисне подвійне заперечення в заголовку коментаря, який ви можете захотіти виправити у вашому оригіналі теж: «Є багато , що НЕ вважають , що це НЕ дуже хороша ідея.»)
Sz.

1

Для середовища Windows я написав невеликий (швидкий і брудний) EXE в Delphi 10.1 Berlin, який збирає всі дати файлів у вихідному дереві у файл .gitfilattr і може застосувати їх у перевіреному нашому дереві джерела знову.

Звичайно, я ділюсь кодом у GitHub:

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

Я використовую його у своїй системі збірки на базі бігунів GitLab.


1

У моїй (та інших) інтерпретації OP є певна неоднозначність щодо того, чи означає це час коміту чи щось інше, але припускаючи, що це означає час коміту, тоді цей простий одношаровий лайнер буде працювати в Linux (на основі фрагмента відповіді Дітріха Еппа ):

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

Але є ще складніші відповіді (включаючи git hooks), пов’язані з коментаря до оригінального запитання cregox.


ха-ха, це скинуло величезну кількість файлів у моєму касі з іменем--date=@foo
mxcl

0

За допомогою інструментів GNU.

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 <andrew.wood@ivarch.com>
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

Паралель, здається, не робить багато, можливо, вузьке місце fs. YMMV
Ярослав Рахматуллін

0

У CentOS 7 у вас /usr/share/doc/rsync-*/support/git-set-file-timesі в Debian (та похідних) однаковий сценарій /usr/share/doc/rsync/scripts/git-set-file-times.gz, оригінал від Еріка Вонга і тут https://yhbt.net/git-set-file-times .

Це працює швидше, ніж інші згадані тут приклади, і вам може бути зручніше мати його вже у своєму дистрибутиві Linux.


0

Ось моя.

Трохи швидше, ніж деякі інші, оскільки я не називаю "отримати журнал" для кожного знайденого файлу; натомість один раз викликає 'git log' і трансформує цей результат у команди дотику.

Будуть випадки, коли в одному коміті занадто багато файлів із переліком, щоб вміститися в один буфер команд оболонки; запустіть "getconf ARG_MAX", щоб побачити максимальну довжину команди в байтах - на моїй установці debian це 2 Мб, що достатньо.

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

опис за рядком:

  • найперший список комітів та імен файлів
  • відфільтрувати непотрібні рядки коміту / злиття / автора
  • відфільтруйте рядки, починаючи з подвійного тире
  • команда sed (редагувати потік) a) додавати / додавати подвійні лапки до рядків, і b) замінити "Date:. " на ~ touch -c -m -d. (параметри сенсорної команди -c = не створювати, якщо вона не існує, -m = змінити час модифікації файлу та -d = використовувати вказану дату / час)
  • перекласти символи tilda (~) та newline (\ n) відповідно до newline та space
  • перевести отриманий потік текстових рядків у оболонку.

Що стосується швидкості, це 5 секунд 1700 комітів для 6500 файлів у 700 каталогах.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.