Skip to content

Instantly share code, notes, and snippets.

@aleung
Last active June 15, 2020 11:15
Show Gist options
  • Save aleung/65dd468ec9a5d39f64862cab6a0b09ba to your computer and use it in GitHub Desktop.
Save aleung/65dd468ec9a5d39f64862cab6a0b09ba to your computer and use it in GitHub Desktop.
Retrieve a file or a directory from git history at specific reversion
#!/usr/bin/env python3
#
# Usage:
# git-history-extract.py [-o <output_path>] <rev> [<path>]
#
# Retrieve a file or a directory from git history at specific reversion (tag or commit).
# The restored file(s) are saved into destination path.
#
# You must run it in a directory which is inside a git repository.
#
# Limitations:
# - Not support LFS
# - Not support symbolic link
import subprocess
import argparse
import os
def parse_arguments():
parser = argparse.ArgumentParser(
description='Retrieve a file or a directory from git history at specific reversion')
parser.add_argument('-o', '--output-path',
help='Path to save restored file. Print file content at console if not specify.')
parser.add_argument('rev',
help='Revision of the commit to retrieve, can be SHA1 or tag. Default to current directory.')
parser.add_argument('path', nargs='?', default='./',
help='Path to the file or directory to retrieve')
return parser.parse_args()
def save_file(output_path, file_path, content, executable):
full_file_path = os.path.join(output_path, file_path)
dir_name = os.path.dirname(full_file_path)
print('Save {}'.format(full_file_path))
os.makedirs(dir_name, exist_ok=True)
with open(full_file_path, 'wb') as file:
file.write(content)
if executable:
os.chmod(full_file_path, 0o755)
if __name__ == '__main__':
args = parse_arguments()
ls_tree = subprocess.check_output(
['git', 'ls-tree', '-rz', args.rev, args.path]).decode().split('\0')
for line in ls_tree:
if line:
meta, file = line.split('\t')
mode, _, object = meta.split(' ')
content = subprocess.check_output(
['git', 'cat-file', 'blob', object])
if args.output_path:
save_file(args.output_path, file, content,
executable=(mode == '100755'))
else:
print('\n>>>>> {}'.format(file))
try:
print(content.decode())
except:
print('[ ... binary content ... ]')
# Example: output to console
$ git-history-extract.py v3
>>>>> 1658350664.jpg
[ ... binary content ... ]
>>>>> dir1.1/dir with space/name with space.x.y.txt
This file has LF ending.
LF
>>>>> dir1.1/script.sh
echo Runnable
>>>>> dir1.1/中文文件名.txt
This file has CRLF ending.
中文
hello
# Example: output to folder
$ git-history-extract.py v3 -o ../
Save ../1658350664.jpg
Save ../dir1.1/dir with space/name with space.x.y.txt
Save ../dir1.1/script.sh
Save ../dir1.1/中文文件名.txt
$ ls -l dir1.1
total 16
drwxr-xr-x 3 myid staff 96 Jun 12 14:05 dir with space
-rwxr-xr-x 1 myid staff 13 Jun 11 16:03 script.sh
-rw-r--r-- 1 myid staff 43 Jun 12 18:24 中文文件名.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment