Jaki jest odpowiednik czasów zatwierdzania użycia dla git?

97

Potrzebuję sygnatur czasowych plików na moim serwerze lokalnym i na serwerze, aby były zsynchronizowane. Osiąga się to w Subversion, ustawiając w konfiguracji use-commit-times = true, tak aby ostatnia modyfikacja każdego pliku miała miejsce, gdy został zatwierdzony.

Za każdym razem, gdy klonuję repozytorium, chcę, aby sygnatury czasowe plików odzwierciedlały datę ich ostatniej zmiany w repozytorium zdalnym, a nie datę sklonowania repozytorium.

Czy jest jakiś sposób, aby to zrobić za pomocą git?

Ben W.
źródło
W ramach procesu wdrażania przesyłam zasoby (obrazy, pliki javascript i pliki css) do CDN. Do każdej nazwy pliku dołączana jest ostatnia zmodyfikowana sygnatura czasowa. Ważne jest, aby nie tracić ważności wszystkich zasobów za każdym razem, gdy wdrażam. (Innym efektem ubocznym czasu zatwierdzenia użycia jest to, że mogę wykonać ten proces na moim lokalnym i wiem, że mój serwer będzie odnosił się do tych samych plików, ale to nie jest tak ważne.) Jeśli zamiast wykonać klon git, zrobiłem git fetch, po którym następuje reset git - hard z mojego zdalnego repozytorium, który działałby na jednym serwerze, ale nie na wielu serwerach, ponieważ sygnatury czasowe na każdym byłyby różne.
Ben W
@BenW: git annexmoże być przydatne do śledzenia obrazów
jfs
Możesz sprawdzić, co się zmieniło, sprawdzając identyfikatory. Próbujesz sprawić, by znaczniki czasu systemu plików miały to samo znaczenie, co znaczniki czasu vcs. Nie mają na myśli tego samego.
jthill

Odpowiedzi:

25

Nie jestem pewien, czy będzie to odpowiednie dla DVCS (jak w „Distributed” VCS)

Ogromna dyskusja odbyła się już w 2007 roku (zobacz ten wątek)

Niektórzy z odpowiedzi Linusa nie byli zachwyceni tym pomysłem. Oto jedna próbka:

Przykro mi. Jeśli nie zobaczyć, jak to jest złe ustawienie DATESTAMP plecami do czegoś, co zrobić prosty „make” miscompile drzewie źródłowym, ja nie wiem, co defintiion z „źle”, o której mówisz.
To jest źle.
To jest głupie.
I jest to całkowicie NIEPRZEWIDZIANE do wdrożenia.


(Uwaga: niewielka poprawa: po wyewidencjonowaniu sygnatury czasowe aktualnych plików nie są już modyfikowane (Git 2.2.2+, styczeń 2015): „git checkout - jak mogę zachować znaczniki czasu podczas przełączania gałęzi?” ).


Długa odpowiedź brzmiała:

Myślę, że lepiej jest, jeśli zamiast tego używasz wielu repozytoriów, jeśli jest to coś powszechnego.

Majstrowanie ze znacznikami czasu na ogół nie zadziała. To tylko zagwarantuje ci, że "make" zostanie źle zdezorientowany i nie dokompiluje się wystarczająco ponownie , zamiast zbyt dużo .

Git umożliwia bardzo łatwe „sprawdzenie drugiej gałęzi” na wiele różnych sposobów.

Możesz stworzyć trywialny skrypt, który wykonuje dowolną z następujących czynności (od trywialnych po bardziej egzotyczne):

  • po prostu utwórz nowe repozytorium:
    git clone old new
    cd new
    git checkout origin/<branch>

i tu jesteś. Stare sygnatury czasowe są w porządku w starym repozytorium i możesz pracować (i kompilować) w nowym, bez wpływu na stare.

Użyj flag „-n -l -s” do „git clone”, aby w zasadzie uczynić to natychmiastowym. W przypadku wielu plików (np. Dużych repozytoriów, takich jak jądro), nie będzie to tak szybkie, jak zwykłe przełączanie gałęzi, ale druga kopia drzewa roboczego może być dość potężna.

  • jeśli chcesz, zrób to samo z kulką tarową
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

co jest naprawdę dość szybkie, jeśli chcesz tylko migawki.

  • przyzwyczaić się do „ git show” i po prostu spojrzeć na poszczególne pliki. Czasami
    jest to naprawdę przydatne. Po prostu to robisz
    git show otherbranch:filename

w jednym oknie xterm i spójrz na ten sam plik w bieżącej gałęzi w innym oknie. W szczególności powinno to być trywialne w przypadku edytorów skryptowych (np. GNU emacs), gdzie powinno być możliwe posiadanie całego "trybu dired" dla innych gałęzi w edytorze, używając tego. Z tego co wiem, tryb git emacsa już oferuje coś takiego (nie jestem użytkownikiem emacsa)

  • aw skrajnym przykładzie tego „wirtualnego katalogu” przynajmniej ktoś pracował nad wtyczką git dla FUSE, tj. można było po prostu mieć wirtualne katalogi pokazujące wszystkie twoje gałęzie.

i jestem pewien, że każda z powyższych opcji jest lepszą alternatywą niż granie w gry z sygnaturami czasowymi plików.

Linus

VonC
źródło
5
Zgoda. Nie należy mylić DVCS z systemem dystrybucji. gitto DVCS do manipulowania kodem źródłowym, który zostanie wbudowany w produkt końcowy. Jeśli potrzebujesz systemu dystrybucji, wiesz, gdzie go znaleźć rsync.
Randal Schwartz
14
Hm, będę musiał zaufać jego argumentowi, że jest to niewykonalne. Ale czy to źle, czy głupio, to inna sprawa. Wersję moje pliki używam znacznika czasu i przesyłam je do CDN, dlatego ważne jest, aby sygnatury czasowe odzwierciedlały, kiedy plik został faktycznie zmodyfikowany, a nie kiedy został ostatnio ściągnięty z repozytorium.
Ben W
3
@Ben W: „Odpowiedź Linusa” nie jest tutaj, aby powiedzieć, że jest ona niewłaściwa w twojej konkretnej sytuacji. Jest tylko dla przypomnienia, że ​​DVCS nie jest dobrze przystosowany do tego rodzaju funkcji (zachowywanie znacznika czasu).
VonC
15
@VonC: Ponieważ inne nowoczesne DVCS, takie jak Bazaar i Mercurial, radzą sobie dobrze ze znacznikami czasu, wolałbym powiedzieć, że „ git nie nadaje się do tego rodzaju funkcji”. Jeśli „a” DVCS powinno mieć to cecha jest dyskusyjna (i zdecydowanie myśleć robią).
MestreLion
10
To nie jest odpowiedź na pytanie, ale filozoficzna dyskusja na temat zalet zrobienia tego w systemie kontroli wersji. Gdyby osobie to się spodobało, spytałaby: „Jaki jest powód, dla którego git nie używa czasu zatwierdzenia dla czasu modyfikacji plików?”
thomasfuchs
85

Jeśli jednak naprawdę chcesz użyć czasu zatwierdzenia dla znaczników czasu podczas sprawdzania, spróbuj użyć tego skryptu i umieść go (jako plik wykonywalny) w pliku $ 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

Należy jednak pamiętać, że ten skrypt spowoduje dość duże opóźnienie przy sprawdzaniu dużych repozytoriów (gdzie duże oznacza dużą liczbę plików, a nie duże rozmiary plików).

