Is there a way to know or get the original create/modified timestamps?
- 3this is a cleaner page, but both question and most voted answer are basically duplicated: stackoverflow.com/questions/1964470/…cregox– cregox2013-07-25 20:51:59 +00:00Commented Jul 25, 2013 at 20:51
- 1git.wiki.kernel.org/index.php/…ashwinik001– ashwinik0012017-12-29 06:29:16 +00:00Commented Dec 29, 2017 at 6:29
- 2Does this answer your question? What's the equivalent of use-commit-times for git?NoDataDumpNoContribution– NoDataDumpNoContribution2020-12-15 10:51:31 +00:00Commented Dec 15, 2020 at 10:51
- The answers to Git clone changes file modification time may be more practical and to the point (command-line one liners and similar).Peter Mortensen– Peter Mortensen2021-10-23 12:34:20 +00:00Commented Oct 23, 2021 at 12:34
17 Answers
YES, metastore or git-cache-meta can store such (meta-)information! Git by itself, without third-party tools, can't. Metastore or git-cache-meta can store any file metadata for a file.
That is by design, as metastore or git-cache-meta are intended for that very purpose, as well as supporting backup utilities and synchronization tools.
6 Comments
find's -printf extension, and I'm almost certain that metastore (being a C project) is even more work to make portable. Quite unfortunate. I will post back here if I find out that this situation changes.git-cache-meta turned into a repository with updates; now it is installable as a git hook, so it will automatically store metadata on every commit!I believe that the only timestamps recorded in the Git database are the author and commit timestamps. I don't see an option for Git to modify the file's timestamp to match the most recent commit, and it makes sense that this wouldn't be the default behavior (because if it were, Makefiles wouldn't work correctly).
You could write a script to set the modification date of your files to the the time of the most recent commit. It might look something like this:
# No arguments? Recursively list all git-controlled files in $PWD and start over if [ $# = 0 ]; then git ls-files -z |xargs -0 sh "$0" exit $? fi for file in "$@"; do time="$(git log --pretty=format:%cd -n 1 \ --date=format:%Y%m%d%H%M.%S --date-order -- "$file")" if [ -z "$time" ]; then echo "ERROR: skipping '$file' -- no git log found" >&2 continue fi touch -m -t "$time" "$file" done This accepts specific files as arguments or else updates each git-controlled file in the current directory or its children. This is done in a manner that permits spaces and even line breaks in filenames since git ls-files -z outputs a null-terminated file list and xargs -0 parses null-terminated lists into arguments.
This will take a while if you have a lot of files.
25 Comments
NO, Git simply does not store such (meta-)information, unless you use third-party tools like metastore or git-cache-meta. The only timestamp that get stored is the time a patch/change was created (author time), and the time the commit was created (committer time).
That is by design, as Git is a version control system, not a backup utility or synchronization tool.
5 Comments
UPDATE: TL;DR: Git itself does not save original times, but some solutions circumvent this by various methods. git-restore-mtime is one of them.
Ubuntu and Debian: sudo apt install git-restore-mtime Fedora, Red Hat Enterprise Linux (RHEL), and CentOS: sudo yum install git-tools
See my other answer for more details.
Full disclaimer: I'm the author of git-tools
This Python script may help: for each file, it applies the timestamp of the most recent commit where the file was modified:
- Core functionality, with --help, debug messages. Can be run anywhere within the work tree
- Full-fledged beast, with lots of options. Supports any repository layout.
Below is a really bare-bones version of the script. For actual usage I strongly suggest one of the more robust versions above:
#!/usr/bin/env python # Bare-bones version. The current directory 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 All versions parse the full log generated by a single git whatchanged command, which is hundreds of times faster than lopping for each file. It is under four seconds for Git (24,000 commits, 2,500 files) and less than one minute for the Linux kernel (40,000 files and 300,000 commits).
6 Comments
$ 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 API Would you mind maybe telling us what version of Python is needed? I'm using 3.3.3str in Python 2 is the equivalent of bytestring in Python 3 , while str in Python 3 is unicode in Python 2. Can you please report this issue at github.com/MestreLion/git-tools/issues ?This did the trick for me on Ubuntu (which lacks OS X's "-j" flag on date(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 1 Comment
-d flag instead of -t. So: for FILE in $(git ls-files) ; do TIME=$(git log --pretty=format:%cd -n 1 --date=iso $FILE) ; touch -m -d "$TIME" $FILE ; doneNative Git doesn't have the functionality, but it can be achieved by hook scripts or third-party tools.
I've tried metastore. It's very fast, but I don't like the need to install and that metadata are not stored in plain text format. git-cache-meta is a simple tool I've tried, but it's extremely slow for large repositories (for a repository with tens of thousands of files, it takes minutes to update the metadata file) and could have cross-platform compatibility issues. setgitperms and other approaches also have their shortcomings that I don't like.
At last, I made a hook script for this job: git-store-meta. It has very light dependency (*nix shell, sort, and perl, which is required by Git, and optionally chown, chgrp and touch), so that nothing additional have to be installed for a platform that can run Git, desirable performance (for a repository with tens of thousands of files, it takes < 10 seconds to update the metadata file; although longer to create), saves data in plain text format, and which metadata to be "saved" or "loaded" is customizable.
It has worked fine for me. Try this if you are not satisfied with metastore, git-cache-meta, and other approaches.
3 Comments
--install hooks do not seem to start working until I manually run git-store-meta.pl --store the first time.--init flag, after which the hooks can work normally? Just a suggestion...I have been skirmishing with git and file timestamps for some time already.
Tested some of your ideas and made my own awfully huge and predecessor/ram heavy scripts, untill i found (on some git wiki) a script in perl that does almost what i wanted. https://git.wiki.kernel.org/index.php/ExampleScripts
And what i wanted is to be able to preserve last modification of files based on commit dates.
So after some readjustment the script is able to change creation and modification date of 200k files in around 2-3min.
#!/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; Assuming that your repositories wont have 10k+ files this should take seconds to execute, so you can hook it to the checkout, pull or other git basic hooks.
Comments
I hope you appreciate the simplicity:
# 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 1 Comment
Contrary to other solutions that set mtime to commit time, git-store-meta saves meta data like mtime into a .git_store_meta file that is added to the repository. It can install git hooks to the current repository that save and apply metadata automatically.
Comments
For a Windows environment, I wrote a small (quick and dirty) EXE file in Delphi 10.1 Berlin that collects all file dates in the source tree into the file .gitfilattr and can apply them on the checked our source tree again.
The code is on GitHub:
https://github.com/michaschumann/gitfiledates/blob/master/gitFileDates.dpr
I use it in my build system based on GitLab runners.
Comments
Here is my solution that takes into consideration paths that contain spaces:
#! /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 Note that this does not take the time which git log reports; it's the time reported by the system. If you want the time since the files were committed use git log solution instead of date -r
Comments
Git doesn't support storing file dates.
But you can use git-meta, which is git-cache-meta turned into a up-to-date repository (all of the comments in the gist were implemented); now it is installable as a Git hook, so it will automatically store metadata on every commit!
So, Git doesn't support storing files' metadata by default; but it doesn't mean you can't be modding it with custom features (LFS is a proof of how you can extend Git).
1 Comment
In CentOS 7 you have /usr/share/doc/rsync-*/support/git-set-file-times and in Debian (and derivatives) the same script in /usr/share/doc/rsync/scripts/git-set-file-times.gz. The original is from Eric Wong and is at https://yhbt.net/git-set-file-times.
It works faster than other examples mentioned here and you may find it more handy to have it already on your Linux distribution.
1 Comment
/usr/share/rsync/scripts/git-set-file-times. It is Python3 and not Perl, and it originates from the rsync package.There's some ambiguity in my (and others') interpretation of the OP about whether this means the commit time or something else, but assuming it means commit time, then this simple one-liner will work in Linux (based on answer snippet from Dietrich Epp):
git ls-files | xargs -I{} bash -c 'touch "{}" --date=@$(git log -n1 --pretty=format:%ct -- "{}")' But there are more sophisticated answers (including Git hooks) linked from a comment to the original question by cregox.
1 Comment
--date=@fooWith GNU tools.
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 <[email protected]> GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu) 1 Comment
for file in `find . -type f -not -path "./.git/*"`; do touch -d "`git rev-list -n 1 HEAD \$file | xargs git show -s --format=%ai`" $file; done Comments
Here's mine.
A little quicker than some others, as I'm not calling 'get log' for each file found; instead, calling 'git log' once and transforming that output into touch commands.
There'll be cases where there are too many listed files in 1 commit to fit into a single shell command buffer; run "getconf ARG_MAX" to see the max length of a command in bytes - on my Debian install, it's 2 MB, which is plenty.
# 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 - Description by line:
- earliest-first list of commits and filenames
- filter out unneeded commit/merge/author lines
- filter out lines starting with double-dash
- sed (stream-edit) command a) prepend/append double-quote to lines, and b) replace "Date: ." with ~touch -c -m -d. ( the touch command options are -c = don't create if it doesn't exist, -m = change file modification time, and -d = use the provided date/time )
- translate tilde (~) and newline (\n) characters to newline and space, respectively
- pipe the resulting stream of text lines into a shell.
In terms of speed, it 5 seconds 1700 commits for 6500 files in 700 directories.