การใช้คอมมิต - ไทม์เทียบเท่ากับอะไร


97

ฉันต้องการการประทับเวลาของไฟล์ในเครื่องและบนเซิร์ฟเวอร์ของฉันเพื่อให้ซิงค์กัน สิ่งนี้ทำได้ด้วยการโค่นล้มโดยการตั้งค่า use-commit-times = true ใน config เพื่อให้การแก้ไขครั้งสุดท้ายของแต่ละไฟล์คือเมื่อถูกคอมมิต

ทุกครั้งที่ฉันโคลนที่เก็บฉันต้องการให้การประทับเวลาของไฟล์แสดงถึงเวลาที่มีการเปลี่ยนแปลงครั้งล่าสุดในที่เก็บระยะไกลไม่ใช่เมื่อฉันโคลนที่เก็บข้อมูล

มีวิธีใดบ้างที่จะทำสิ่งนี้ด้วย git?


ในขั้นตอนการปรับใช้ของฉันฉันอัปโหลดเนื้อหา (รูปภาพไฟล์จาวาสคริปต์และไฟล์ css) ไปยัง CDN ชื่อไฟล์แต่ละชื่อจะต่อท้ายด้วยการประทับเวลาที่แก้ไขล่าสุด สิ่งสำคัญคือฉันจะต้องไม่หมดอายุเนื้อหาทั้งหมดทุกครั้งที่ใช้งาน (ผลข้างเคียงอีกประการหนึ่งของการใช้คอมมิตไทม์คือฉันสามารถทำกระบวนการนี้ในเครื่องของฉันและรู้ว่าเซิร์ฟเวอร์ของฉันจะอ้างถึงไฟล์เดียวกัน แต่นั่นไม่สำคัญเท่า) ถ้าแทนที่จะทำ git clone ฉันก็ทำ git fetch ตามด้วยการรีเซ็ต git - ฮาร์ดจาก repo ระยะไกลของฉันซึ่งจะใช้ได้กับเซิร์ฟเวอร์เดียว แต่ไม่ใช่สำหรับเซิร์ฟเวอร์หลายเครื่องเนื่องจากการประทับเวลาของแต่ละเครื่องจะแตกต่างกัน
Ben W

@BenW: git annexอาจมีประโยชน์ในการติดตามภาพ
jfs

คุณสามารถตรวจสอบสิ่งที่เปลี่ยนแปลงได้โดยตรวจสอบรหัส คุณกำลังพยายามทำให้การประทับเวลาของระบบไฟล์มีความหมายเช่นเดียวกับการประทับเวลา vcs พวกเขาไม่ได้หมายถึงสิ่งเดียวกัน
jthill

คำตอบ:


25

ฉันไม่แน่ใจว่าสิ่งนี้จะเหมาะสมสำหรับ DVCS (เช่นเดียวกับใน VCS แบบ "แจกจ่าย")

การอภิปรายครั้งใหญ่เกิดขึ้นแล้วในปี 2550 (ดูหัวข้อนี้)

และคำตอบบางคำของ Linus ก็ไม่กระตือรือร้นกับความคิดนี้มากนัก นี่คือตัวอย่างหนึ่ง:

ฉันขอโทษ. หากคุณไม่เห็นว่ามันผิดที่จะตั้ง datestamp กลับไปยังสิ่งที่จะทำให้ง่าย "ให้" miscompileแหล่งต้นไม้ของคุณผมไม่ทราบว่าสิ่ง defintiion ของ "ผิด" คุณกำลังพูดถึง
มันผิด.
มันโง่.
และเป็นไปไม่ได้เลยที่จะนำไปใช้


(หมายเหตุ: การปรับปรุงเล็กน้อย: หลังจากการชำระเงินการประทับเวลาของไฟล์ที่เป็นปัจจุบันจะไม่มีการแก้ไขอีกต่อไป (Git 2.2.2+, มกราคม 2015): "git checkout - ฉันจะรักษาการประทับเวลาเมื่อเปลี่ยนสาขาได้อย่างไร" )


คำตอบยาวคือ:

ฉันคิดว่าคุณดีกว่ามากเพียงแค่ใช้หลายที่เก็บแทนหากเป็นเรื่องธรรมดา

การยุ่งกับการประทับเวลาจะไม่ได้ผลโดยทั่วไป มันเป็นเพียงแค่จะรับประกันได้ว่า "ให้" ได้รับสับสนในทางที่ไม่ดีจริงๆและไม่ recompile พอแทน recompiling มากเกินไป

Git ช่วยให้คุณสามารถ "ตรวจสอบสาขาอื่น ๆ " ได้อย่างง่ายดายด้วยวิธีต่างๆมากมาย

คุณสามารถสร้างสคริปต์เล็กน้อยที่ทำสิ่งต่อไปนี้ (ตั้งแต่เรื่องเล็กน้อยไปจนถึงสิ่งที่แปลกใหม่กว่า):

  • เพียงสร้าง repo ใหม่:
    git clone old new
    cd new
    git checkout origin/<branch>

และคุณอยู่ที่นั่น การประทับเวลาเก่าใช้ได้ดีใน repo เก่าของคุณและคุณสามารถทำงาน (และคอมไพล์) ในการประทับเวลาใหม่ได้โดยไม่ส่งผลต่อการประทับเวลาเก่าเลย

ใช้แฟล็ก "-n -l -s" เพื่อ "git clone" เพื่อสร้างสิ่งนี้ทันที สำหรับไฟล์จำนวนมาก (เช่น repos ขนาดใหญ่เช่นเคอร์เนล) มันจะไม่เร็วเท่ากับการสลับสาขา แต่การกำหนดสำเนาที่สองของแผนผังการทำงานจะมีประสิทธิภาพมาก

  • ทำสิ่งเดียวกันกับ tar-ball แทนถ้าคุณต้องการ
    git archive --format=tar --prefix=new-tree/ <branchname> |
            (cd .. ; tar xvf -)

ซึ่งค่อนข้างเร็วมากหากคุณต้องการเพียงแค่สแนปชอต

  • คุ้นเคยกับ " git show" และดูที่ไฟล์แต่ละไฟล์
    นี่เป็นประโยชน์จริงในบางครั้ง คุณเพียงแค่ทำ
    git show otherbranch:filename

ในหน้าต่าง xterm เดียวและดูไฟล์เดียวกันในสาขาปัจจุบันของคุณในหน้าต่างอื่น โดยเฉพาะอย่างยิ่งสิ่งนี้ควรทำกับโปรแกรมแก้ไขแบบสคริปต์ (เช่น GNU emacs) ซึ่งโดยพื้นฐานแล้วควรมี "โหมด dired" ทั้งหมดสำหรับสาขาอื่น ๆ ภายในตัวแก้ไขโดยใช้สิ่งนี้ สำหรับสิ่งที่ฉันรู้โหมด git emacs มีสิ่งนี้อยู่แล้ว (ฉันไม่ใช่ผู้ใช้ emacs)

  • และในตัวอย่างที่รุนแรงของ "ไดเรกทอรีเสมือน" นั้นอย่างน้อยก็มีใครบางคนกำลังทำงานกับปลั๊กอิน git สำหรับ FUSE กล่าวคือคุณสามารถมีไดเรกทอรีเสมือนที่แสดงสาขาทั้งหมดของคุณได้

และฉันแน่ใจว่าข้อใดข้อหนึ่งข้างต้นเป็นทางเลือกที่ดีกว่าการเล่นเกมด้วยการประทับเวลาของไฟล์

ไลนัส


5
ตกลง คุณไม่ควรสับสนกับ DVCS กับระบบจำหน่าย gitเป็น DVCS สำหรับจัดการซอร์สโค้ดที่จะสร้างไว้ในผลิตภัณฑ์ขั้นสุดท้ายของคุณ ถ้าคุณต้องการระบบการจัดจำหน่ายคุณรู้ว่าจะหาได้rsyncที่ไหน
Randal Schwartz

