Skip to content

Instantly share code, notes, and snippets.

@VarunBarad
Created March 3, 2019 10:33
Show Gist options
  • Save VarunBarad/c291e98dd426b0da1322171290d7bbd0 to your computer and use it in GitHub Desktop.
Save VarunBarad/c291e98dd426b0da1322171290d7bbd0 to your computer and use it in GitHub Desktop.
Script to create a single-file backup of a git repository
#!/usr/bin/python3
import os
import sys
import re
import shutil
from git import Repo
from zipfile import ZipFile
def create_backup_zip(directory_to_backup):
parent_directory, directory_name = os.path.split(directory_to_backup)
repository = Repo(directory_to_backup)
git_handle = repository.git
git_bundle_file_name = os.path.join(parent_directory, (directory_name + '.bundle'))
git_handle.bundle('create', git_bundle_file_name, '--all')
pattern_for_untracked_files_flag = re.compile(r'^\?+$')
non_staged_files_raw_output = git_handle.status('--porcelain')
if len(non_staged_files_raw_output.strip()) > 0:
non_staged_files = list(filter(
lambda x: pattern_for_untracked_files_flag.match(x[0]) is None,
list(map(lambda _: _.strip().split(), non_staged_files_raw_output.split(os.linesep)))
))
else:
non_staged_files = []
if len(non_staged_files) > 0:
git_handle.stash('push', '-m', 'git-backup-stash')
stashes = git_handle.stash('list').strip()
stash_list = stashes.split(os.linesep)
patch_files = []
if (len(stashes) > 0) and (len(stash_list) > 0):
patch_files_directory = os.path.join(parent_directory, 'patch-files')
os.makedirs(patch_files_directory, exist_ok=True)
for stash in stash_list:
stash_name = stash.split(': ')[0]
stash_branch_name = stash.split(': ')[1].split()[1]
stash_message = ': '.join(stash.split(': ')[2:])
patch_file_name = ':'.join([stash_name, stash_branch_name, stash_message]) + '.txt'
patch_file_path = os.path.join(patch_files_directory, patch_file_name)
patch_contents = git_handle.stash('show', '-p', stash_name)
with open(patch_file_path, 'x') as f:
f.write(patch_contents)
patch_files.append(patch_file_path)
zip_file_name = directory_name + '.zip'
zip_file_path = os.path.join(parent_directory, zip_file_name)
with ZipFile(zip_file_path, 'x') as z:
z.write(git_bundle_file_name, arcname=os.path.split(git_bundle_file_name)[1])
for patch_file in patch_files:
z.write(patch_file, arcname=os.path.join('patch-files', os.path.split(patch_file)[1]))
os.remove(git_bundle_file_name)
if len(patch_files) > 0:
shutil.rmtree(patch_files_directory)
print(zip_file_name + ' created')
if __name__ == "__main__":
arguments = sys.argv[1:]
if len(arguments) == 0:
directory_to_backup = os.getcwd()
else:
directory_to_backup = arguments[0]
directory_to_backup = os.path.abspath(directory_to_backup)
git_directory = os.path.join(directory_to_backup, '.git')
if os.path.exists(git_directory) and os.path.isdir(git_directory):
create_backup_zip(directory_to_backup)
else:
print(directory_to_backup + ' is not a git repository')
@flandersen
Copy link

I want to backup my bare git repos and give it a try, but this script cannot handle those.

@VarunBarad
Copy link
Author

@flandersen I could not understand what you mean by "bare git repos". Can you please explain a bit more?

@flandersen
Copy link

@VarunBarad I want to backup my repos on my server which are created the following:

git init --bare <directory>

The --bare flag creates a repository that doesn’t have a working directory, making it impossible to edit files and commit changes in that repository. You would create a bare repository to git push and git pull from, but never directly commit to it.
https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-init

@VarunBarad
Copy link
Author

@flandersen I guess bare repos won't have any stashes. In that case all you need should be handled by git bundle

https://git-scm.com/docs/git-bundle

@flandersen
Copy link

@VarunBarad Thank you! I have tested git-bundle and it works fine.

If you are interested, here is the script I came up with which fits my purpose:
https://gist.github.com/flandersen/6d33c609de80247c9059bf8c3e2ade05

@VarunBarad
Copy link
Author

Thanks @flandersen

I really liked how you have put the documentation at the top of the script in comments 💯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment