gitのuse-commit-timesに相当するものは何ですか?


97

ローカルとサーバーのファイルのタイムスタンプを同期させる必要があります。これはSubversionのconfigでuse-commit-times = trueを設定することで実現され、各ファイルの最終変更はコミットされた日時になります。

リポジトリのクローンを作成するたびに、リポジトリのクローンを作成したときではなく、リモートリポジトリでファイルが最後に変更されたときのファイルのタイムスタンプを反映させます。

gitでこれを行う方法はありますか?


デプロイプロセスの一環として、アセット(画像、JavaScriptファイル、CSSファイル)をCDNにアップロードします。各ファイル名には、最後に変更されたタイムスタンプが追加されます。デプロイするたびにすべてのアセットを期限切れにしないことが重要です。(use-commit-timesのもう1つの副作用は、ローカルでこのプロセスを実行でき、サーバーが同じファイルを参照することを知っていることですが、それほど重要ではありません。)git cloneを実行する代わりに、 git fetchに続いてgit reset --hardを私のリモートリポジトリから取得すると、単一のサーバーでは機能しますが、複数のサーバーでは機能しません。それぞれのタイムスタンプが異なるためです。
ベンW

@BenW:git annex画像を追跡するのに役立つかもしれません
jfs

変更点を確認するには、IDを確認します。ファイルシステムのタイムスタンプをvcsのタイムスタンプと同じものにしようとしています。同じことではありません。
jthill

回答:


25

これがDVCSに適しているかどうかはわかりません(「分散」VCSの場合など)。

2007年にはすでに大規模な議論が行われていました(このスレッドを参照)

そして、Linusの回答の一部は、そのアイデアにあまり熱心ではありませんでした。これが1つのサンプルです。

申し訳ありません。日付スタンプをソースツリーの単純な「make」で誤っコンパイルするようなものに戻すのがどのように間違っているのかわからない場合、私はあなたが話している「間違った」の定義を知りません。
それは間違っています。
それは愚かです。
そして、それを実装することは完全に不可能です。


(注:小さな改善:チェックアウトした後、もはや変更された最新のファイルのタイムスタンプのGit 2.2.2+、2015年1月)(:「gitのチェックアウトを-枝を切り替えたとき、私はタイムスタンプを維持できますか?」。)


長い答えは:

これが一般的である場合は、代わりに複数のリポジトリを使用する方がはるかに良いと思います。

タイムスタンプをいじることは、一般的に機能しません。「make」が本当に悪い方法で混乱し、再コンパイルしすぎずに再コンパイルしないことを保証するだけです。

Gitを使用すると、「他のブランチをチェックアウトする」ことが非常に簡単に、さまざまな方法で実行できます。

次のいずれかを実行する簡単なスクリプトを作成できます(簡単なものからよりエキゾチックなものまで)。

  • 新しいリポジトリを作成するだけです:
    git clone old new
    cd new
    git checkout origin/<branch>

そしてあなたはそこにいます。古いタイムスタンプは古いリポジトリでは問題なく、古いタイムスタンプにまったく影響を与えることなく、新しいタイムスタンプで作業(およびコンパイル)できます。

フラグ「-n -l -s」を「git clone」に使用して、これを基本的に瞬時にします。多くのファイル(たとえば、カーネルのような大きなリポジトリ)の場合、ブランチを切り替えるほど高速ではありませんが、作業ツリーの2番目のコピーを作成することは非常に強力です。

  • 代わりに、タールボールだけで同じことを行います
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

スナップショットが必要な場合は、これは非常に高速です。

  • " git show"に慣れ、個々のファイルを見てください。
    これは時々本当に便利です。あなたがするだけ
    git show otherbranch:filename

1つのxtermウィンドウで、別のウィンドウで現在のブランチの同じファイルを確認します。特に、これは、スクリプト可能なエディター(つまり、GNU emacs)を使用する場合は簡単です。これを使用すると、エディター内の他のブランチに対して基本的に「diredモード」全体を使用できるはずです。私が知っていることすべてのために、emacs gitモードはすでにこのようなものを提供しています(私はemacsユーザーではありません)

  • そして、その「仮想ディレクトリ」の極端な例では、少なくとも誰かがFUSEのgitプラグインで作業していました。つまり、文字通りすべてのブランチを表示する仮想ディレクトリを持つことができます。

上記のいずれも、ファイルのタイムスタンプを使用してゲームをプレイするよりも優れた選択肢です。

ライナス


5
同意した。DVCSを配布システムと混同しないでください。最終製品に組み込まれるgitソースコードを操作するためのDVCS です。配布システムが必要な場合は、どこにあるかがわかります。rsync
ランダルシュワルツ

14
ええと、私はそれが実行不可能であるという彼の議論を信頼しなければなりません。それが間違っているか愚かであるかは別の問題です。私はタイムスタンプを使用してファイルにバージョンを付け、CDNにアップロードします。そのため、タイムスタンプは、ファイルが最後にリポジトリからプルダウンされたときではなく、ファイルが実際に変更されたときに反映されることが重要です。
ベンW

3
@ベンW:「Linusの答え」は特定の状況で間違っていると言っているわけではありません。DVCSがその種の機能(タイムスタンプ保持)にあまり適していないことを思い出させるためだけにあります。
VonC、2009

15
@VonC:BazaarやMercurialなどの他の最新のDVCSはタイムスタンプを適切に処理するので、「gitはそのような機能にはあまり適していません」と言いたいです。「a」のDVCSにその機能が必要な場合は、議論の余地があります(私はそう思うと思います)。
MestreLion 2013

10
これは質問に対する答えではありませんが、バージョン管理システムでこれを行うメリットについての哲学的な議論です。その人がそれを気に入ったとしたら、彼らは「gitがファイルの変更時刻にコミット時間を使用しない理由は何ですか?」
thomasfuchs 2014年

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-listgit log代わりに使用することができます。
エレゴン

3
`...`andで行を読み取らない方がよいでしょうfor「for」で行を読み取らない理由をご覧ください。私はのために行くだろうgit ls-files -zwhile IFS= read -r -d ''
musiphil 2013年

2
Windowsバージョンは可能ですか?
Ehryk、2015

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コマンドによって生成されるはるかに少ないデータを引き起こし、オーバーヘッドを減らすべきです。
Scott Chamberlain

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

私見、タイムスタンプ(および権限や所有権などの他のメタデータ)を保存しないことは、の大きな制限ですgit

Linusがタイムスタンプを「混乱させるmake」という理由だけで有害であるという根拠は不十分です。

  • make clean 問題を修正するには十分です。

  • make主にC / C ++ を使用するプロジェクトにのみ適用されます。これは、Python、Perl、または一般的なドキュメントなどのスクリプトには完全に無意味です。

  • タイムスタンプを適用する場合にのみ害があります。それらをリポジトリに保存しても害はありません。それらを適用することは、ユーザーの裁量で、および友人(、など)の--with-timestampsための簡単なオプションである可能性があります。git checkoutclonepull

BazaarとMercurialはどちらもメタデータを保存します。ユーザーはチェックアウト時にそれらを適用するかどうかを指定できます。しかしgitでは、元のタイムスタンプはリポジトリでも利用できないため、そのようなオプションはありません。

したがって、プロジェクトのサブセットに固有の非常に小さな利益(すべてを再コンパイルするgit必要はない)の場合、一般的なDVCSが機能しなくなったため、ファイルに関する情報の一部が失われ、Linusが言ったように、実行するのは不可能ですそれ。悲しい

とはいえ、2つのアプローチを提供できますか?

1- DavidHärdemanによるhttp://repo.or.cz/w/metastore.gitgit 最初に行うべきことを実行しようとします:コミットするときに(事前コミットフックを介して)メタデータ(タイムスタンプだけでなく)をリポジトリに格納し、プルするときに(フックを介して)メタデータを再適用します。

2-リリースtarballを生成するために以前使用したスクリプトの控えめなバージョン。他の回答で述べたように、アプローチがある少し異なるファイルごとに適用する:タイムスタンプ最新のコミットファイルが変更されました。

  • 多くのオプションを備えた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

パフォーマンスも、モンスターのプロジェクトのために、かなり印象的であるwinegitあるいは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 2013年

