Kiểm tra tệp cũ VỚI dấu thời gian tạo / sửa đổi ban đầu


Câu trả lời:


45

Tôi tin rằng dấu thời gian duy nhất được ghi lại trong cơ sở dữ liệu Git là dấu thời gian của tác giả và cam kết. Tôi không thấy tùy chọn nào để Git sửa đổi dấu thời gian của tệp để phù hợp với cam kết gần đây nhất và điều này có nghĩa là đây không phải là hành vi mặc định (vì nếu có, Makefiles sẽ không hoạt động chính xác).

Bạn có thể viết một tập lệnh để đặt ngày sửa đổi các tệp của mình thành thời điểm của lần cam kết gần đây nhất. Nó có thể trông giống như sau:

IFS="
"
for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso -- "$FILE")
    TIME=$(date -j -f '%Y-%m-%d %H:%M:%S %z' "$TIME" +%Y%m%d%H%M.%S)
    touch -m -t "$TIME" "$FILE"
done

10
Có một số vấn đề với đoạn mã này: 1 - Không thành công nếu có khoảng trắng trong tên tệp; 2 - Có thể không thành công đối với các dự án có hơn vài nghìn tệp; 3 - hiệu suất là hoàn toàn đau khổ bất kỳ dự án vừa và nhỏ với một vài ngàn cam kết (ngay cả với vài tác phẩm)
MestreLion

10
+1 có thể không hoạt động cho mọi trường hợp có thể xảy ra, nhưng đó là một câu trả lời đơn giản tốt.
qwerty9967

5
Câu hỏi của OP không phải là làm thế nào để bảo toàn dấu thời gian đã sửa đổi tệp gốc, không đeo dấu thời gian cam kết vào tệp?
BT

15
Thiết kế VCS xung quanh Make là thiển cận. Tôi nghĩ đây là một lỗi của Git. Vì vậy, thực sự không có nghĩa là nó không phải là hành vi mặc định. Làm cho tệp phải chạy trên nội dung tệp, không phải dấu thời gian. Việc băm tệp và xem liệu băm có khớp với những gì bạn đã tạo hay không sẽ mạnh mẽ hơn nhiều.
BT

4
Tôi đồng ý với BT và các phần nhận xét của bạn Dietrich. Ý của BT về OP là câu trả lời của bạn không thực sự cho phép giữ nguyên thời gian ban đầu của tệp. Thay vào đó, nó thay thế chúng bằng thời gian thanh toán ban đầu. Không giống nhau ... Vì vậy , tôi nghĩ anh ấy đã nói rõ ràng bài đăng của bạn có sai sót thực tế. Và tôi có thể thấy quyết định không lưu trữ dấu thời gian đến từ đâu, như bạn đã nói. Tôi cũng nghĩ rằng BT đang quay lại một chút với lý do đó. Điều mà tôi đồng ý với BT một lần nữa - không có lý do chính đáng để không thể làm điều đó chút nào. Mọi VCS khác đều có thể làm được.
cregox,

56

, siêu thị hoặc git-cache-meta có thể lưu trữ thông tin (meta-) như vậy! Git của chính nó, không có công cụ của bên thứ ba, không thể. Metastore hoặc git-cache-meta có thể lưu trữ bất kỳ siêu dữ liệu tệp nào cho một tệp.

Điều đó là do thiết kế, vì di căn hoặc git-cache-meta được thiết kế cho mục đích đó, cũng như hỗ trợ các tiện ích sao lưu và công cụ đồng bộ hóa.

(Xin lỗi chỉ là một chút vui vẻ về câu trả lời của Jakub)


8
Bạn thậm chí đã bắt chước tất cả các mũ của anh ấy! Nếu bạn cũng áp dụng chữ in đậm, tôi chắc chắn rằng bạn sẽ nhận được nhiều sự ủng hộ hơn nữa. ;-)
Michael Scheper

1
Vì vậy, tôi hơi bối rối chủ yếu vì cả hai công cụ này (sau khi tìm hiểu kỹ về chúng) đều thả bóng theo kiểu ngoạn mục trên macOS. Chúng hoàn toàn không thể di động ra khỏi Linux. git-cache-meta dựa vào tiện ích mở rộng findcủa GNU -printfvà tôi gần như chắc chắn rằng công cụ di căn (là một dự án C) thậm chí còn nhiều công việc hơn để làm cho khả năng di động. Khá đáng tiếc. Tôi sẽ đăng lại ở đây nếu tôi phát hiện ra rằng tình hình này thay đổi.
Steven Lu

