Tương đương với use-commit-times cho git là gì?


97

Tôi cần dấu thời gian của các tệp trên cục bộ và trên máy chủ của mình được đồng bộ hóa. Điều này được thực hiện với Subversion bằng cách đặt use-commit-times = true trong cấu hình để lần sửa đổi cuối cùng của mỗi tệp là khi nó được cam kết.

Mỗi lần tôi sao chép kho lưu trữ của mình, tôi muốn dấu thời gian của tệp phản ánh thời điểm chúng được thay đổi lần cuối trong kho lưu trữ từ xa, chứ không phải khi tôi sao chép kho.

Có cách nào để làm điều này với git?


Là một phần của quá trình triển khai, tôi tải nội dung (hình ảnh, tệp javascript và tệp css) lên CDN. Mỗi tên tệp được nối với dấu thời gian sửa đổi cuối cùng. Điều quan trọng là tôi không hết hạn tất cả tài sản của mình mỗi khi tôi triển khai. (Một tác dụng phụ khác của use-commit-times là tôi có thể thực hiện quá trình này trên cục bộ của mình và biết rằng máy chủ của tôi sẽ tham chiếu đến các tệp giống nhau, nhưng điều đó không quan trọng bằng.) Nếu thay vì thực hiện một bản sao git, tôi đã thực hiện một git fetch theo sau là git reset --hard từ kho lưu trữ từ xa của tôi, sẽ hoạt động cho một máy chủ duy nhất, nhưng không hoạt động cho nhiều máy chủ vì dấu thời gian trên mỗi máy chủ sẽ khác nhau.
Ben W

@BenW: git annexcó thể có ích để theo dõi các hình ảnh
JFS

Bạn có thể kiểm tra những gì đã thay đổi bằng cách kiểm tra id. Bạn đang cố gắng tạo dấu thời gian của hệ thống tệp có ý nghĩa giống như dấu thời gian của vcs. Chúng không có nghĩa giống nhau.
jthill

Câu trả lời:


25

Tôi không chắc điều này có phù hợp với DVCS (như trong VCS "Phân tán")

Cuộc thảo luận lớn đã diễn ra vào năm 2007 (xem chủ đề này)

Và một số câu trả lời của Linus không quá quan tâm đến ý tưởng này. Đây là một mẫu:

Tôi xin lỗi. Nếu bạn không thấy nó SAI khi đặt dấu dữ liệu trở lại thứ gì đó sẽ làm cho cây nguồn của bạn "làm" sai đơn giản , tôi không biết bạn đang nói đến định nghĩa nào về "sai".
Nó sai.
Đó là STUPID.
Và nó hoàn toàn KHÔNG THỂ THỰC HIỆN.


(Lưu ý: cải tiến nhỏ: sau khi kiểm tra, dấu thời gian của các tệp cập nhật không còn được sửa đổi (Git 2.2.2+, tháng 1 năm 2015): "git checkout - làm cách nào để duy trì dấu thời gian khi chuyển đổi chi nhánh?" .)


Câu trả lời dài là:

Tôi nghĩ bạn tốt hơn nhiều nên chỉ sử dụng nhiều kho lưu trữ thay thế, nếu đây là điều phổ biến.

Nói chung, việc lộn xộn với các dấu thời gian sẽ không hoạt động. Nó sẽ đảm bảo với bạn rằng "make" bị nhầm lẫn theo một cách thực sự tồi tệ và không biên dịch đủ thay vì biên dịch lại quá nhiều .

Git thực sự giúp bạn có thể thực hiện việc "kiểm tra chi nhánh khác" của mình một cách rất dễ dàng, theo nhiều cách khác nhau.

Bạn có thể tạo một số tập lệnh tầm thường thực hiện bất kỳ thao tác nào sau đây (từ tầm thường đến kỳ lạ hơn):

  • chỉ cần tạo một repo mới:
    git clone old new
    cd new
    git checkout origin/<branch>

và bạn đây. Các dấu thời gian cũ vẫn tốt trong repo cũ của bạn và bạn có thể làm việc (và biên dịch) trong repo mới mà không cần gắn bó với repo cũ.

Sử dụng các cờ "-n -l -s" để "git clone" về cơ bản thực hiện điều này ngay lập tức. Đối với nhiều tệp (ví dụ như các kho lưu trữ lớn như hạt nhân), nó sẽ không nhanh như chỉ chuyển nhánh, nhưng bản sao thứ hai của cây làm việc có thể khá mạnh mẽ.

  • thay vào đó, hãy làm điều tương tự chỉ với một quả bóng nhựa, nếu bạn muốn
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

điều này thực sự khá nhanh, nếu bạn chỉ muốn chụp nhanh.

  • làm quen với " git show" và chỉ cần xem các tệp riêng lẻ.
    Điều này đôi khi thực sự hữu ích. Bạn chỉ làm
    git show otherbranch:filename

trong một cửa sổ xterm và xem cùng một tệp trong nhánh hiện tại của bạn trong một cửa sổ khác. Đặc biệt, điều này có thể xảy ra với các trình soạn thảo có script (tức là GNU emacs), nơi về cơ bản có thể có toàn bộ "chế độ dired" cho các nhánh khác trong trình soạn thảo bằng cách sử dụng điều này. Đối với tất cả những gì tôi biết, chế độ git emacs đã cung cấp một cái gì đó như thế này (tôi không phải là người dùng emacs)

  • và trong ví dụ điển hình về thứ "thư mục ảo" đó, ít nhất có ai đó đang làm việc trên một plugin git cho FUSE, tức là bạn có thể chỉ có các thư mục ảo hiển thị tất cả các chi nhánh của bạn.

và tôi chắc chắn rằng bất kỳ cách nào ở trên đều là lựa chọn thay thế tốt hơn là chơi trò chơi với dấu thời gian tệp.

Linus


5
Đã đồng ý. Bạn không nên nhầm lẫn giữa DVCS với hệ thống phân phối. gitlà một DVCS, để thao tác mã nguồn sẽ được tích hợp vào sản phẩm cuối cùng của bạn. Nếu bạn muốn có một hệ thống phân phối, bạn biết nơi để tìm rsync.
Randal Schwartz

14
Hừm, tôi phải tin vào lập luận của anh ấy rằng nó không khả thi. Dù nó sai hay ngu ngốc là một vấn đề khác. Tôi phiên bản tệp của mình bằng cách sử dụng dấu thời gian và tải chúng lên CDN, đó là lý do tại sao dấu thời gian phản ánh thời điểm tệp thực sự được sửa đổi, chứ không phải thời điểm nó được kéo xuống từ kho lưu trữ lần cuối.
Ben W

3
@Ben W: "Câu trả lời của Linus" không phải ở đây để nói rằng nó sai trong tình huống cụ thể của bạn . Nó chỉ như một lời nhắc nhở rằng DVCS không phù hợp cho loại tính năng đó (bảo toàn dấu thời gian).
VonC

15
@VonC: Vì các DVCS hiện đại khác như Bazaar và Mercurial xử lý tốt các dấu thời gian, tôi muốn nói rằng " git không phù hợp cho loại tính năng đó". Nếu "a" DVCS nên có tính năng đó thì còn gì phải tranh cãi (và tôi thực sự nghĩ rằng họ có).
MestreLion

10
Đây không phải là một câu trả lời cho câu hỏi, mà là một cuộc thảo luận triết học về giá trị của việc này trong một hệ thống kiểm soát phiên bản. Nếu người đó thích điều đó, họ sẽ hỏi, "Lý do git không sử dụng thời gian cam kết cho thời gian sửa đổi của tệp là gì?"
thomasfuchs

85

Tuy nhiên, nếu bạn thực sự muốn sử dụng thời gian cam kết cho dấu thời gian khi thanh toán, hãy thử sử dụng tập lệnh này và đặt nó (dưới dạng tệp thực thi) trong tệp $ 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

Tuy nhiên, lưu ý rằng tập lệnh này sẽ gây ra độ trễ khá lớn cho việc kiểm tra các kho lưu trữ lớn (trong đó lớn có nghĩa là lượng tệp lớn chứ không phải kích thước tệp lớn).


55
1 cho một câu trả lời thực tế, chứ không phải chỉ nói "Đừng làm điều đó"
DanC

4
| head -n 1Nên tránh vì nó sinh ra một quy trình mới, -n 1cho git rev-listgit logcó thể được sử dụng để thay thế.
ergon

3
Tốt hơn là KHÔNG đọc các dòng có `...`for; xem Tại sao bạn không đọc các dòng có "for" . Tôi sẽ đi cho git ls-files -zwhile IFS= read -r -d ''.
musiphil

2
Phiên bản Windows có được không?
Ehryk

2
thay vì git show --pretty=format:%ai --abbrev-commit "$(get_file_rev "$1")" | head -n 1bạn có thể làm git show --pretty=format:%ai -s "$(get_file_rev "$1")", nó khiến showlệnh tạo ra ít dữ liệu hơn và sẽ giảm chi phí.
Scott Chamberlain

79

CẬP NHẬT : Giải pháp của tôi hiện đã được đóng gói thành Debian / Ubuntu / Mint, Fedora, Gentoo và có thể là các bản phân phối khác:

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, không lưu trữ dấu thời gian (và các siêu dữ liệu khác như quyền và quyền sở hữu) là một hạn chế lớngit .

Cơ sở lý luận của Linus về việc dấu thời gian có hại chỉ vì nó "nhầm lẫn make" là khập khiễng :

  • make clean là đủ để sửa chữa mọi vấn đề.

  • Chỉ áp dụng cho các dự án sử dụng make, chủ yếu là C / C ++. Nó là hoàn toàn tranh luận cho các tập lệnh như Python, Perl hoặc tài liệu nói chung.

  • Chỉ có hại nếu bạn áp dụng các dấu thời gian. Sẽ không có hại gì khi lưu trữ chúng trong repo. Áp dụng chúng có thể là một --with-timestampslựa chọn đơn giản cho git checkoutvà bạn bè ( clone, pullv.v.), theo quyết định của người dùng .

Cả siêu dữ liệu cửa hàng Bazaar và Mercurial. Người dùng có thể áp dụng chúng hoặc không khi thanh toán. Nhưng trong git, vì dấu thời gian gốc thậm chí không có sẵn trong repo, nên không có tùy chọn như vậy.

Vì vậy, đối với một lợi ích rất nhỏ (không phải biên dịch lại mọi thứ) cụ thể cho một tập hợp con của các dự án, gitvì một DVCS chung đã bị tê liệt , một số thông tin từ các tệp bị mất và, như Linus đã nói, KHÔNG THỂ LÀM ĐƯỢC. nó bây giờ. Buồn .

Điều đó nói rằng, tôi có thể đưa ra 2 cách tiếp cận không?

1 - http://repo.or.cz/w/metastore.git , của David Härdeman. Cố gắng thực hiện những gì đáng git lẽ phải làm ngay từ đầu : lưu trữ siêu dữ liệu (không chỉ dấu thời gian) trong repo khi cam kết (thông qua hook trước khi cam kết) và áp dụng lại chúng khi kéo (cũng như qua hook).

2 - Phiên bản khiêm tốn của tôi về một script mà tôi đã sử dụng trước đây để tạo tarball phát hành. Như đã đề cập trong các câu trả lời khác, cách tiếp cận hơi khác một chút : áp dụng cho mỗi tệp dấu thời gian của lần cam kết gần đây nhất mà tệp đã được sửa đổi.

  • git-restore-mtime , với nhiều tùy chọn, hỗ trợ mọi bố cục kho lưu trữ và chạy trên Python 3.

Dưới đây là phiên bản thực sự đơn giản của tập lệnh, như một bằng chứng về khái niệm, trên Python 2.7. Để sử dụng thực tế, tôi thực sự khuyên bạn nên sử dụng phiên bản đầy đủ ở trên:

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

Hiệu suất là khá ấn tượng, ngay cả đối với các dự án con quái vật wine, githay thậm chí là hạt nhân 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
Nhưng git lưu trữ dấu thời gian, v.v. Nó không đặt dấu thời gian theo mặc định. Chỉ cần nhìn vào sản lượnggit ls-files --debug
Ross Smith II

9
@RossSmithII: git ls-fileshoạt động trên thư mục và chỉ mục đang làm việc, vì vậy không có nghĩa là nó thực sự lưu trữ thông tin đó trên repo. Nếu nó đã được lưu trữ, việc truy xuất (và áp dụng) mtime sẽ rất nhỏ.
MestreLion

13
"Cơ sở lý luận của Linus về dấu thời gian có hại chỉ vì nó" gây nhầm lẫn "là khập khiễng" - đồng ý 100%, một DCVS không nên biết hoặc quan tâm đến mã mà nó chứa! Một lần nữa, điều này cho thấy những cạm bẫy của việc cố gắng sử dụng lại các công cụ được viết cho các trường hợp sử dụng cụ thể thành các trường hợp sử dụng chung. Mercurial luôn và sẽ là một sự lựa chọn ưu việt bởi vì nó được thiết kế chứ không phải phát triển.
Ian Kemp