Giel
źródło
55
+1 dla rzeczywistej odpowiedzi, zamiast po prostu powiedzieć „Nie rób tego”
DanC,
4
| head -n 1Należy unikać jak ikra nowy proces, -n 1za git rev-listi git logmoże być stosowany zamiast.
eregon
3
Lepiej NIE czytać wierszy z `...`i for; zobacz Dlaczego nie czytasz wierszy z „dla” . Poszedłbym na git ls-files -zi while IFS= read -r -d ''.
musiphil
2
Czy możliwa jest wersja dla systemu Windows?
Ehryk
2
zamiast tego git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1możesz to zrobić git show --pretty=format:%ai -s "$(get_file_rev "$1")", powoduje generowanie znacznie mniej danych przez showpolecenie i powinno zmniejszyć narzut.
Scott Chamberlain
80

AKTUALIZACJA : Moje rozwiązanie jest teraz spakowane w dystrybucji Debian / Ubuntu / Mint, Fedora, Gentoo i prawdopodobnie innych:

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, nieprzechowywanie sygnatur czasowych (i innych metadanych, takich jak uprawnienia i własność), jest dużym ograniczeniem git.

Rozumowanie Linusa, że ​​sygnatury czasowe są szkodliwe tylko dlatego, że „dezorientuje make”, jest kiepskie :

  • make clean wystarczy, aby naprawić wszelkie problemy.

  • Dotyczy tylko projektów, które używają make, głównie C / C ++. Jest to całkowicie dyskusyjne w przypadku skryptów takich jak Python, Perl lub ogólnie dokumentacji.

  • Szkoda jest tylko wtedy, gdy zastosujesz znaczniki czasu. Przechowywanie ich w repozytorium nie byłoby szkodliwe . Ich stosowania może być prostym --with-timestampsrozwiązaniem dla git checkouti przyjaciele ( clone, pulletc), na użytkownika uznania.

Zarówno Bazaar, jak i Mercurial przechowują metadane. Użytkownicy mogą je zastosować lub nie podczas płatności. Ale w git, ponieważ oryginalne znaczniki czasu nie są nawet dostępne w repozytorium, nie ma takiej opcji.

Tak więc, dla bardzo małego zysku (brak konieczności ponownej kompilacji wszystkiego), który jest charakterystyczny dla podzbioru projektów, gitponieważ ogólny DVCS został uszkodzony , niektóre informacje z plików są tracone i, jak powiedział Linus, jest to NIEDOPROGRAMOWANE. teraz. Smutne .

To powiedziawszy, czy mogę zaoferować 2 podejścia?

1 - http://repo.or.cz/w/metastore.git , autor: David Härdeman. Próbuje zrobić to, co git powinno było zrobić w pierwszej kolejności : przechowuje metadane (nie tylko znaczniki czasu) w repozytorium podczas zatwierdzania (za pomocą haka przed zatwierdzeniem) i ponownie je stosuje podczas ściągania (również za pomocą haków).

2 - Moja skromna wersja skryptu, którego użyłem wcześniej do generowania paczek z wydaniami. Jak wspomniano w innych odpowiedzi, podejście jest trochę inaczej : do zastosowania dla każdego akt datownik z najnowszym popełnienia gdzie plik został zmodyfikowany.

  • git-restore-mtime , z wieloma opcjami, obsługuje dowolny układ repozytorium i działa w Pythonie 3.

Poniżej znajduje się naprawdę prosta wersja skryptu, jako dowód słuszności koncepcji, w Pythonie 2.7. Do rzeczywistego użytku zdecydowanie polecam pełną wersję powyżej:

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

Wydajność jest imponująca, nawet w przypadku potwornych projektów wine, gita nawet jądra Linuksa:

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
źródło
2
Ale git nie przechowywać znaczniki czasu itd To po prostu nie ustawia timestampów domyślnie. Wystarczy spojrzeć na wynikgit ls-files --debug
Ross Smith II
9
@RossSmithII: git ls-filesdziała na katalogu roboczym i indeksie, więc nie oznacza to, że faktycznie przechowuje te informacje w repozytorium. Gdyby przechował, pobranie (i zastosowanie) mtime byłoby trywialne.
MestreLion
13
„Racjonalne uzasadnienie Linusa, że ​​znaczniki czasu są szkodliwe tylko dlatego, że„ dezorientuje ”sprawia, że ​​jest kiepskie” - zgodził się w 100%, DCVS nie powinien wiedzieć ani przejmować się zawartym w nim kodem! Ponownie pokazuje to pułapki związane z próbą zmiany przeznaczenia narzędzi napisanych dla konkretnych przypadków użycia na ogólne przypadki użycia. Mercurial jest i zawsze będzie najlepszym wyborem, ponieważ został zaprojektowany, a nie ewoluował.
Ian Kemp
6
@davec Nie ma za co, cieszę się, że to było przydatne. Pełna wersja na github.com/MestreLion/git-tools już obsługuje Windows, Python 3, ścieżki inne niż ASCII, itp. Powyższy skrypt jest tylko działającym dowodem koncepcji, unikaj go do użytku produkcyjnego.
MestreLion
3
Twoje argumenty są ważne. Mam nadzieję, że ktoś, kto ma jakąś siłę przebicia, poprosiłby git o ulepszenie, aby zaproponował opcję --with-timestamps.
weberjn
12

Wziąłem odpowiedź Giela i zamiast używać skryptu przechwytującego po zatwierdzeniu, umieściłem ją w moim niestandardowym skrypcie wdrażania.

Aktualizacja : usunąłem również jedną | head -nsugestię @ eregon i dodałem obsługę plików ze spacjami:

# 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
Alex Dean
źródło
Dzięki, Daniel, warto wiedzieć
Alex Dean,
--abbrev-commitjest zbyteczne git showpolecenia z powodu --pretty=format:%aijest stosowany (popełnienia mieszania nie jest częścią wyjściu) i | head -n 1może być zastąpiona za pomocą -sflag, abygit show
Elan Ruusamäe
1
@ DanielS.Sterling: %aito data autora, format podobny do ISO 8601 , dla ścisłego użycia ISO8601%aI : git-scm.com/docs/git-show
Elan Ruusamäe
4

byliśmy zmuszeni wymyślić jeszcze jedno rozwiązanie, ponieważ potrzebowaliśmy konkretnie czasu modyfikacji, a nie czasu zatwierdzenia, a także rozwiązanie musiało być przenośne (tj. sprawienie, aby Python działał w instalacjach git systemu Windows naprawdę nie jest prostym zadaniem) i szybkie. Przypomina rozwiązanie Davida Hardemana, z którego zrezygnowałem ze względu na brak dokumentacji (z repozytorium nie mogłem się zorientować, co dokładnie robi jego kod).

To rozwiązanie przechowuje mtimes w pliku .mtimes w repozytorium git, aktualizuje je odpowiednio przy zatwierdzeniach (jsut selektywnie mtimes plików pomostowych) i stosuje je przy pobieraniu. Działa nawet z wersjami git cygwin / mingw (ale może być konieczne skopiowanie niektórych plików ze standardowego cygwin do folderu git)

Rozwiązanie składa się z 3 plików:

  1. mtimestore - rdzeń skryptu udostępniający 3 opcje -a (zapisz wszystko - do inicjalizacji w już istniejącym repozytorium (działa z plikami w wersji git)), -s (aby zapisać zmiany w fazie) i -r, aby je przywrócić. To faktycznie występuje w 2 wersjach - bash (przenośna, ładna, łatwa do odczytania / modyfikacji) i wersja c (niechlujna, ale szybka, ponieważ mingw bash jest strasznie powolny, co uniemożliwia użycie rozwiązania bash w dużych projektach).
  2. hak przed zatwierdzeniem
  3. hak po kasie

wstępne zobowiązanie:

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

po wymeldowaniu

#!/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;
}
  • zwróć uwagę, że hooki można umieścić w katalogu szablonów, aby zautomatyzować ich umieszczanie

więcej informacji można znaleźć tutaj https://github.com/kareltucek/git-mtime-extension niektóre nieaktualne informacje są dostępne pod adresem http://www.ktweb.cz/blog/index.php?page=page&id=116

// edit - aktualizacja wersji c ++:

  • Teraz wersja c ++ zachowuje porządek alfabetyczny -> mniej konfliktów przy scalaniu.
  • Pozbyłem się brzydkich wywołań system ().
  • Usunięto $ git update-index --refresh $ z hooka post-checkout. Powoduje pewne problemy z przywracaniem pod wpływem żółwia, i tak nie wydaje się być zbyt ważne.
  • Nasz pakiet Windows można pobrać ze strony http://ktweb.cz/blog/download/git-mtimestore-1.4.rar

// edytuj zobacz github, aby uzyskać aktualną wersję

Karel Tucek
źródło
1
Zwróć uwagę, że po zakończeniu płatności sygnatury czasowe aktualnych plików nie są już modyfikowane (Git 2.2.2+, styczeń 2015): stackoverflow.com/a/28256177/6309
VonC
3

Poniższy skrypt zawiera sugestie -n 1i HEAD, działa w większości środowisk innych niż Linux (takich jak Cygwin) i można go uruchomić przy kasie po fakcie:

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

Zakładając, że nazwałeś powyższy skrypt /path/to/templates/hooks/post-checkouti / lub /path/to/templates/hooks/post-updatemożesz uruchomić go na istniejącym repozytorium poprzez:

git clone git://path/to/repository.git
cd repository
/path/to/templates/hooks/post-checkout
Ross Smith II
źródło
Potrzebuje jeszcze jednej ostatniej linii: git update-index --refresh // Narzędzia GUI mogą polegać na indeksie i wyświetlać status "brudny" dla całego pliku po takiej operacji. Mianowicie dzieje się to w TortoiseGit dla Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The
1
Dziękuję za scenariusz. Chciałbym, żeby taki skrypt był częścią standardowego instalatora Gita. Nie to, żebym tego potrzebował osobiście, ale członkowie zespołu po prostu czują, że znacznik czasu pojawia się ponownie jako czerwony baner „stop” w adopcji VCS.
Arioch „The
3

To rozwiązanie powinno działać dość szybko. Ustala momenty na czasy autora i czasy na czasy autora. Nie wykorzystuje modułów, więc powinien być w miarę przenośny.

#!/usr/bin/perl

# git-utimes: update file times to last commit on them
# Tom Christiansen <[email protected]>

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;
tchrist
źródło
2

Oto zoptymalizowana wersja powyższych rozwiązań powłoki, z drobnymi poprawkami:

#!/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
vszakats
źródło
1

Oto metoda z 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);
}

Jest podobna do odpowiedzi tutaj:

Jaki jest odpowiednik czasów zatwierdzania użycia dla git?

tworzy listę plików, taką jak ta odpowiedź, ale buduje na podstawie git ls-files zamiast tylko szukać w katalogu roboczym. To rozwiązuje problem wykluczania .git, a także rozwiązuje problem nieśledzonych plików. Również ta odpowiedź nie powiedzie się, jeśli ostatnim zatwierdzeniem pliku było zatwierdzenie scalające, które rozwiązałem git log -m. Podobnie jak druga odpowiedź, zatrzyma się po znalezieniu wszystkich plików, więc nie musi czytać wszystkich zatwierdzeń. Na przykład z:

https://github.com/git/git

od tego wysłania musiał przeczytać tylko 292 zatwierdzenia. W razie potrzeby ignoruje również stare pliki z historii i nie dotknie pliku, który został już dotknięty. Wreszcie wydaje się, że jest trochę szybszy niż inne rozwiązanie. Wyniki z git/gitrepozytorium:

PS C:\git> Measure-Command { git-touch.php }
TotalSeconds      : 3.4215134
Steven Penny
źródło
0

Widziałem prośby o wersję dla systemu Windows, więc oto one. Utwórz następujące dwa pliki:

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;
    }
}

Używa git whatchanged , więc przechodzi przez wszystkie pliki w jednym przebiegu zamiast wywoływać git dla każdego pliku.

Brain2000
źródło