40

NO , Git chỉ đơn giản là không lưu trữ như vậy (meta) thông tin , trừ khi bạn sử dụng công cụ của bên thứ ba như metastore hoặc git-cache-meta. Dấu thời gian duy nhất được lưu trữ là bản vá thời gian / thay đổi đã được tạo (thời gian của tác giả) và thời gian cam kết đã được tạo (thời gian của người thực hiện).

Đó là theo thiết kế, vì Git là hệ thống kiểm soát phiên bản, không phải là một tiện ích sao lưu hoặc công cụ đồng bộ hóa.


có bản build nào cho win32 không? hay nên tạo lại script / hooks cho Windows? Franklt, tôi không cần những đoạn tin nhắn khác, chỉ có mtime
Arioch 'The

7
Tôi nghĩ câu trả lời của bạn thực sự là "CÓ! Metastore hoặc git-cache-meta có thể làm điều này cho bạn!" Tôi đoán đó là sự khác biệt giữa những người thất bại và lạc quan.
BT

2
Ngoài ra, như tôi đã nghe nói, chợ và thương mại cũng là "hệ thống kiểm soát phiên bản" lưu trữ thông tin meta. Không có gì sai khi làm như vậy.
cregox

Làm rõ: Git giữ hai dấu thời gian cho mỗi tệp: ngày của tác giả (mà tôi nghĩ theo cách hiểu của Jakub là 'bản vá thời gian') và ngày của người cam kết. Cái trước là thời điểm tệp được cam kết lần đầu và cái sau là thời điểm tệp được cam kết gần đây nhất.
Michael Scheper

4
"Đó là theo thiết kế, vì Git là hệ thống kiểm soát phiên bản, không phải là một tiện ích sao lưu hoặc công cụ đồng bộ hóa." Đó là điều không phải tuần tự : việc bỏ qua siêu dữ liệu ( đặc biệt là ngày tháng, có liên quan mật thiết đến các phiên bản) không liên quan gì đến việc trở thành một VCS hoặc một công cụ sao lưu. Ngoài ra, mọi VCS đều có sự chồng chéo lớn về chức năng với các công cụ sao lưu: cả hai đều cố gắng duy trì các trạng thái quan trọng trong quá khứ. Cuối cùng, ngay cả Git cũng không bỏ qua tất cả siêu dữ liệu (ví dụ như nó theo dõi bit thực thi), mặc dù là một VCS. Tuy nhiên, nó vẫn do thiết kế, chỉ vì một lý do khác: sự tập trung độc quyền của Git vào nội dung.
Sz.

13

CẬP NHẬT : TL; DR: git bản thân nó không lưu thời gian gốc, nhưng một số giải pháp đã phá vỡ điều này bằng nhiều phương pháp khác nhau. git-restore-mtimelà một trong số họ:

https://github.com/MestreLion/git-tools/

Ubuntu / Debian: sudo apt install git-restore-mtime
Fedora / RHEL / CentOS:sudo yum install git-tools

Xem câu trả lời khác của tôi để biết thêm chi tiết

Tuyên bố từ chối trách nhiệm đầy đủ: Tôi là tác giả của git-tools


Tập lệnh python này có thể hữu ích: đối với mỗi tệp áp dụng dấu thời gian của lần cam kết gần đây nhất nơi tệp đã được sửa đổi:

  • Chức năng cốt lõi , với - trợ giúp, thông báo gỡ lỗi. Có thể chạy ở bất cứ đâu trong cây công việc
  • Con thú đầy đủ , với rất nhiều lựa chọn. Hỗ trợ mọi bố cục kho lưu trữ.

Dưới đây là một phiên bản thực sự rõ ràng của kịch bản. Đối với việc sử dụng thực tế, tôi thực sự đề xuất một trong những phiên bản mạnh mẽ hơn ở 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

