Skip to content

Instantly share code, notes, and snippets.

@gagern
Created April 25, 2014 16:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gagern/11294875 to your computer and use it in GitHub Desktop.
Save gagern/11294875 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
# (c) Martin von Gagern 2014
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
import hashlib
import mmap
import os
import os.path
import stat
import sys
def digest(path, stat):
h = hashlib.sha1()
if stat.st_size:
with open(path, "rb") as f:
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as m:
h.update(m)
return h.hexdigest()
def save():
for path in sys.stdin:
path = path.rstrip("\n")
s = os.stat(path)
if not stat.S_ISREG(s.st_mode):
continue
d = [digest(path, s), s.st_mtime_ns, s.st_size, path]
print(" ".join(map(str, d)))
def restore():
for line in sys.stdin:
hash, mtime, size, path = line.rstrip("\n").split(" ", 3)
try:
s = os.stat(path)
except FileNotFoundError:
continue
s = os.stat(path)
if not stat.S_ISREG(s.st_mode):
continue
mtime = int(mtime)
size = int(size)
if (s.st_mtime_ns != mtime and
s.st_size == size and hash == digest(path, s)):
# File timestamp changed but not its content, restore mtime
os.utime(path, ns=(s.st_atime_ns, mtime))
def usage():
print("""\
Usage: {0} {{save|restore}}
This tool stores modification timestamps of files, and restores them
after verifying (using a cryptographic hash) that the file content is
the same.
Example:
git ls-files | {0} save > mtimes.txt
git checkout old-feature-branch
git merge master
{0} restore < mtimes.txt
rm mtimes.txt
make
In this examle, files which are not affected by the changes of
old-feature-branch won't cause a recompilation in make, even though they
might have been modified in master and switching branch will temporarily
change their content.\
""".format(os.path.basename(sys.argv[0])))
if __name__ == "__main__":
if len(sys.argv) != 2:
usage()
sys.exit(1)
operation = sys.argv[1]
if operation == "save":
save()
elif operation == "restore":
restore()
else:
usage()
sys.exit(1)
sys.exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment