Apa yang setara dengan use-commit-times untuk git?

97

Saya memerlukan cap waktu file di lokal saya dan di server saya untuk disinkronkan. Ini dilakukan dengan Subversion dengan menyetel use-commit-times = true dalam konfigurasi sehingga modifikasi terakhir dari setiap file adalah saat itu dilakukan.

Setiap kali saya mengkloning repositori saya, saya ingin stempel waktu file mencerminkan kapan terakhir kali diubah di repositori jarak jauh, bukan ketika saya mengkloning repo.

Apakah ada cara untuk melakukan ini dengan git?

Ben W.
sumber
Sebagai bagian dari proses penerapan saya, saya mengunggah aset (gambar, file javascript, dan file css) ke CDN. Setiap nama file ditambahkan dengan stempel waktu yang terakhir diubah. Sangat penting bagi saya untuk tidak mengakhiri semua aset saya setiap kali saya menerapkan. (Efek samping lain dari use-commit-times adalah saya dapat melakukan proses ini di lokal saya dan mengetahui server saya akan merujuk ke file yang sama, tetapi itu tidak terlalu penting.) Jika alih-alih melakukan git clone, saya melakukan a git fetch diikuti dengan git reset --hard dari remote repo saya, yang akan berfungsi untuk satu server, tetapi tidak untuk beberapa server karena cap waktu pada masing-masing server akan berbeda.
Ben W
@BenW: git annexmungkin berguna untuk melacak gambar
jfs
Anda dapat memeriksa apa yang berubah dengan memeriksa id. Anda mencoba membuat stempel waktu sistem file memiliki arti yang sama dengan stempel waktu vcs. Itu tidak berarti hal yang sama.
sampai

Jawaban:

25

Saya tidak yakin ini sesuai untuk DVCS (seperti dalam VCS "Terdistribusi")

Diskusi besar telah terjadi pada tahun 2007 (lihat utas ini)

Dan beberapa dari jawaban Linus tidak terlalu tertarik pada ide tersebut. Ini salah satu contohnya:

Maafkan saya. Jika Anda tidak melihat betapa SALAH untuk menyetel stempel tanggal kembali ke sesuatu yang akan membuat "membuat" salah mengkompilasi pohon sumber Anda, saya tidak tahu apa definisi "salah" yang Anda bicarakan.
SALAH.
BODOH.
Dan itu benar-benar TIDAK MUDAH untuk diterapkan.


(Catatan: peningkatan kecil: setelah pembayaran, stempel waktu dari file terbaru tidak lagi diubah (Git 2.2.2+, Januari 2015): "git checkout - bagaimana cara mempertahankan stempel waktu saat berpindah cabang?" .)


Jawaban panjangnya adalah:

Saya pikir Anda lebih baik menggunakan banyak repositori saja, jika ini adalah sesuatu yang umum.

Mengacaukan dengan cap waktu tidak akan berfungsi secara umum. Ini hanya akan menjamin Anda bahwa "membuat" menjadi bingung dengan cara yang sangat buruk, dan tidak cukup mengkompilasi ulang daripada terlalu banyak mengkompilasi ulang .

Git memang memungkinkan untuk melakukan "periksa cabang lain" dengan sangat mudah, dengan berbagai cara.

Anda dapat membuat beberapa skrip sepele yang melakukan salah satu hal berikut (mulai dari yang sepele hingga yang lebih eksotis):

  • buat saja repo baru:
    git clone old new
    cd new
    git checkout origin/<branch>

dan itu dia. Stempel waktu lama baik-baik saja di repo lama Anda, dan Anda dapat bekerja (dan mengkompilasi) di yang baru, tanpa mempengaruhi yang lama sama sekali.

Gunakan tanda "-n -l -s" untuk "git clone" untuk membuat ini instan. Untuk banyak file (mis. Repo besar seperti kernel), ini tidak akan secepat hanya berpindah cabang, tetapi memiliki salinan kedua dari pohon kerja bisa sangat berguna.

  • lakukan hal yang sama hanya dengan tar-ball, jika Anda mau
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

yang sangat cepat, jika Anda hanya ingin mengambil foto.

  • biasakan " git show", dan lihat saja file satu per satu.
    Ini sebenarnya sangat berguna di kali. Lakukan saja
    git show otherbranch:filename

di satu jendela xterm, dan lihat file yang sama di cabang Anda saat ini di jendela lain. Secara khusus, hal ini sepele untuk dilakukan dengan editor skrip (yaitu GNU emacs), di mana pada dasarnya memungkinkan untuk memiliki "mode dired" secara keseluruhan untuk cabang lain di dalam editor, menggunakan ini. Yang saya tahu, emacs git mode sudah menawarkan sesuatu seperti ini (Saya bukan pengguna emacs)

  • dan dalam contoh ekstrem dari hal "direktori virtual" itu, setidaknya ada seseorang yang mengerjakan plugin git untuk FUSE, yaitu Anda benar-benar dapat menampilkan direktori virtual semua cabang Anda.

