Що еквівалентно часу використання-коміту для git?


97

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

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

Чи є спосіб зробити це за допомогою git?


Як частина мого процесу розгортання, я завантажую ресурси (зображення, файли javascript та css-файли) на CDN. Кожне ім'я файлу додається з останньою зміненою міткою часу. Важливо, щоб я не закінчував усі свої активи кожного разу, коли я розгортаю. . git fetch, за яким слідує git reset --hard з мого віддаленого репо, це буде працювати для одного сервера, але не для кількох серверів, оскільки мітки часу на кожному будуть різними.
Ben W

@BenW: git annexможе бути корисним для відстеження зображень
jfs

Ви можете перевірити, що змінилося, перевіривши ідентифікатори. Ви намагаєтесь зробити так, щоб мітки часу файлової системи означали те саме, що мітки часу vcs. Вони означають не одне і те ж.
jthill

Відповіді:


25

Я не впевнений, що це було б доречно для DVCS (як у "Розподіленому" VCS)

Величезна дискусія вже відбулася в 2007 році (див. Цю тему)

І деякі відповіді Лінуса не надто захоплювались цією ідеєю. Ось один зразок:

Мені шкода. Якщо ви не бачите, як НЕПРАВИЛЬНО повертати метку дати до чогось, що змусить просту "змусити" неправильно скомпілювати ваше вихідне дерево, я не знаю, про яке визначення "неправильного" ви говорите.
Це неправильно.
Це ДУРО.
І це абсолютно НЕВИННОВИЙ для реалізації.


