Что эквивалентно времени использования-фиксации для git?

97

Мне нужно, чтобы метки времени файлов на моем локальном и на моем сервере синхронизировались. Это достигается с помощью Subversion путем установки use-commit-times = true в конфигурации, чтобы последнее изменение каждого файла происходило при его фиксации.

Каждый раз, когда я клонирую свой репозиторий, я хочу, чтобы временные метки файлов отражали, когда они в последний раз были изменены в удаленном репозитории, а не когда я клонировал репозиторий.

Есть ли способ сделать это с помощью git?

Бен В
источник
В процессе развертывания я загружаю ресурсы (изображения, файлы javascript и файлы css) в CDN. К каждому имени файла добавляется метка времени последнего изменения. Важно, чтобы у меня не истекал срок действия всех моих активов при каждом развертывании. (Еще один побочный эффект использования use-commit-times заключается в том, что я могу выполнять этот процесс на моем локальном компьютере и знать, что мой сервер будет ссылаться на те же файлы, но это не так важно.) Если вместо клонирования git я сделал git fetch, за которым следует git reset --hard из моего удаленного репо, который будет работать для одного сервера, но не для нескольких серверов, поскольку метки времени на каждом будут различаться.
Ben W
@BenW: git annexможет быть полезно для отслеживания изображений
jfs
Вы можете проверить, что изменилось, проверив id. Вы пытаетесь сделать так, чтобы временные метки файловой системы означали то же самое, что и временные метки vcs. Они означают не одно и то же.
jthill

Ответы:

25

Я не уверен, что это подойдет для DVCS (как в «Распределенной» VCS).

Огромное обсуждение уже состоялось в 2007 году (см. Эту ветку)

И некоторым из ответов Линуса эта идея не понравилась. Вот один пример:

Мне жаль. Если вы не видите , как это неправильно установить DATESTAMP назад к чему - то , что будет сделать просто «грим» ошибки компиляции дерева исходных текстов, я не знаю , что defintiion «неправильный» вы говорите.
Это не правильно.
Это глупо.
И реализовать это совершенно НЕВЕРОЯТНО.


(Примечание: небольшое улучшение: после оформления заказа временные метки актуальных файлов больше не изменяются (Git 2.2.2+, январь 2015 г.): «git checkout - как я могу поддерживать временные метки при переключении ветвей?» .)


Длинный ответ был:

Я думаю, вам будет гораздо лучше использовать несколько репозиториев, если это обычное дело.

В общем, возиться с временными метками не получится. Это просто гарантирует вам, что "make" очень сильно запутается и не перекомпилирует достаточно, вместо того чтобы перекомпилировать слишком много .

Git действительно позволяет сделать вашу вещь "проверить другую ветку" очень легко, многими различными способами.

Вы можете создать какой-нибудь тривиальный сценарий, который выполняет одно из следующих действий (от тривиального до более экзотического):

  • просто создайте новое репо:
    git clone old new
    cd new
    git checkout origin/<branch>

и вот ты где. Старые временные метки подходят для вашего старого репо, и вы можете работать (и компилировать) в новом, не затрагивая старую вообще.

Используйте флаги «-n -l -s» для «git clone», чтобы сделать это мгновенно. Для большого количества файлов (например, больших репозиториев, таких как ядро), это будет не так быстро, как просто переключение ветвей, но создание второй копии рабочего дерева может быть весьма эффективным.

  • сделайте то же самое с тарболом вместо этого, если хотите
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

что действительно довольно быстро, если вам просто нужен снимок.

  • привыкнуть к " git show" и просто смотреть на отдельные файлы. Иногда
    это действительно полезно. Ты просто делаешь
    git show otherbranch:filename

в одном окне xterm и посмотрите тот же файл в текущей ветке в другом окне. В частности, это должно быть тривиально в редакторах со сценариями (например, GNU emacs), где должна быть возможность иметь в принципе целый «режим dired» для других веток внутри редактора, используя это. Насколько я знаю, режим emacs git уже предлагает что-то вроде этого (я не пользователь emacs)

  • и в крайнем примере этой штуки с «виртуальным каталогом», по крайней мере, кто-то работал над подключаемым модулем git для FUSE, т.е. вы могли буквально иметь виртуальные каталоги, показывающие все ваши ветки.