14
อืมฉันจะต้องเชื่อคำโต้แย้งของเขาว่ามันไม่มีทางเป็นไปได้ ไม่ว่าจะผิดหรือโง่ แต่ก็เป็นอีกเรื่องหนึ่ง ฉันกำหนดเวอร์ชันไฟล์ของฉันโดยใช้การประทับเวลาและอัปโหลดไปยัง CDN ซึ่งเป็นสาเหตุสำคัญที่การประทับเวลาจะสะท้อนให้เห็นเมื่อไฟล์ได้รับการแก้ไขจริงไม่ใช่เมื่อถูกดึงออกจาก repo ครั้งล่าสุด
Ben W

3
@ เบ็น W: "คำตอบของไลนัส" ไม่ได้อยู่ที่นี่เพื่อบอกว่ามันผิดในสถานการณ์เฉพาะของคุณ มีเพียงเพื่อเป็นการเตือนความจำว่า DVCS ไม่เหมาะอย่างยิ่งสำหรับคุณสมบัติประเภทนั้น (การสงวนเวลาประทับ)
VonC

15
@VonC: เนื่องจาก DVCS สมัยใหม่อื่น ๆ เช่น Bazaar และ Mercurial จัดการการประทับเวลาได้ดีฉันจึงอยากบอกว่า " คอมไพล์ไม่เหมาะกับฟีเจอร์แบบนั้น" หาก "a" DVCS ควรมีคุณลักษณะนั้นเป็นที่ถกเถียงกัน (และฉันคิดอย่างยิ่งว่าพวกเขามี)
MestreLion

10
นี่ไม่ใช่คำตอบสำหรับคำถาม แต่เป็นการอภิปรายเชิงปรัชญาเกี่ยวกับข้อดีของการทำเช่นนี้ในระบบควบคุมเวอร์ชัน หากบุคคลนั้นชอบสิ่งนั้นพวกเขาจะถามว่า "อะไรคือเหตุผลที่ git ไม่ใช้เวลากระทำสำหรับเวลาที่แก้ไขของไฟล์"
thomasfuchs

85

แต่ถ้าคุณจริงๆต้องการที่จะใช้การกระทำครั้ง timestamps เมื่อตรวจสอบจากนั้นให้ลองใช้สคริปต์นี้และวางไว้ (ในขณะที่ปฏิบัติการ) ในแฟ้ม $ GIT_DIR / .git / ตะขอ / โพสต์เช็คเอาท์:

#!/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ควรหลีกเลี่ยงเช่นมัน spawns กระบวนการใหม่-n 1สำหรับgit rev-listและgit logสามารถนำมาใช้แทน
eregon

3
จะดีกว่าที่จะไม่อ่านเส้นที่มี`...`และfor; ดูทำไมคุณไม่อ่านบรรทัดด้วย "สำหรับ" ฉันไปและgit ls-files -z while IFS= read -r -d ''
musiphil

2
เวอร์ชัน Windows เป็นไปได้หรือไม่?
Ehryk

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

81

อัปเดต : ตอนนี้โซลูชันของฉันรวมอยู่ใน Debian / Ubuntu / Mint, Fedora, Gentoo และอาจเป็นไปได้ว่า distros อื่น ๆ :

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 ไม่เก็บ timestamps (และข้อมูลอื่น ๆ เช่นสิทธิ์และความเป็นเจ้าของ) เป็นใหญ่ข้อ จำกัด gitของ

เหตุผลในการประทับเวลาของ Linus เป็นอันตรายเพียงเพราะ "สับสนmake" คือง่อย :

  • make clean ก็เพียงพอที่จะแก้ไขปัญหาต่างๆ

  • ใช้เฉพาะกับโปรเจ็กต์ที่ใช้makeC / C ++ เป็นส่วนใหญ่ มันเป็นที่สงสัยอย่างสมบูรณ์สำหรับสคริปต์เช่น Python, Perl หรือเอกสารโดยทั่วไป

  • จะมีอันตรายหากคุณใช้การประทับเวลาเท่านั้น จะไม่มีอันตรายใด ๆ ในการจัดเก็บไว้ใน repo นำมาใช้อาจจะเป็นที่เรียบง่าย--with-timestampsตัวเลือกสำหรับการgit checkoutและเพื่อน ( clone, pullฯลฯ ) ที่ผู้ใช้ดุลยพินิจ

ทั้ง Bazaar และ Mercurial เก็บข้อมูลเมตา ผู้ใช้สามารถนำไปใช้หรือไม่ก็ได้เมื่อชำระเงิน แต่ในคอมไพล์เนื่องจากการประทับเวลาดั้งเดิมไม่สามารถใช้ได้ใน repo จึงไม่มีตัวเลือกดังกล่าว

ดังนั้นเพื่อให้ได้รับผลตอบแทนเพียงเล็กน้อย (ไม่ต้องคอมไพล์ใหม่ทุกอย่าง) ที่เฉพาะเจาะจงสำหรับชุดย่อยของโครงการgitเนื่องจาก DVCS ทั่วไปถูกทำให้พิการข้อมูลบางส่วนจากไฟล์เกี่ยวกับไฟล์จะหายไปและอย่างที่ Linus กล่าวว่าเป็นไปไม่ได้ที่จะทำ ตอนนี้ เศร้า .

ที่กล่าวว่าขอเสนอ 2 แนวทางได้ไหม

1 - http://repo.or.cz/w/metastore.gitโดย David Härdeman พยายามทำสิ่งที่git ควรทำตั้งแต่แรก : จัดเก็บข้อมูลเมตา (ไม่ใช่แค่การประทับเวลา)ใน repo เมื่อทำการคอมมิชชัน (ผ่าน pre-commits hook) และนำไปใช้ใหม่เมื่อดึง (รวมถึงผ่านตะขอ)

2 - สคริปต์เวอร์ชันต่ำต้อยของฉันที่ฉันเคยใช้มาก่อนเพื่อสร้าง tarballs ที่ปล่อยออกมา ดังที่ได้กล่าวไว้ในคำตอบอื่น ๆ วิธีการจะแตกต่างกันเล็กน้อย : ในการใช้การประทับเวลาของการกระทำล่าสุดที่ไฟล์ถูกแก้ไข

  • 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

ผลการดำเนินงานเป็นที่น่าประทับใจสวยแม้สำหรับโครงการมอนสเตอร์wine, gitหรือแม้กระทั่งเคอร์เนลลินุกซ์:

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 ไม่ timestamps ร้านค้าอื่น ๆ มันก็ไม่ได้ตั้งค่าการประทับเวลาไปโดยปริยาย เพียงแค่ดูที่ผลลัพธ์ของgit ls-files --debug
Ross Smith II

9
@RossSmithII: git ls-filesทำงานบนไดเร็กทอรีและดัชนีการทำงานดังนั้นจึงไม่ได้หมายความว่าจะเก็บข้อมูลนั้นไว้ใน repo หากมีการจัดเก็บการเรียก (และการใช้) mtime จะเป็นเรื่องเล็กน้อย
MestreLion

14
"เหตุผลของการประทับเวลาของ Linus เป็นอันตรายเพียงเพราะ" ทำให้สับสน "เป็นง่อย" - เห็นด้วย 100% DCVS ไม่ควรรู้หรือสนใจเกี่ยวกับรหัสที่มีอยู่! อีกครั้งนี่แสดงให้เห็นถึงข้อผิดพลาดของการพยายามนำเครื่องมือที่เขียนขึ้นสำหรับกรณีการใช้งานเฉพาะไปใช้ในกรณีใช้งานทั่วไป Mercurial เป็นตัวเลือกที่เหนือกว่าเสมอเพราะได้รับการออกแบบมาไม่ได้รับการพัฒนา
Ian Kemp

6
@davec ยินดีต้อนรับดีใจที่มีประโยชน์ เวอร์ชันเต็มได้ที่github.com/MestreLion/git-toolsแล้วจัดการกับ Windows, Python 3, ชื่อพา ธ ที่ไม่ใช่ ASCII เป็นต้นสคริปต์ข้างต้นเป็นเพียงข้อพิสูจน์แนวคิดที่ใช้งานได้โปรดหลีกเลี่ยงการนำไปใช้ในการผลิต
MestreLion