(Примітка: невелике вдосконалення: після оформлення відмітки часу оновлених файлів більше не змінюються (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), де в основному має бути можливість мати цілий "режим роботи" для інших гілок в редакторі, використовуючи це. Наскільки я знаю, режим emacs git вже пропонує щось подібне (я не користувач emacs)

  • і в крайньому прикладі цього "віртуального каталогу", щонайменше хтось працював над плагіном git для FUSE, тобто ви могли буквально просто мати віртуальні каталоги, що відображають усі ваші гілки.

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

Лінус


5
Домовились. Не слід плутати DVCS із системою розподілу. git- це DVCS для маніпулювання вихідним кодом, який буде вбудований у ваш кінцевий продукт. Якщо вам потрібна система розподілу, ви знаєте, де її знайти rsync.
Рендал Шварц

14
Хм, мені доведеться довіряти його аргументації, що це неможливо. Чи це неправильно, чи дурно - це вже інша справа. Я виконую версію своїх файлів за допомогою мітки часу і завантажую їх на CDN, тому важливо, щоб мітки часу відображали, коли файл був фактично змінений, а не коли його востаннє витягували з репо.
Ben W

3
@Ben W: "Відповідь Лінуса" не тут, щоб сказати, що це неправильно у вашій конкретній ситуації. Це лише як нагадування про те, що DVCS не дуже підходить для такого роду функцій (збереження позначки часу).
VonC

15
@VonC: Оскільки інші сучасні DVCS, такі як Bazaar та Mercurial, обробляють відмітки часу, чудово, я б скоріше сказав, що " git не дуже підходить для такого роду функцій". Якщо "a" 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

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


55
+1 за фактичну відповідь, а не просто сказати "Не роби цього"
DanC

4
| head -n 1слід уникати , оскільки це породжує новий процес, -n 1для git rev-listі git logможе бути використаний замість.
Ерегон

3
Краще НЕ читати рядки з `...`і for; дивіться, чому ви не читаєте рядки з "за" . Я піду на git ls-files -zі while IFS= read -r -d ''.
musiphil

2
Чи можлива версія Windows?
Ehryk,

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команда спричиняє набагато менше даних, і це зменшує накладні витрати.
Скотт Чемберлен,

79

ОНОВЛЕННЯ : Моє рішення тепер упаковано в 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

2
Але git робить магазин тимчасові мітки і т.д. Це просто не встановлює тимчасові мітки за умовчанням. Просто подивіться на результатиgit ls-files --debug
Росс Сміт II

9
@RossSmithII: git ls-filesпрацює з робочим каталогом та індексом, тому це не означає, що він насправді зберігає цю інформацію в репо. Якби воно зберігалося, отримання (та застосування) mtime було б тривіальним.
MestreLion

13
"Обгрунтування Лінуса тимчасових позначок шкідливим лише тому, що воно" збиває з пантелику "кульгає" - погодився на 100%, DCVS не повинен знати або дбати про код, який він містить! Це знову показує підводні камені спроб перепрофілювати інструменти, написані для конкретних випадків використання, у загальні випадки використання. Меркуріал є і завжди буде вищим вибором, оскільки він був розроблений, а не еволюціонував.
Ян Кемп

6
@davec Ласкаво просимо, раді, що це було корисно. Повна версія на сайті github.com/MestreLion/git-tools вже обробляє Windows, Python 3, імена шляхів, що не належать до ASCII, тощо. Наведений вище сценарій - лише робочий доказ концепції, уникайте його для виробничого використання.
MestreLion

3
Ваші аргументи слушні. Я сподіваюся, хтось із певним впливом зробить запит на вдосконалення для git, щоб він запропонував ваш варіант --with-timestamps.
weberjn

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-sgit show
коміту

1
@ DanielS.Sterling: %aiє дата , автор, ISO 8601 , як формат для суворого використання ISO8601 %aI: git-scm.com/docs/git-show
Elan Ruusamäe

4

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

Це рішення зберігає mtimes у файлі .mtimes у сховищі git, відповідно оновлює їх у комітах (jsut вибірково mtimes інсценізованих файлів) та застосовує при оформленні замовлення. Це працює навіть з версіями git git cygwin / mingw (але вам може знадобитися скопіювати деякі файли зі стандартного cygwin в папку git)

Рішення складається з 3 файлів:

  1. mtimestore - основний скрипт, що надає 3 опції -a (зберегти все - для ініціалізації у вже існуючому репо (працює з git-versed-файлами)), -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-extention, деяку застарілу інформацію можна знайти за адресою http://www.ktweb.cz/blog/index.php?page=page&id=116

// редагування - версія c ++ оновлена:

  • Тепер версія c ++ підтримує алфавітний порядок -> менше конфліктів злиття.
  • Позбувся потворних викликів system ().
  • Видалено $ git update-index --refresh $ з гачка після оформлення замовлення. Викликає деякі проблеми з ревертом під черепаховим 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

Йому потрібен ще один останній рядок: git update-index --refresh // Інструменти графічного інтерфейсу можуть покладатися на індекс і показувати статус "брудного" усьому файлу після такої операції. А саме, що трапляється в TortoiseGit для Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch '

1
І дякую за сценарій. Я хотів би, щоб такий сценарій був частиною стандартного інсталятора Git. Не те щоб мені це потрібно особисто, але члени команди просто відчувають, що мітка часу переосмислюється як червоний банер "зупинки" при прийнятті VCS.
Аріох '

3

Це рішення має працювати досить швидко. Він встановлює час для комітера та час для автора. Він не використовує ніяких модулів, тому повинен бути досить портативним.

#!/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, що змінилося , тому він проходить усі файли за один прохід, а не викликає git для кожного файлу.


0

Я працюю над проектом, де тримати клон мого сховища для використання з rsyncрозгортаннями на базі. Я використовую гілки для націлювання на різні середовища таgit checkout спричиняю зміни модифікацій файлу.

Дізнавшись, що git не забезпечує спосіб перевірки файлів та збереження міток часу, я наткнувся на команду git log --format=format:%ai --name-only .в іншому запитанні SO: Перелічіть дати останнього коміту для великої кількості файлів, швидко .

Зараз я використовую наступний сценарій для touchсвоїх файлів проекту та каталогів, щоб моє розгортання зrsync було легше відрізняти:

<?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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.