dan saya yakin salah satu di atas adalah alternatif yang lebih baik daripada bermain game dengan stempel waktu file.

Linus

VonC
sumber
5
Sepakat. Anda tidak boleh mengacaukan DVCS dengan sistem distribusi. gitadalah DVCS, untuk memanipulasi kode sumber yang akan dimasukkan ke dalam produk akhir Anda. Jika Anda menginginkan sistem distribusi, Anda tahu di mana menemukannya rsync.
Randal Schwartz
14
Hm, saya harus percaya argumennya bahwa itu tidak layak. Apakah itu salah atau bodoh, itu soal lain. Saya membuat versi file saya menggunakan stempel waktu dan mengunggahnya ke CDN, oleh karena itu penting agar stempel waktu mencerminkan saat file benar-benar diubah, bukan saat terakhir kali ditarik dari repo.
Ben W
3
@ Ben W: "Jawaban Linus" tidak di sini untuk mengatakan itu salah dalam situasi khusus Anda . Ini hanya sebagai pengingat bahwa DVCS tidak cocok untuk fitur semacam itu (menjaga stempel waktu).
VonC
15
@VonC: Karena DVCS modern lainnya seperti Bazaar dan Mercurial menangani stempel waktu dengan baik, saya lebih suka mengatakan bahwa " git tidak cocok untuk fitur semacam itu". Jika "sebuah" DVCS harus memiliki fitur itu masih bisa diperdebatkan (dan saya sangat yakin mereka melakukannya).
MestreLion
10
Ini bukan jawaban atas pertanyaan, tetapi diskusi filosofis tentang manfaat melakukan ini dalam sistem kontrol versi. Jika orang tersebut akan menyukainya, mereka akan bertanya, "Apa alasan git tidak menggunakan waktu commit untuk waktu modifikasi file?"
thomasfuchs
85

Namun, jika Anda benar - benar ingin menggunakan waktu komit untuk stempel waktu saat check out, coba gunakan skrip ini dan letakkan (sebagai dapat dieksekusi) di file $ 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

Namun perlu dicatat, bahwa skrip ini akan menyebabkan penundaan yang cukup besar untuk memeriksa repositori besar (di mana besar berarti file dalam jumlah besar, bukan ukuran file besar).

Giel
sumber
55
+1 untuk jawaban yang sebenarnya, daripada hanya mengatakan "Jangan lakukan itu"
DanC
4
| head -n 1harus dihindari karena memunculkan proses baru, -n 1karena git rev-listdan git logdapat digunakan sebagai gantinya.
eregon
3
Lebih baik TIDAK membaca baris dengan `...`dan for; lihat Mengapa Anda tidak membaca baris dengan "untuk" . Saya akan pergi untuk git ls-files -zdan while IFS= read -r -d ''.
musiphil
2
Apakah versi Windows mungkin?
Ehryk
2
daripada git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1yang bisa Anda lakukan git show --pretty=format:%ai -s "$(get_file_rev "$1")", itu menyebabkan lebih sedikit data yang dihasilkan oleh showperintah dan seharusnya mengurangi overhead.
Scott Chamberlain
80

PEMBARUAN : Solusi saya sekarang dikemas ke dalam Debian / Ubuntu / Mint, Fedora, Gentoo dan mungkin distro lain:

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, tidak menyimpan cap waktu (dan metadata lain seperti izin dan kepemilikan) adalah batasan besargit .

Alasan Linus tentang stempel waktu yang berbahaya hanya karena "membingungkan make" adalah timpang :

  • make clean cukup untuk memperbaiki masalah apa pun.

  • Berlaku hanya untuk project yang menggunakan make, kebanyakan C / C ++. Ini benar-benar diperdebatkan untuk skrip seperti Python, Perl, atau dokumentasi secara umum.

  • Hanya ada salahnya jika Anda menerapkan cap waktu. Tidak ada salahnya menyimpannya dalam repo. Menerapkannya bisa menjadi --with-timestampsopsi sederhana untuk git checkoutdan teman ( clone, pulldll), sesuai kebijaksanaan pengguna .

Baik Bazaar dan Mercurial menyimpan metadata. Pengguna dapat menerapkannya atau tidak saat check out. Tapi di git, karena stempel waktu asli bahkan tidak tersedia di repo, tidak ada opsi seperti itu.

