Извлечение старого файла с оригинальными временными метками создания / изменения
Есть ли способ узнать или получить оригинальные метки создания / изменения времени? Благодарю.
18 ответов
Я считаю, что единственными временными метками, записанными в базе данных 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
ДА, metastore или git-cache-meta могут хранить такую (мета) информацию! Git сам по себе, без сторонних инструментов, не может. Metastore или git-cache-meta могут хранить любые метаданные файла для файла.
Это сделано специально, так как metastore или git-cache-meta предназначены именно для этой цели, а также поддерживают утилиты резервного копирования и инструменты синхронизации.
(Извините, немного забавного ответа на ответ Якуба)
НЕТ, Git просто не хранит такую (мета) информацию, если вы не используете сторонние инструменты, такие как metastore или git-cache-meta. Единственная временная метка, которая будет сохранена, это время, когда был создан патч / изменение (время автора), и время фиксации было создано (время коммиттера).
Это сделано специально, так как Git - это система контроля версий, а не утилита резервного копирования или инструмент синхронизации.
Этот скрипт на python может помочь: для каждого файла применяется временная метка самого последнего коммита, где файл был изменен:
- Основная функциональность, с --help, отладочные сообщения. Может быть запущен в любом месте рабочего дерева
- Полноценный зверь, с множеством вариантов. Поддерживает любой макет хранилища.
Ниже приведена действительно простая версия сценария. Для реального использования я настоятельно рекомендую одну из более надежных версий выше:
#!/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 коммитов)
Это он обманул меня в Ubuntu (в OSX отсутствует флаг "-j" на date(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
У нативного git нет функциональности, но это может быть достигнуто с помощью скриптов-хуков или сторонних инструментов.
я пробовал metastore
, Это очень быстро, но мне не нравится необходимость установки, и метаданные не хранятся в текстовом формате. git-cache-meta
это простой инструмент, который я пробовал, но он очень медленный для больших репозиториев (для репо с десятками тысяч файлов требуется несколько минут, чтобы обновить файл метаданных) и может иметь проблемы кроссплатформенной совместимости. setgitperms
и другие подходы также имеют свои недостатки, которые мне не нравятся.
Наконец я сделал скрипт для этой работы: git-store-meta. У него очень легкая зависимость (* nix shell, sort
, а также perl
, что требуется git, и, необязательно, chown
, chgrp
а также touch
) чтобы на платформе, на которой можно запустить git, не нужно было устанавливать ничего лишнего, желаемой производительности (для репозитория с десятками тысяч файлов требуется менее 10 секунд для обновления файла метаданных; хотя и дольше для создания), данные сохраняются в простой текстовый формат, и какие метаданные должны быть "сохранены" или "загружены" настраивается.
Это работало хорошо для меня. Попробуйте это, если вы не удовлетворены метастарем, git-cache-meta и другими подходами.
Я уже некоторое время перебиваюсь с метками времени 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.
В отличие от других решений, которые устанавливают mtime для времени фиксации, git-store-meta сохраняет метаданные, такие как mtime, в файл .git_store_meta, который добавляется в репозиторий. Он может устанавливать git-хуки в текущий репозиторий, которые автоматически сохраняют и применяют метаданные.
Смотрите https://github.com/przemoc/metastore
Он содержится в репо Debain, просто apt-get install metastore
,
Я надеюсь, вы оцените простоту:
# 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 don't 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
Вот мое решение, которое учитывает пути, которые содержат пробелы:
#! /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
Для среды Windows я написал небольшой (быстрый и грязный) EXE-файл в Delphi 10.1 Berlin, который собирает все даты файлов в дереве исходного кода в файл.gitfilattr и может снова применить их к проверенному нашему исходному дереву.
Конечно, я делюсь кодом в GitHub:
https://github.com/michaschumann/gitfiledates/blob/master/gitFileDates.dpr
Я использую его в своей системе сборки на основе бегунов GitLab.
В моей (и других) интерпретации ОП есть некоторая двусмысленность относительно того, означает ли это время фиксации или что-то еще, но если предположить, что это означает время фиксации, тогда этот простой однострочный текст будет работать в Linux (на основе фрагмента ответа Дитриха Эппа).):
git ls-files | xargs -I{} bash -c 'touch "{}" --date=@$(git log -n1 --pretty=format:%ct -- "{}")'
Но есть более изощренные ответы (включая git hooks), связанные с комментарием на оригинальный вопрос от cregox.
В 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.
Git не поддерживает сохранение дат файлов.
Но вы можете использовать git-meta, который
git-cache-meta
превратился в актуальный репозиторий (реализованы все комментарии в сущности); теперь его можно установить как ловушку git, поэтому метаданные будут автоматически сохраняться при каждой фиксации!
Итак, git по умолчанию не поддерживает хранение метаданных файлов; но это не значит, что вы не можете модифицировать его с помощью специальных функций (LFS - это доказательство того, как вы можете расширить git).
С инструментами 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)
Вот мой.
Немного быстрее, чем некоторые другие, так как я не вызываю "получение журнала" для каждого найденного файла; вместо этого вызовите "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 (stream-edit) а) вставлять / добавлять двойные кавычки к строкам и б) заменять "Дата:." на ~touch -c -m -d.(параметры сенсорной команды: -c = не создавать, если он не существует, -m = изменить время модификации файла и -d = использовать указанную дату / время)
- переводить символы тильды (~) и новой строки (\n) в новую строку и пробел соответственно
- перенаправить получившийся поток текстовых строк в оболочку.
Что касается скорости, это 5 секунд, 1700 коммитов для 6500 файлов в 700 каталогах.
for file in `find . -type f -not -path "./.git/*"`; do
touch -d "`git rev-list -n 1 HEAD \$file | xargs git show -s --format=%ai`" $file;
done