13
「タイムスタンプが「makeの混乱を招く」という理由で有害であるという、Linusの根拠」-100%合意、DCVSはその中に含まれるコードを知らないか気にすべきではない!繰り返しになりますが、これは特定のユースケース用に記述されたツールを一般的なユースケースに再利用しようとする場合の落とし穴を示しています。Mercurialは、進化したのではなく設計されたため、常に優れた選択肢です。
Ian Kemp

6
@davecどういたしまして。github.com/MestreLion/git-toolsにあるフルバージョンは、Windows、Python 3、非ASCIIパス名などをすでに処理しています。上記のスクリプトは、概念の実証にすぎません。本番環境での使用は避けてください。
MestreLion、2015年

3
引数は有効です。私はいくつかの影響力を持つ誰かがあなたの提案する--with-timestampsオプションを持つためにgitに拡張リクエストを出すことを望みます。
weberjn 2017年

12

Gielの回答を受け取り、コミット後のフックスクリプトを使用する代わりに、それをカスタムデプロイメントスクリプトに組み込みました。

更新| head -n@eregonの提案に従って1つ削除し、スペースを含むファイルのサポートを追加しました:

# 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にフラグをgit show
エランRuusamäe

1
@ DanielS.Sterling: %ai著者の日付は、ISO 8601であるようにするためのフォーマット、厳格な ISO8601の使用%aIgit-scm.com/docs/git-show
エランRuusamäe

4

コミット時間ではなく具体的​​に変更時間を必要とするため、さらに別のソリューションを発明せざるを得ませんでした。また、ソリューションは移植可能(つまり、Windowsのgitインストールでpythonを動作させることは、実際には簡単な作業ではありません)かつ高速でなければなりませんでした。これは、David Hardemanのソリューションに似ています。これは、ドキュメントがないために使用しないことにしました(リポジトリから、彼のコードが正確に何をしているのかを理解できませんでした)。

このソリューションは、gitリポジトリ内のファイル.mtimesにmtimesを保存し、コミット時にそれらを更新し(ステージングされたファイルのmtimesを選択的にjsut)、チェックアウトに適用します。gitのcygwin / mingwバージョンでも機能します(ただし、一部のファイルを標準のcygwinからgitのフォルダーにコピーする必要がある場合があります)

ソリューションは3つのファイルで構成されています。

  1. mtimestore-3つのオプションを提供するコアスクリプト-a(すべて保存-既存のリポジトリでの初期化用(git-versedファイルで動作))、-s(段階的な変更を保存)、および-rでそれらを復元します。これは実際には2つのバージョンで提供されます-bashバージョン(移植性があり、見栄えがよく、読み取り/変更が簡単)とcバージョン(面倒なバージョンですが高速です。
  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年1月):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

最後の行がもう1つ必要です 。git update-index --refresh // GUIツールはインデックスに依存し、そのような操作の後にすべてのファイルに「ダーティ」ステータスを表示する可能性があります。つまり、それはTortoiseGit for Windowsのcode.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The

1
そしてスクリプトをありがとう。そのようなスクリプトがGit標準のインストーラーの一部であるといいのですが。個人的にそれが必要なわけではありませんが、チームメンバーはタイムスタンプの再ハッシュをVCSの採用における赤い「停止」バナーとして感じています。
Arioch

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のuse-commit-timesに相当するものは何ですか?

それはその答えのようなファイルリストを構築しますが、それ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版のリクエストがいくつかありましたので、こちらをご覧ください。次の2つのファイルを作成します。

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を呼び出すのではなく、1つのパスですべてのファイルを実行します。


0

rsyncベースのデプロイメントで使用するためにリポジトリのクローンを保持するプロジェクトに取り組んでいます。ブランチを使用してさまざまな環境をターゲットにしgit checkout、ファイルの変更を変更します。

gitがファイルをチェックアウトしてタイムスタンプを保持する方法を提供していないことを知ったのでgit log --format=format:%ai --name-only .、別のSOの質問でコマンドに出くわしました:多数のファイルの最終コミット日付をすばやくリストします

touchプロジェクトファイルとディレクトリに次のスクリプトを使用しているので、を使用した展開でrsyncdiffが簡単になりました。

<?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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.