Jadi, untuk keuntungan yang sangat kecil (tidak harus mengkompilasi ulang semuanya) yang spesifik untuk subset proyek, gitkarena DVCS umum lumpuh , beberapa informasi dari file hilang , dan, seperti yang dikatakan Linus, TIDAK MUDAH dilakukan sekarang. Sedih .

Karena itu, bolehkah saya menawarkan 2 pendekatan?

1 - http://repo.or.cz/w/metastore.git , oleh David Härdeman. Mencoba melakukan apa yang git seharusnya dilakukan di tempat pertama : menyimpan metadata (tidak hanya stempel waktu) di repo saat melakukan (melalui hook pre-commit), dan menerapkannya kembali saat menarik (juga melalui hook).

2 - Versi sederhana saya dari skrip yang saya gunakan sebelumnya untuk menghasilkan rilis tarball. Seperti yang disebutkan dalam jawaban lain, pendekatannya sedikit berbeda : untuk menerapkan stempel waktu setiap file dari komit terbaru tempat file dimodifikasi untuk setiap file .

  • git-restore-mtime , dengan banyak opsi, mendukung tata letak repositori apa pun, dan berjalan di Python 3.

Di bawah ini adalah benar-benar telanjang-tulang versi script, sebagai bukti-of-konsep, di Python 2.7. Untuk penggunaan sebenarnya, saya sangat merekomendasikan versi lengkap di atas:

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

Performanya cukup mengesankan, bahkan untuk proyek monster wine, gitatau bahkan kernel 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
sumber
2
Tapi git apakah menyimpan cap waktu, dll. Itu hanya tidak mengatur cap waktu secara default. Lihat saja output darigit ls-files --debug
Ross Smith II
9
@RossSmithII: git ls-filesberoperasi pada direktori dan indeks kerja, jadi itu tidak berarti itu benar-benar menyimpan info itu di repo. Jika itu menyimpan, mengambil (dan menerapkan) waktu akan menjadi hal yang sepele.
MestreLion
13
"Alasan Linus tentang stempel waktu berbahaya hanya karena" membuat bingung "adalah timpang" - 100% setuju, DCVS seharusnya tidak tahu atau tidak peduli dengan kode yang dikandungnya! Sekali lagi, ini menunjukkan kesulitan dalam mencoba menggunakan kembali alat yang ditulis untuk kasus penggunaan tertentu ke dalam kasus penggunaan umum. Mercurial adalah dan akan selalu menjadi pilihan yang unggul karena dirancang, bukan berevolusi.
Ian Kemp
6
@davec Sama-sama, senang bermanfaat. Versi lengkap di github.com/MestreLion/git-tools sudah menangani Windows, Python 3, nama jalur non-ASCII, dll. Skrip di atas hanyalah bukti konsep yang berfungsi, hindari untuk penggunaan produksi.
MestreLion
3
Argumen Anda valid. Saya berharap seseorang yang memiliki pengaruh akan membuat permintaan tambahan untuk git agar memiliki opsi --with-timestamps yang Anda sarankan.
weberjn
12

Saya mengambil jawaban Giel dan alih-alih menggunakan skrip hook pasca-komit, saya mengerjakannya ke dalam skrip penerapan khusus saya.

Pembaruan : Saya juga menghapus satu | head -nsaran @ eregon berikut, dan menambahkan dukungan untuk file dengan spasi di dalamnya:

# 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
sumber
Terima kasih Daniel, sangat membantu untuk mengetahui
Alex Dean
yang --abbrev-commitadalah berlebihan dalam git showperintah karena --pretty=format:%aisedang digunakan (komit hash bukan bagian dari output) dan | head -n 1bisa diganti dengan menggunakan -sbendera untukgit show
Elan Ruusamäe
1
@ DanielS.Sterling: %aiadalah tanggal penulis, format seperti ISO 8601 , untuk penggunaan ketat iso8601 %aI: git-scm.com/docs/git-show
Elan Ruusamäe
4

kami terpaksa menemukan solusi lain, karena kami memerlukan waktu modifikasi khusus dan bukan waktu commit, dan solusinya juga harus portabel (yaitu membuat python bekerja di instalasi git windows sebenarnya bukan tugas yang mudah) dan cepat. Ini mirip dengan solusi David Hardeman, yang saya putuskan untuk tidak digunakan karena kurangnya dokumentasi (dari repositori saya tidak bisa mendapatkan ide apa sebenarnya yang dilakukan kodenya).

Solusi ini menyimpan mtimes dalam file .mtimes di repositori git, memperbaruinya sesuai saat melakukan (hanya memilih mtimes file bertahap) dan menerapkannya saat checkout. Ia bekerja bahkan dengan versi cygwin / mingw dari git (tetapi Anda mungkin perlu menyalin beberapa file dari cygwin standar ke dalam folder git)