Tất cả các phiên bản phân tích cú pháp toàn bộ nhật ký được tạo bằng một git whatchangedlệnh duy nhất , nhanh hơn hàng trăm lần so với việc ghi nhật ký cho từng tệp. Dưới 4 giây cho git (24.000 cam kết, 2.500 tệp) và dưới 1 phút cho nhân linux (40.000 tệp, 300.000 cam kết)


2
Câu trả lời tương tự khác của bạn tốt hơn nhiều so với câu trả lời này!
cregox,

$ python ./git-restore-mtime Traceback (most recent call last): File "./git-restore-mtime", line 122, in <module> 'git rev-parse --show-toplevel --git-dir')).split('\n')[:2] TypeError: Type str doesn't support the buffer APIBạn có phiền có thể cho chúng tôi biết phiên bản Python nào là cần thiết không? Tôi đang sử dụng 3.3.3
Rolf

@Cawas: Cảm ơn ... tôi đoán vậy. Nhưng mã trong cả hai câu trả lời đều giống hệt nhau, vì vậy tôi không chắc tại sao bạn nghĩ câu kia tốt hơn. Sự khác biệt duy nhất là một số lời nói về git. Điều này có phần phù hợp với câu hỏi đó, nhưng không phù hợp với câu hỏi này.
MestreLion

1
@Rolf: Tôi đã sử dụng Python 2.7 và có vẻ như mã cần một số chỉnh sửa trong Python 3, cảm ơn bạn đã chỉ ra. Lý do là: strtrong Python 2 tương đương với bytestringPython 3, trong khi strPython 3 là unicodePython 2. Bạn có thể vui lòng báo cáo vấn đề này tại github.com/MestreLion/git-tools/issues không?
MestreLion

Nó không chỉ là "rant". Ở đó, bạn cũng giải thích mã làm gì chi tiết hơn và do đó, rõ ràng hơn.
cregox

6

Điều này đã làm anh ta lừa tôi trên ubuntu (thiếu cờ "-j" của OSX vào ngày (1))

for FILE in $(git ls-files)
do
    TIME=$(git log --pretty=format:%cd -n 1 --date=iso $FILE)
    TIME2=`echo $TIME | sed 's/-//g;s/ //;s/://;s/:/\./;s/ .*//'`
    touch -m -t $TIME2 $FILE
done 

4

Tôi đã giao tranh với git và dấu thời gian tệp được một thời gian rồi.

Đã thử nghiệm một số ý tưởng của bạn và tạo ra các tập lệnh cực kỳ lớn và nặng nề trước đó / ram của riêng tôi, cho đến khi tôi tìm thấy (trên một số git wiki) một tập lệnh trong perl gần như những gì tôi muốn. https://git.wiki.kernel.org/index.php/ExampleScripts

Và những gì tôi muốn là có thể duy trì sửa đổi cuối cùng của các tệp dựa trên ngày cam kết.

Vì vậy, sau một số điều chỉnh lại, tập lệnh có thể thay đổi ngày tạo và sửa đổi của 200k tệp trong khoảng 2-3 phút .

#!/usr/bin/perl
my %attributions;
my $remaining = 0;

open IN, "git ls-tree -r --full-name HEAD |" or die;
while (<IN>) {
    if (/^\S+\s+blob \S+\s+(\S+)$/) {
        $attributions{$1} = -1;
    }
}
close IN;

$remaining = (keys %attributions) + 1;
print "Number of files: $remaining\n";
open IN, "git log -r --root --raw --no-abbrev --date=raw --pretty=format:%h~%cd~ |" or die;
while (<IN>) {
    if (/^([^:~]+)~([^~]+)~$/) {
        ($commit, $date) = ($1, $2);
    } elsif (/^:\S+\s+1\S+\s+\S+\s+\S+\s+\S\s+(.*)$/) {
        if ($attributions{$1} == -1) {
            $attributions{$1} = "$date";
            $remaining--;

            utime $date, $date, $1;
            if ($remaining % 1000 == 0) {               
                print "$remaining\n";
            }
            if ($remaining <= 0) {
                break;
            }
        }
    }
}
close IN;

Giả sử rằng kho lưu trữ của bạn sẽ không có hơn 10k + tệp, điều này sẽ mất vài giây để thực thi, vì vậy bạn có thể kết nối nó với thanh toán, kéo hoặc các móc cơ bản git khác.


2

Đây là giải pháp của tôi có tính đến các đường dẫn có chứa khoảng trắng:

#! /bin/bash

IFS=$'\n'
list_of_files=($(git ls-files | sort))
unset IFS

for file in "${list_of_files[@]}"; do
  file_name=$(echo $file)

  ## When you collect the timestamps:
  TIME=$(date -r "$file_name" -Ins)

  ## When you want to recover back the timestamps:
  touch -m -d $TIME "$file_name"
done

Lưu ý rằng điều này không mất thời gian git logbáo cáo mà là thời gian được hệ thống báo cáo. Nếu bạn muốn thời gian kể từ khi các tệp được cam kết sử dụng git loggiải pháp thay vìdate -r


2

Gt gốc không có chức năng, nhưng nó có thể đạt được bằng các tập lệnh hook hoặc các công cụ của bên thứ ba.

Tôi đã thử metastore. Nó rất nhanh, nhưng tôi không thích phải cài đặt và siêu dữ liệu đó không được lưu trữ ở định dạng văn bản thuần túy. git-cache-metalà một công cụ đơn giản mà tôi đã thử, nhưng nó cực kỳ chậm đối với các kho lưu trữ lớn (đối với một kho lưu trữ có hàng chục nghìn tệp, phải mất vài phút để cập nhật tệp siêu dữ liệu) và có thể có vấn đề về tương thích đa nền tảng. setgitpermsvà các cách tiếp cận khác cũng có những khuyết điểm mà tôi không thích.

Cuối cùng, tôi đã tạo một tập lệnh hook cho công việc này: git-store-meta . Nó có độ phụ thuộc rất nhẹ (* nix shell, sortperl, được yêu cầu bởi git, và tùy chọn chown, chgrptouch) để không cần phải cài đặt thêm gì cho một nền tảng có thể chạy git, hiệu suất đáng mơ ước (đối với một repo có hàng chục nghìn của tệp, mất <10 giây để cập nhật tệp siêu dữ liệu; mặc dù thời gian tạo lâu hơn), lưu dữ liệu ở định dạng văn bản thuần túy và siêu dữ liệu nào được "lưu" hoặc "tải" có thể tùy chỉnh .

Nó đã làm việc tốt cho tôi. Hãy thử điều này nếu bạn không hài lòng với các phương pháp tiếp cận phổ biến, git-cache-meta và các phương pháp khác.


2

Tôi hy vọng bạn đánh giá cao sự đơn giản:

# getcheckin - Retrieve the last committed checkin date and time for
#              each of the files in the git project.  After a "pull"
#              of the project, you can update the timestamp on the
#              pulled files to match that date/time.  There are many
#              that believe that this is not a good idea, but
#              I found it useful to get the right source file dates
#
#              NOTE: This script produces commands suitable for
#                    piping into BASH or other shell
# License: Creative Commons Attribution 3.0 United States
# (CC by 3.0 US)

##########
# walk back to the project parent or the relative pathnames don't make
# sense
##########
while [ ! -d ./.git ]
do
    cd ..
done
echo "cd $(pwd)"
##########
# Note that the date format is ISO so that touch will work
##########
git ls-tree -r --full-tree HEAD |\
    sed -e "s/.*\t//" | while read filename; do
    echo "touch --date=\"$(git log -1 --date=iso --format="%ad" -- "$filename")\" -m $filename" 
done

(FYI, có một phủ định kép ngoài ý muốn trong bình luận tiêu đề, mà bạn có thể muốn sửa trong bản gốc của mình: "Có nhiều người không tin rằng đây không phải là một ý kiến ​​hay.")
Sz.

1

Đối với môi trường Windows, tôi đã viết một EXE nhỏ (nhanh và bẩn) trong Delphi 10.1 Berlin để thu thập tất cả ngày tháng của tệp trong cây nguồn vào tệp .gitfilattr và có thể áp dụng chúng trên cây nguồn đã được kiểm tra của chúng tôi một lần nữa.

Tất nhiên tôi chia sẻ mã trong GitHub:

https://github.com/michaschumann/gitfiledates/blob/master/gitFileDates.dpr

Tôi sử dụng nó trong hệ thống xây dựng của mình dựa trên trình chạy GitLab.


1

Có một số mơ hồ trong cách giải thích OP của tôi (và những người khác) về việc liệu điều này có nghĩa là thời gian cam kết hay điều gì khác, nhưng giả sử nó có nghĩa là thời gian cam kết, thì một lớp lót đơn giản này sẽ hoạt động trong linux (dựa trên đoạn mã trả lời từ Dietrich Epp ):

git ls-files | xargs -I{} bash -c 'touch "{}" --date=@$(git log -n1 --pretty=format:%ct -- "{}")'

Nhưng có nhiều câu trả lời phức tạp hơn (bao gồm cả git hook) được liên kết từ một nhận xét đến câu hỏi ban đầu bởi cregox.


lol điều này đã giải phóng một số lượng lớn các tệp trong thanh toán của tôi có tên--date=@foo
mxcl

0

Với các công cụ GNU.

s=$(git ls-files  | wc -l); 
git ls-files -z  |
 xargs -0 -I{} -n1 bash -c \
"git log --date=format:%Y%m%d%H%M.%S '--pretty=format:touch -m -t %cd \"{}\"%n' -n1 -- {}"|
 pv -l -s$s |
 parallel -n1 -j8

 967  0:00:05 [ 171 /s] [=====================================>  ] 16% 

.

$ git --version ; xargs --version | sed 1q ; ls --version | sed 1q;
  parallel --version  | sed 1q;  pv --version | sed 1q; sh --version | sed 1q 
git version 2.13.0
xargs (GNU findutils) 4.6.0
ls (GNU coreutils) 8.25
GNU parallel 20150522
pv 1.6.0 - Copyright 2015 Andrew Wood <andrew.wood@ivarch.com>
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

Parallell dường như không làm được gì nhiều, có lẽ là một nút thắt cổ chai. YMMV
Ярослав Рахматуллин

0

Trong CentOS 7, bạn có /usr/share/doc/rsync-*/support/git-set-file-timesvà trong Debian (và các dẫn xuất) cùng một tập lệnh /usr/share/doc/rsync/scripts/git-set-file-times.gz, bản gốc là của Eric Wong và ở đây https://yhbt.net/git-set-file-times .

Nó hoạt động nhanh hơn các ví dụ khác được đề cập ở đây và bạn có thể thấy tiện lợi hơn khi có nó trên bản phân phối Linux của mình.


0

Đây là của tôi.

Nhanh hơn một chút so với một số tệp khác, vì tôi không gọi 'lấy nhật ký' cho mỗi tệp được tìm thấy; thay vào đó, gọi 'git log' một lần và chuyển đầu ra đó thành các lệnh cảm ứng.

Sẽ có trường hợp có quá nhiều tệp được liệt kê trong 1 cam kết để vừa với một bộ đệm lệnh shell duy nhất; chạy "getconf ARG_MAX" để xem độ dài tối đa của một lệnh tính bằng byte - trên bản cài đặt debian của tôi, nó là 2MB, quá nhiều.

# set file last modification time to last commit of file
git log --reverse --date=iso --name-only | \
  grep -vE "^(commit |Merge:|Author:|    |^$)" | \
  grep -B 1 "^[^D][^a][^t][^e][^:][^ ]" | \
  grep -v "^\-\-" | \
  sed "s|^\(.*\)$|\"\1\"|;s|^\"Date: *\(.*\)\"$|~touch -c -m -d'\1'|" | \
  tr '~\n' '\n ' | \
  sh -

mô tả theo dòng:

  • danh sách cam kết và tên tệp sớm nhất-đầu tiên
  • lọc ra các dòng cam kết / hợp nhất / tác giả không cần thiết
  • lọc ra các dòng bắt đầu bằng dấu gạch ngang kép
  • Lệnh sed (stream-edit) a) thêm / nối dấu ngoặc kép vào các dòng và b) thay thế "Ngày:. " bằng ~ touch -c -m -d. (các tùy chọn lệnh cảm ứng là -c = không tạo nếu nó không tồn tại, -m = thay đổi thời gian sửa đổi tệp và -d = sử dụng ngày / giờ được cung cấp)
  • dịch các ký tự dấu ngã (~) và newline (\ n) sang dòng mới và dấu cách, tương ứng
  • đưa dòng văn bản kết quả vào một trình bao.

Về tốc độ, 5 giây 1700 cam kết cho 6500 tệp trong 700 thư mục.

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.