и я уверен, что любой из вышеперечисленных вариантов лучше, чем играть в игры с временными метками файлов.

Линус

VonC
источник
5
Согласовано. Не следует путать DVCS с системой распределения. git- это DVCS для управления исходным кодом, который будет встроен в ваш конечный продукт. Если вам нужна система распространения, вы знаете, где ее найти rsync.
Randal Schwartz
14
Хм, мне придется верить его аргументу, что это невозможно. Другое дело, неправильно это или глупо. Я проверяю свои файлы, используя временную метку, и загружаю их в CDN, поэтому важно, чтобы временные метки отражали, когда файл был фактически изменен, а не когда он последний раз был извлечен из репо.
Ben W
3
@Ben W: «Ответ Линуса» здесь не для того, чтобы сказать, что это неправильно в вашей конкретной ситуации. Это только как напоминание о том, что DVCS не очень подходит для такой функции (сохранение отметки времени).
VonC
15
@VonC: Поскольку другие современные DVCS, такие как Bazaar и Mercurial, отлично обрабатывают временные метки, я бы сказал, что « git не очень подходит для такого рода функций». Вопрос о том, должна ли такая функция у "DVCS" быть, остается спорным (и я твердо уверен, что они есть).
MestreLion
10
Это не ответ на вопрос, а философское обсуждение достоинств этого в системе контроля версий. Если бы человеку это понравилось, он бы спросил: «По какой причине git не использует время фиксации для времени изменения файлов?»
thomasfuchs
85

Однако, если вы действительно хотите использовать время фиксации для отметок времени при проверке, попробуйте использовать этот сценарий и поместите его (как исполняемый файл) в файл $ 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

Обратите внимание, однако, что этот сценарий вызовет довольно большую задержку при проверке больших репозиториев (где большой означает большое количество файлов, а не большие размеры файлов).

Giel
источник
55
+1 за фактический ответ, вместо того, чтобы просто сказать «Не делай этого»
DanC
4
| head -n 1следует избегать , поскольку это порождает новый процесс, -n 1для git rev-listи git logможет быть использован вместо.
eregon
3
Лучше НЕ читать строки с `...`и for; см. Почему вы не читаете строки с "for" . Я бы пошел на git ls-files -zи while IFS= read -r -d ''.
musiphil 08
2
Возможна ли версия для Windows?
Эрик
2
вместо того, чтобы git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1вы могли это сделать git show --pretty=format:%ai -s "$(get_file_rev "$1")", это приводит к тому, что showкоманда генерирует намного меньше данных и снижает накладные расходы.
Скотт Чемберлен
80

ОБНОВЛЕНИЕ : мое решение теперь упаковано в Debian / Ubuntu / Mint, Fedora, Gentoo и, возможно, в другие дистрибутивы:

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, отказ от хранения меток времени (и других метаданных, таких как разрешения и владение) является большим ограничением git.

Обоснование Линуса того, что временные метки вредны только потому, что они "сбивают с толку make", неубедительно :

  • make clean достаточно, чтобы исправить любые проблемы.

  • Применимо только к проектам, использующим make, в основном, C / C ++. Это совершенно не подходит для таких скриптов, как Python, Perl или документации в целом.

  • Будет только вред, если вы примените временные метки. Хранить их в репо не повредит . Их применение может быть простым --with-timestampsвариантом для git checkoutдрузей ( cloneи pullт. Д.) По усмотрению пользователя .

И Bazaar, и Mercurial хранят метаданные. Пользователи могут применять их или нет при оформлении заказа. Но в git, поскольку исходные временные метки даже не доступны в репо, такой опции нет.

Итак, для очень небольшого выигрыша (без необходимости заново компилировать все), специфичного для подмножества проектов, gitпоскольку общая DVCS была повреждена , некоторая информация о файлах потеряна , и, как сказал Линус, НЕОБХОДИМО сделать это сейчас. Грустно .