6
@davec Bạn được chào đón, rất vui vì nó hữu ích. Phiên bản đầy đủ tại github.com/MestreLion/git-tools đã xử lý Windows, Python 3, tên đường dẫn không phải ASCII, v.v. Tập lệnh trên chỉ là một bằng chứng về khái niệm hoạt động, hãy tránh nó để sử dụng trong sản xuất.
MestreLion

3
Lập luận của bạn là hợp lệ. Tôi hy vọng ai đó có chút ảnh hưởng sẽ đưa ra yêu cầu nâng cao cho git để có tùy chọn - với dấu thời gian được đề xuất của bạn.
weberjn

12

Tôi đã lấy câu trả lời của Giel và thay vì sử dụng một tập lệnh hook hậu cam kết, hãy đưa nó vào tập lệnh triển khai tùy chỉnh của tôi.

Cập nhật : Tôi cũng đã xóa một | head -nđề xuất sau đây của @rectgon và thêm hỗ trợ cho các tệp có khoảng trắng trong đó:

# 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

Cảm ơn Daniel, đó là hữu ích để biết
Alex Dean

các --abbrev-commitlà không cần thiết trong git showlệnh do --pretty=format:%aiđang được sử dụng (cam băm không phải là một phần của đầu ra) và | head -n 1có thể được thay thế bằng cách sử dụng -slá cờ đểgit show
Elan Ruusamäe

1
@ DanielS.Sterling: %ailà ngày tác giả, tiêu chuẩn ISO 8601 như định dạng, cho chặt chẽ sử dụng ISO8601 %aI: git-scm.com/docs/git-show
Elan Ruusamäe

4

chúng tôi buộc phải phát minh ra một giải pháp khác, bởi vì chúng tôi cần thời gian sửa đổi cụ thể chứ không phải thời gian cam kết, và giải pháp cũng phải có tính di động (tức là để python hoạt động trong cài đặt git của windows thực sự không phải là một nhiệm vụ đơn giản) và nhanh chóng. Nó giống với giải pháp của David Hardeman, mà tôi quyết định không sử dụng vì thiếu tài liệu (từ kho lưu trữ, tôi không thể biết chính xác mã của anh ấy làm gì).

Giải pháp này lưu trữ mtimes trong một tệp .mtimes trong kho lưu trữ git, cập nhật chúng cho phù hợp trên các cam kết (jsut chọn lọc các mtimes của các tệp theo giai đoạn) và áp dụng chúng khi thanh toán. Nó hoạt động ngay cả với các phiên bản cygwin / mingw của git (nhưng bạn có thể cần sao chép một số tệp từ cygwin tiêu chuẩn vào thư mục của git)

Giải pháp bao gồm 3 tệp:

  1. mtimestore - tập lệnh cốt lõi cung cấp 3 tùy chọn -a (lưu tất cả - để khởi tạo trong repo đã tồn tại (hoạt động với các tệp git-versed)), -s (để lưu các thay đổi theo giai đoạn) và -r để khôi phục chúng. Điều này thực sự có 2 phiên bản - một phiên bản bash (di động, đẹp, dễ đọc / sửa đổi) và phiên bản c (một phiên bản lộn xộn nhưng nhanh, vì mingw bash chậm kinh khủng khiến không thể sử dụng giải pháp bash trong các dự án lớn).
  2. móc nối trước cam kết
  3. móc sau thanh toán

Cam kết trước:

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

hậu kiểm

#!/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;
}
  • lưu ý rằng các móc có thể được đặt vào thư mục mẫu để tự động hóa vị trí của chúng

có thể tìm thấy thêm thông tin tại đây https://github.com/kareltucek/git-mtime-extension một số thông tin lỗi thời có tại http://www.ktweb.cz/blog/index.php?page=page&id=116

// chỉnh sửa - phiên bản c ++ được cập nhật:

  • Bây giờ phiên bản c ++ duy trì thứ tự bảng chữ cái -> ít xung đột hợp nhất hơn.
  • Đã loại bỏ các lệnh gọi system () xấu xí.
  • Đã xóa $ git update-index - làm mới $ khỏi móc sau thanh toán. Gây ra một số vấn đề với việc hoàn nguyên theo git rùa và dường như không quan trọng lắm.
  • Gói cửa sổ của chúng tôi có thể được tải xuống tại http://ktweb.cz/blog/download/git-mtimestore-1.4.rar