Solusinya terdiri dari 3 file:

  1. mtimestore - skrip inti menyediakan 3 opsi -a (simpan semua - untuk inisialisasi di repo yang sudah ada (bekerja dengan file git-versed)), -s (untuk menyimpan perubahan bertahap), dan -r untuk memulihkannya. Ini sebenarnya hadir dalam 2 versi - satu bash (portabel, bagus, mudah dibaca / dimodifikasi), dan versi c (yang berantakan tapi cepat, karena mingw bash sangat lambat sehingga mustahil untuk menggunakan solusi bash pada proyek besar).
  2. hook pra-komit
  3. pengait pasca-checkout

pra-komitmen:

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

pasca-pembayaran

#!/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;
}
  • perhatikan bahwa kait dapat ditempatkan ke direktori template untuk mengotomatiskan penempatannya

info lebih lanjut dapat ditemukan di sini https://github.com/kareltucek/git-mtime-extension beberapa informasi yang sudah ketinggalan zaman ada di http://www.ktweb.cz/blog/index.php?page=page&id=116

// edit - versi c ++ diperbarui:

  • Sekarang versi c ++ mempertahankan pengurutan menurut abjad -> lebih sedikit konflik penggabungan.
  • Singkirkan panggilan system () yang jelek.
  • Menghapus $ git update-index --refresh $ dari hook pasca-checkout. Menyebabkan beberapa masalah dengan pengembalian under tortoise git, dan tampaknya tidak terlalu penting.
  • Paket windows kami dapat diunduh di http://ktweb.cz/blog/download/git-mtimestore-1.4.rar

// edit lihat github untuk versi terbaru

Karel Tucek
sumber
1
Perhatikan bahwa setelah pembayaran, stempel waktu file terbaru tidak lagi diubah (Git 2.2.2+, Januari 2015): stackoverflow.com/a/28256177/6309
VonC
3

Skrip berikut menggabungkan saran -n 1dan HEAD, berfungsi di sebagian besar lingkungan non-Linux (seperti Cygwin), dan dapat dijalankan saat checkout:

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

Dengan asumsi Anda menamai skrip di atas /path/to/templates/hooks/post-checkoutdan / atau /path/to/templates/hooks/post-update, Anda dapat menjalankannya pada repositori yang ada melalui:

git clone git://path/to/repository.git
cd repository
/path/to/templates/hooks/post-checkout
Ross Smith II
sumber
Diperlukan satu baris terakhir: git update-index --refresh // Alat GUI mungkin mengandalkan indeks dan menampilkan status "kotor" ke semua file setelah operasi tersebut. Yaitu yang terjadi di TortoiseGit untuk Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The
1
Dan terima kasih untuk naskahnya. Saya berharap skrip seperti itu adalah bagian dari penginstal standar Git. Bukannya saya membutuhkannya secara pribadi, tetapi anggota tim hanya merasakan stempel waktu yang direfleksikan sebagai spanduk "berhenti" merah dalam adopsi VCS.
Arioch 'The
3

Solusi ini akan berjalan cukup cepat. Ini menetapkan waktu ke waktu pengubah dan waktu ke waktu pengarang. Ini tidak menggunakan modul jadi harus cukup portabel.

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

Berikut adalah versi yang dioptimalkan dari solusi shell di atas, dengan perbaikan kecil:

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

Berikut adalah metode dengan 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);
}

Ini mirip dengan jawaban di sini:

Apa yang setara dengan use-commit-times untuk git?

itu membangun daftar file seperti jawaban itu, tetapi membangun dari git ls-files alih-alih hanya mencari di direktori kerja. Ini memecahkan masalah pengecualian .git, dan juga memecahkan masalah file yang tidak terlacak. Juga, jawaban itu gagal jika komit terakhir dari file adalah komit gabungan, yang saya selesaikan git log -m. Seperti jawaban lainnya, akan berhenti setelah semua file ditemukan, jadi tidak perlu membaca semua komit. Misalnya dengan:

https://github.com/git/git

pada posting ini hanya membaca 292 komit. Juga mengabaikan file lama dari sejarah sesuai kebutuhan, dan tidak akan menyentuh file yang telah disentuh. Akhirnya tampaknya menjadi sedikit lebih cepat daripada solusi lainnya. Hasil dengan git/gitrepo:

PS C:\git> Measure-Command { git-touch.php }
TotalSeconds      : 3.4215134
Steven Penny
sumber
0

Saya melihat beberapa permintaan untuk versi Windows, jadi ini dia. Buat dua file berikut:

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

Ini menggunakan git whatchanged , sehingga berjalan melalui semua file dalam satu lintasan alih-alih memanggil git untuk setiap file.

Brain2000
sumber