Тем не менее, могу ли я предложить 2 подхода?

1 - http://repo.or.cz/w/metastore.git , Дэвид Хердеман. Пытается сделать то, что git должно было быть сделано в первую очередь : сохраняет метаданные (не только временные метки) в репозитории при фиксации (с помощью хука предварительной фиксации) и повторно применяет их при извлечении (также с помощью хуков).

2 - Моя скромная версия скрипта, который я использовал раньше для создания архивов релизов. Как уже упоминалось в других ответах, подход немного отличается : применять для каждого файла , временная метка из самой последней фиксации , где был изменен файл.

  • git-restore-mtime с множеством опций поддерживает любой макет репозитория и работает на Python 3.

Ниже приведена действительно простая версия скрипта в качестве доказательства концепции на Python 2.7. Для фактического использования я настоятельно рекомендую полную версию, указанную выше:

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

Производительность очень впечатляет, даже для монстра проектов wine, gitили даже ядра 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
MestreLion
источник
2
Но git делает магазин временные метки и т.д. Это просто не устанавливает временные метки по умолчанию. Просто посмотрите на результатgit ls-files --debug
Росс Смит II
9
@RossSmithII: git ls-filesработает с рабочим каталогом и индексом, поэтому это не означает, что он фактически хранит эту информацию в репо. Если бы он сохранил, получение (и применение) mtime было бы тривиальным.
MestreLion
13
«Объяснение Линуса того, что временные метки вредны только потому, что они« сбивают с толку make »- неубедительно» - согласен на 100%, DCVS не должен знать или заботиться о коде, который он содержит! Это снова показывает подводные камни попытки перепрофилировать инструменты, написанные для конкретных случаев использования, в общие варианты использования. Mercurial был и всегда будет лучшим выбором, потому что он был разработан, а не усовершенствован.
Ян Кемп,
6
@davec Пожалуйста, рад, что это было полезно. Полная версия на github.com/MestreLion/git-tools уже поддерживает Windows, Python 3, пути, отличные от ASCII, и т. Д. Приведенный выше сценарий является лишь рабочим доказательством концепции, избегайте его использования в производственной среде.
MestreLion
3
Ваши аргументы верны. Я надеюсь, что кто-то с некоторым влиянием сделает запрос на улучшение для git, чтобы иметь предложенную вами опцию --with-timestamps.
weberjn 02
12

Я взял ответ Гила и вместо того, чтобы использовать сценарий ловушки после фиксации, включил его в свой собственный сценарий развертывания.

Обновление : я также удалил одно | head -nследующее предложение @eregon и добавил поддержку файлов с пробелами в них:

# 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
Алекс Дин
источник
Спасибо, Дэниел, это полезно знать
Алекс Дин
используется --abbrev-commitв git showкоманде излишне --pretty=format:%ai(хеш фиксации не является частью вывода) и | head -n 1может быть заменен -sфлагом using togit show
Elan Ruusamäe
1
@ DanielS.Sterling: %aiдата автора, формат, подобный ISO 8601 , для строгого использования iso8601%aI : git-scm.com/docs/git-show
Элан Руусамяэ
4

мы были вынуждены изобрести еще одно решение, потому что нам требовалось определенное время модификации, а не время фиксации, и решение также должно было быть переносимым (т.е. заставить python работать в установках git Windows действительно непросто) и быстро. Это напоминает решение Дэвида Хардемана, которое я решил не использовать из-за отсутствия документации (из репозитория я не смог понять, что именно делает его код).

Это решение хранит mtimes в файле .mtimes в репозитории git, обновляет их соответствующим образом при коммитах (только выборочно mtimes поэтапных файлов) и применяет их при оформлении заказа. Он работает даже с версиями git cygwin / mingw (но вам может потребоваться скопировать некоторые файлы из стандартного cygwin в папку git)