3
ข้อโต้แย้งของคุณถูกต้อง ฉันหวังว่าใครบางคนที่มีอิทธิพลบางอย่างจะส่งคำขอเพิ่มประสิทธิภาพเพื่อให้คอมไพล์มีตัวเลือก - พร้อมประทับเวลาที่คุณแนะนำ
weberjn

12

ฉันรับคำตอบของ Giel และแทนที่จะใช้สคริปต์โพสต์คอมมิต hook ใช้มันในสคริปต์การปรับใช้ที่กำหนดเองของฉัน

อัปเดต : ฉันได้ลบ| head -nข้อเสนอแนะต่อไปนี้ของ @ eregon และเพิ่มการรองรับไฟล์ที่มีช่องว่าง:

# 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

--abbrev-commitเป็นฟุ่มเฟือยในgit showคำสั่งเนื่องจาก--pretty=format:%aiถูกนำมาใช้ (กัญชากระทำไม่เป็นส่วนหนึ่งของการส่งออก) และ| head -n 1จะถูกแทนที่ด้วยการใช้-sธงgit show
Elan Ruusamäe

1
@ DanielS.Sterling: %aiเป็นวันที่เขียน ISO 8601 เหมือนรูปแบบสำหรับการใช้ iso8601 ที่เข้มงวด%aI : git-scm.com/docs/git-show
Elan Ruusamäe

4

เราถูกบังคับให้คิดค้นวิธีแก้ปัญหาอื่นเนื่องจากเราต้องการเวลาในการปรับเปลี่ยนโดยเฉพาะและไม่ต้องใช้เวลาและโซลูชันนี้ยังต้องพกพาได้ด้วย (เช่นการทำให้ python ทำงานในการติดตั้งคอมไพล์ของ windows ไม่ใช่เรื่องง่าย) และรวดเร็ว มันคล้ายกับโซลูชันของ David Hardeman ซึ่งฉันตัดสินใจที่จะไม่ใช้เนื่องจากไม่มีเอกสารประกอบ (จากที่เก็บฉันไม่สามารถเข้าใจได้ว่ารหัสของเขาทำอะไรกันแน่)

โซลูชันนี้เก็บ mtimes ไว้ในไฟล์. mtimes ในที่เก็บ git อัปเดตตามการคอมมิต (jsut เลือก mtimes ของไฟล์ที่จัดฉาก) และนำไปใช้ในการชำระเงิน ทำงานได้แม้กับ git เวอร์ชัน cygwin / mingw (แต่คุณอาจต้องคัดลอกไฟล์บางไฟล์จาก cygwin มาตรฐานไปยังโฟลเดอร์ของ git)

โซลูชันประกอบด้วย 3 ไฟล์:

  1. mtimestore - สคริปต์หลักที่มี 3 ตัวเลือก -a (บันทึกทั้งหมด - สำหรับการเริ่มต้นใน repo ที่มีอยู่แล้ว (ทำงานกับไฟล์ที่มีประสบการณ์ด้าน git), -s (เพื่อบันทึกการเปลี่ยนแปลงแบบทีละขั้น) และ -r เพื่อเรียกคืน สิ่งนี้มีให้เลือก 2 เวอร์ชัน - bash one (แบบพกพา, ดี, อ่านง่าย / แก้ไข) และรุ่น c (ยุ่ง แต่เร็วเพราะ mingw bash ช้าอย่างน่ากลัวซึ่งทำให้ไม่สามารถใช้ bash solution ในโครงการใหญ่ ๆ ได้)
  2. เบ็ดก่อนกระทำ
  3. เบ็ดหลังการชำระเงิน

การกระทำล่วงหน้า:

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

หลังการชำระเงิน

#!/bin/bash
mtimestore -r

mtimestore - ทุบตี:

#!/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;
}
  • โปรดทราบว่าสามารถวาง hooks ลงใน template-directory เพื่อจัดวางตำแหน่งได้โดยอัตโนมัติ

ดูข้อมูลเพิ่มเติมได้ที่นี่ https://github.com/kareltucek/git-mtime-extension ข้อมูล บางส่วนที่ล้าสมัยอยู่ที่ http://www.ktweb.cz/blog/index.php?page=page&id=116

// แก้ไข - ปรับปรุงเวอร์ชัน c ++:

  • ตอนนี้เวอร์ชัน c ++ รักษาการเรียงลำดับตามตัวอักษร -> ความขัดแย้งในการผสานน้อยลง
  • กำจัดระบบที่น่าเกลียด () การโทร
  • ลบ $ git update-index - รีเฟรช $ จาก post-checkout hook ทำให้เกิดปัญหาบางอย่างกับการเปลี่ยนกลับภายใต้คอมไพล์ของเต่าและดูเหมือนจะไม่สำคัญมากนัก
  • สามารถดาวน์โหลดแพ็คเกจ windows ของเราได้ที่http://ktweb.cz/blog/download/git-mtimestore-1.4.rar

// แก้ไขดู github สำหรับเวอร์ชันล่าสุด


1
โปรดทราบว่าหลังจากการชำระเงินการประทับเวลาของไฟล์ที่อัปเดตจะไม่มีการแก้ไขอีกต่อไป (Git 2.2.2+ มกราคม 2015): 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

มันต้องการอีกหนึ่งบรรทัดสุดท้าย: git update-index --refresh // เครื่องมือ GUI อาจอาศัยดัชนีและแสดงสถานะ "สกปรก" ให้กับไฟล์ทั้งหมดหลังจากการดำเนินการดังกล่าว ได้แก่ ที่เกิดขึ้นใน TortoiseGit สำหรับ Windows code.google.com/p/tortoisegit/issues/detail?id=861
Arioch 'The

1
และขอบคุณสำหรับสคริปต์ ฉันหวังว่าสคริปต์ดังกล่าวเป็นส่วนหนึ่งของโปรแกรมติดตั้งมาตรฐาน Git ไม่ใช่ว่าฉันต้องการมันเป็นการส่วนตัว แต่สมาชิกในทีมเพียงแค่รู้สึกว่าการประทับเวลาแสดงเป็นแบนเนอร์ "หยุด" สีแดงในการนำ VCS มาใช้
Arioch '

3

โซลูชันนี้ควรทำงานได้อย่างรวดเร็ว มันกำหนดเวลาในการคอมมิตไทม์และเวลาในการเขียน ไม่ใช้โมดูลดังนั้นควรพกพาได้อย่างสมเหตุสมผล

#!/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 ls-files แทนที่จะดูในไดเร็กทอรีการทำงาน วิธีนี้ช่วยแก้ปัญหาการยกเว้น.gitและยังแก้ปัญหาไฟล์ที่ไม่ได้ติดตาม git log -mนอกจากนี้ยังมีคำตอบที่ล้มเหลวถ้าสุดท้ายกระทำของไฟล์เป็นผสานกระทำซึ่งผมแก้ไขได้ด้วย เช่นเดียวกับคำตอบอื่น ๆ จะหยุดเมื่อพบไฟล์ทั้งหมดดังนั้นจึงไม่จำเป็นต้องอ่านคอมมิตทั้งหมด ตัวอย่างเช่น:

https://github.com/git/git

เมื่อโพสต์นี้จะต้องอ่าน 292 คอมมิตเท่านั้น นอกจากนี้ยังละเว้นไฟล์เก่าจากประวัติตามต้องการและจะไม่แตะต้องไฟล์ที่มีการสัมผัสแล้ว ในที่สุดดูเหมือนว่าจะเร็วกว่าโซลูชันอื่นเล็กน้อย ผลลัพธ์กับgit/gitrepo:

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

0

ฉันเห็นคำขอบางอย่างสำหรับเวอร์ชัน Windows ดังนั้นนี่คือ สร้างสองไฟล์ต่อไปนี้:

C: \ Program Files \ Git \ mingw64 \ share \ git-core \ template \ 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 \ template \ 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 สำหรับแต่ละไฟล์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.