// chỉnh sửa xem github để biết phiên bản cập nhật


1
Lưu ý rằng sau khi kiểm tra, dấu thời gian của tệp cập nhật không còn được sửa đổi (Git 2.2.2+, tháng 1 năm 2015): stackoverflow.com/a/28256177/6309
VonC,

3

Tập lệnh sau kết hợp các đề xuất -n 1HEAD, hoạt động trong hầu hết các môi trường không phải Linux (như Cygwin) và có thể được chạy khi thanh toán sau thực tế:

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

Giả sử bạn đã đặt tên cho tập lệnh trên /path/to/templates/hooks/post-checkoutvà / hoặc /path/to/templates/hooks/post-update, bạn có thể chạy nó trên một kho lưu trữ hiện có thông qua:

git clone git://path/to/repository.git
cd repository
/path/to/templates/hooks/post-checkout

Nó cần thêm một dòng cuối cùng: git update-index --refresh // Các công cụ GUI có thể dựa vào chỉ mục và hiển thị trạng thái "bẩn" cho tất cả tệp sau thao tác như vậy. Cụ thể là điều đó xảy ra trong TortoiseGit dành cho Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The August

1
Và cảm ơn vì kịch bản. Tôi ước gì tập lệnh như vậy là một phần của trình cài đặt chuẩn Git. Không phải cá nhân tôi cần nó, nhưng các thành viên trong nhóm chỉ cảm thấy dấu thời gian được sửa lại như một biểu ngữ "dừng" màu đỏ khi áp dụng VCS.
Arioch 'The August

3

Giải pháp này sẽ chạy khá nhanh. Nó đặt thời gian thành thời gian cam kết và thời gian thành thời gian của tác giả. Nó không sử dụng mô-đun nên có tính di động hợp lý.

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

Đây là phiên bản được tối ưu hóa của các giải pháp shell ở trên, với các bản sửa lỗi nhỏ:

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

Đây là một phương pháp với 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);
}

Nó tương tự như câu trả lời ở đây:

Tương đương với use-commit-times cho git là gì?

nó xây dựng một danh sách tệp giống như câu trả lời đó, nhưng nó xây dựng từ git ls-files thay vì chỉ tìm kiếm trong thư mục làm việc. Điều này giải quyết vấn đề loại trừ .gitvà nó cũng giải quyết vấn đề của các tệp không được theo dõi. Ngoài ra, câu trả lời đó không thành công nếu cam kết cuối cùng của tệp là cam kết hợp nhất mà tôi đã giải quyết bằng git log -m. Giống như câu trả lời khác, sẽ dừng lại sau khi tất cả các tệp được tìm thấy, vì vậy nó không phải đọc tất cả các cam kết. Ví dụ với:

https://github.com/git/git

kể từ khi đăng bài này, nó chỉ phải đọc 292 cam kết. Ngoài ra, nó bỏ qua các tệp cũ khỏi lịch sử khi cần thiết và nó sẽ không chạm vào tệp đã được chạm vào. Cuối cùng nó có vẻ nhanh hơn một chút so với giải pháp kia. Kết quả với git/gitrepo:

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

0

Tôi đã thấy một số yêu cầu cho phiên bản Windows, vì vậy nó đây. Tạo hai tệp sau:

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

Điều này sử dụng git whatchanged , vì vậy nó chạy qua tất cả các tệp trong một lần chuyển thay vì gọi git cho mỗi tệp.


0

Tôi đang làm việc trong một dự án mà giữ một bản sao kho lưu trữ của tôi để sử dụng với các rsynctriển khai dựa trên. Tôi sử dụng các nhánh để nhắm mục tiêu các môi trường khác nhau và git checkoutkhiến các sửa đổi tệp thay đổi.

Sau khi biết rằng git không cung cấp cách kiểm tra tệp và lưu giữ dấu thời gian, tôi đã bắt gặp lệnh git log --format=format:%ai --name-only .trong một câu hỏi SO khác: Liệt kê ngày cam kết cuối cùng cho một số lượng lớn tệp một cách nhanh chóng .

Bây giờ tôi đang sử dụng tập lệnh sau cho touchcác tệp và thư mục dự án của mình để việc triển khai của tôi với rsyncdễ dàng khác biệt hơn:

<?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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.