Решение состоит из 3-х файлов:

  1. mtimestore - основной скрипт, предоставляющий 3 варианта -a (сохранить все - для инициализации в уже существующем репо (работает с файлами, ориентированными на git)), -s (для сохранения поэтапных изменений) и -r для их восстановления. На самом деле он существует в двух версиях - bash (переносимая, приятная, легко читаемая / изменяемая) и версия c (беспорядочная, но быстрая, потому что mingw bash ужасно медленный, что делает невозможным использование решения bash в больших проектах).
  2. хук предварительной фиксации
  3. пост-кассовый крючок

предварительная фиксация:

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

пост-расчет

#!/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;
}
  • обратите внимание, что хуки могут быть помещены в каталог шаблонов, чтобы автоматизировать их размещение

дополнительную информацию можно найти здесь https://github.com/kareltucek/git-mtime-extension, некоторая устаревшая информация находится на http://www.ktweb.cz/blog/index.php?page=page&id=116

// редактировать - обновлена ​​версия c ++:

  • Теперь версия C ++ поддерживает алфавитный порядок -> меньше конфликтов слияния.
  • Избавился от уродливых вызовов system ().
  • Удален $ git update-index --refresh $ из хука после оформления заказа. Вызывает некоторые проблемы с откатом под tortoise git, и в любом случае это не имеет большого значения.
  • Наш пакет Windows можно загрузить по адресу http://ktweb.cz/blog/download/git-mtimestore-1.4.rar

// редактируем актуальную версию см. на github

Карел Тучек
источник
1
Обратите внимание, что после оформления заказа временные метки обновленных файлов больше не изменяются (Git 2.2.2+, январь 2015 г.): stackoverflow.com/a/28256177/6309
VonC
3

Следующий сценарий вобрал в -n 1и HEADпредложения, работы в большинстве сред , отличных от Linux (как Cygwin), и может работать на кассе после факта:

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

Предполагая, что вы назвали приведенный выше сценарий /path/to/templates/hooks/post-checkoutи / или /path/to/templates/hooks/post-updateвы можете запустить его в существующем репозитории с помощью:

git clone git://path/to/repository.git
cd repository
/path/to/templates/hooks/post-checkout
Росс Смит II
источник
Ему нужна еще одна последняя строка: git update-index --refresh // Инструменты графического интерфейса могут полагаться на индекс и показывать "грязный" статус для всего файла после такой операции. А именно это происходит в TortoiseGit для Windows code.google.com/p/tortoisegit/issues/detail?id=861
'The
1
И спасибо за сценарий. Я бы хотел, чтобы такой скрипт был частью стандартного установщика Git. Не то чтобы мне это было нужно лично, но члены команды просто ощущают обновление метки времени как красный баннер остановки при внедрении VCS.
Ариох 'The
3

Это решение должно работать довольно быстро. Он устанавливает atimes на время коммиттера и mtimes на время автора. Он не использует никаких модулей, поэтому должен быть достаточно портативным.

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

Вот оптимизированная версия вышеупомянутых решений оболочки с небольшими исправлениями:

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

Вот метод с 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);
}

Это похоже на ответ здесь:

Что эквивалентно времени использования-фиксации для git?

он создает список файлов, подобный этому ответу, но он строится из git ls-files него, а не просто просматривает рабочий каталог. Это решает проблему исключения .git, а также решает проблему неотслеживаемых файлов. Кроме того, этот ответ не выполняется, если последняя фиксация файла была фиксацией слияния, с которой я решил git log -m. Как и другой ответ, он остановится, как только будут найдены все файлы, поэтому ему не нужно читать все коммиты. Например с:

https://github.com/git/git

на момент публикации было прочитано всего 292 коммита. Также он игнорирует старые файлы из истории по мере необходимости и не трогает файл, который уже был затронут. Наконец, кажется, что это немного быстрее, чем другое решение. Результаты с git/gitрепо:

PS C:\git> Measure-Command { git-touch.php }
TotalSeconds      : 3.4215134
Стивен Пенни
источник
0

Я видел несколько запросов на версию для Windows, так что вот она. Создайте следующие два файла:

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

При этом используется git whatchanged , поэтому он просматривает все файлы за один проход вместо вызова git для каждого файла.

Мозг2000
источник