mirror of
https://github.com/josegonzalez/python-github-backup.git
synced 2025-12-11 18:41:11 +01:00
Merge pull request #466 from Iamrodos/fix/112-windows-support
fix: add Windows support with entry_points and os.replace
This commit is contained in:
@@ -1,76 +1,18 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Backwards-compatible wrapper script.
|
||||||
|
|
||||||
|
The recommended way to run github-backup is via the installed command
|
||||||
|
(pip install github-backup) or python -m github_backup.
|
||||||
|
|
||||||
|
This script is kept for backwards compatibility with existing installations
|
||||||
|
that may reference this path directly.
|
||||||
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from github_backup.github_backup import (
|
from github_backup.cli import main
|
||||||
backup_account,
|
from github_backup.github_backup import logger
|
||||||
backup_repositories,
|
|
||||||
check_git_lfs_install,
|
|
||||||
filter_repositories,
|
|
||||||
get_auth,
|
|
||||||
get_authenticated_user,
|
|
||||||
logger,
|
|
||||||
mkdir_p,
|
|
||||||
parse_args,
|
|
||||||
retrieve_repositories,
|
|
||||||
)
|
|
||||||
|
|
||||||
# INFO and DEBUG go to stdout, WARNING and above go to stderr
|
|
||||||
log_format = logging.Formatter(
|
|
||||||
fmt="%(asctime)s.%(msecs)03d: %(message)s",
|
|
||||||
datefmt="%Y-%m-%dT%H:%M:%S",
|
|
||||||
)
|
|
||||||
|
|
||||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
|
||||||
stdout_handler.setLevel(logging.DEBUG)
|
|
||||||
stdout_handler.addFilter(lambda r: r.levelno < logging.WARNING)
|
|
||||||
stdout_handler.setFormatter(log_format)
|
|
||||||
|
|
||||||
stderr_handler = logging.StreamHandler(sys.stderr)
|
|
||||||
stderr_handler.setLevel(logging.WARNING)
|
|
||||||
stderr_handler.setFormatter(log_format)
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, handlers=[stdout_handler, stderr_handler])
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = parse_args()
|
|
||||||
|
|
||||||
if args.private and not get_auth(args):
|
|
||||||
logger.warning(
|
|
||||||
"The --private flag has no effect without authentication. "
|
|
||||||
"Use -t/--token, -f/--token-fine, or -u/--username to authenticate."
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.quiet:
|
|
||||||
logger.setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
output_directory = os.path.realpath(args.output_directory)
|
|
||||||
if not os.path.isdir(output_directory):
|
|
||||||
logger.info("Create output directory {0}".format(output_directory))
|
|
||||||
mkdir_p(output_directory)
|
|
||||||
|
|
||||||
if args.lfs_clone:
|
|
||||||
check_git_lfs_install()
|
|
||||||
|
|
||||||
if args.log_level:
|
|
||||||
log_level = logging.getLevelName(args.log_level.upper())
|
|
||||||
if isinstance(log_level, int):
|
|
||||||
logger.root.setLevel(log_level)
|
|
||||||
|
|
||||||
if not args.as_app:
|
|
||||||
logger.info("Backing up user {0} to {1}".format(args.user, output_directory))
|
|
||||||
authenticated_user = get_authenticated_user(args)
|
|
||||||
else:
|
|
||||||
authenticated_user = {"login": None}
|
|
||||||
|
|
||||||
repositories = retrieve_repositories(args, authenticated_user)
|
|
||||||
repositories = filter_repositories(args, repositories)
|
|
||||||
backup_repositories(args, output_directory, repositories)
|
|
||||||
backup_account(args, output_directory)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
|
|||||||
13
github_backup/__main__.py
Normal file
13
github_backup/__main__.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"""Allow running as: python -m github_backup"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from github_backup.cli import main
|
||||||
|
from github_backup.github_backup import logger
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
sys.exit(1)
|
||||||
82
github_backup/cli.py
Normal file
82
github_backup/cli.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Command-line interface for github-backup."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from github_backup.github_backup import (
|
||||||
|
backup_account,
|
||||||
|
backup_repositories,
|
||||||
|
check_git_lfs_install,
|
||||||
|
filter_repositories,
|
||||||
|
get_auth,
|
||||||
|
get_authenticated_user,
|
||||||
|
logger,
|
||||||
|
mkdir_p,
|
||||||
|
parse_args,
|
||||||
|
retrieve_repositories,
|
||||||
|
)
|
||||||
|
|
||||||
|
# INFO and DEBUG go to stdout, WARNING and above go to stderr
|
||||||
|
log_format = logging.Formatter(
|
||||||
|
fmt="%(asctime)s.%(msecs)03d: %(message)s",
|
||||||
|
datefmt="%Y-%m-%dT%H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stdout_handler.setLevel(logging.DEBUG)
|
||||||
|
stdout_handler.addFilter(lambda r: r.levelno < logging.WARNING)
|
||||||
|
stdout_handler.setFormatter(log_format)
|
||||||
|
|
||||||
|
stderr_handler = logging.StreamHandler(sys.stderr)
|
||||||
|
stderr_handler.setLevel(logging.WARNING)
|
||||||
|
stderr_handler.setFormatter(log_format)
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, handlers=[stdout_handler, stderr_handler])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for github-backup CLI."""
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
if args.private and not get_auth(args):
|
||||||
|
logger.warning(
|
||||||
|
"The --private flag has no effect without authentication. "
|
||||||
|
"Use -t/--token, -f/--token-fine, or -u/--username to authenticate."
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.quiet:
|
||||||
|
logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
output_directory = os.path.realpath(args.output_directory)
|
||||||
|
if not os.path.isdir(output_directory):
|
||||||
|
logger.info("Create output directory {0}".format(output_directory))
|
||||||
|
mkdir_p(output_directory)
|
||||||
|
|
||||||
|
if args.lfs_clone:
|
||||||
|
check_git_lfs_install()
|
||||||
|
|
||||||
|
if args.log_level:
|
||||||
|
log_level = logging.getLevelName(args.log_level.upper())
|
||||||
|
if isinstance(log_level, int):
|
||||||
|
logger.root.setLevel(log_level)
|
||||||
|
|
||||||
|
if not args.as_app:
|
||||||
|
logger.info("Backing up user {0} to {1}".format(args.user, output_directory))
|
||||||
|
authenticated_user = get_authenticated_user(args)
|
||||||
|
else:
|
||||||
|
authenticated_user = {"login": None}
|
||||||
|
|
||||||
|
repositories = retrieve_repositories(args, authenticated_user)
|
||||||
|
repositories = filter_repositories(args, repositories)
|
||||||
|
backup_repositories(args, output_directory, repositories)
|
||||||
|
backup_account(args, output_directory)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
sys.exit(1)
|
||||||
@@ -1038,7 +1038,7 @@ def download_attachment_file(url, path, auth, as_app=False, fine=False):
|
|||||||
bytes_downloaded += len(chunk)
|
bytes_downloaded += len(chunk)
|
||||||
|
|
||||||
# Atomic rename to final location
|
# Atomic rename to final location
|
||||||
os.rename(temp_path, path)
|
os.replace(temp_path, path)
|
||||||
|
|
||||||
metadata["size_bytes"] = bytes_downloaded
|
metadata["size_bytes"] = bytes_downloaded
|
||||||
metadata["success"] = True
|
metadata["success"] = True
|
||||||
@@ -1459,7 +1459,7 @@ def download_attachments(
|
|||||||
|
|
||||||
# Rename to add extension (already atomic from download)
|
# Rename to add extension (already atomic from download)
|
||||||
try:
|
try:
|
||||||
os.rename(filepath, final_filepath)
|
os.replace(filepath, final_filepath)
|
||||||
metadata["saved_as"] = os.path.basename(final_filepath)
|
metadata["saved_as"] = os.path.basename(final_filepath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -1490,7 +1490,7 @@ def download_attachments(
|
|||||||
manifest_path = os.path.join(attachments_dir, "manifest.json")
|
manifest_path = os.path.join(attachments_dir, "manifest.json")
|
||||||
with open(manifest_path + ".temp", "w") as f:
|
with open(manifest_path + ".temp", "w") as f:
|
||||||
json.dump(manifest, f, indent=2)
|
json.dump(manifest, f, indent=2)
|
||||||
os.rename(manifest_path + ".temp", manifest_path) # Atomic write
|
os.replace(manifest_path + ".temp", manifest_path) # Atomic write
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Wrote manifest for {0} #{1}: {2} attachments".format(
|
"Wrote manifest for {0} #{1}: {2} attachments".format(
|
||||||
item_type_display, number, len(attachment_metadata_list)
|
item_type_display, number, len(attachment_metadata_list)
|
||||||
@@ -1811,7 +1811,7 @@ def backup_issues(args, repo_cwd, repository, repos_template):
|
|||||||
|
|
||||||
with codecs.open(issue_file + ".temp", "w", encoding="utf-8") as f:
|
with codecs.open(issue_file + ".temp", "w", encoding="utf-8") as f:
|
||||||
json_dump(issue, f)
|
json_dump(issue, f)
|
||||||
os.rename(issue_file + ".temp", issue_file) # Unlike json_dump, this is atomic
|
os.replace(issue_file + ".temp", issue_file) # Atomic write
|
||||||
|
|
||||||
|
|
||||||
def backup_pulls(args, repo_cwd, repository, repos_template):
|
def backup_pulls(args, repo_cwd, repository, repos_template):
|
||||||
@@ -1886,7 +1886,7 @@ def backup_pulls(args, repo_cwd, repository, repos_template):
|
|||||||
|
|
||||||
with codecs.open(pull_file + ".temp", "w", encoding="utf-8") as f:
|
with codecs.open(pull_file + ".temp", "w", encoding="utf-8") as f:
|
||||||
json_dump(pull, f)
|
json_dump(pull, f)
|
||||||
os.rename(pull_file + ".temp", pull_file) # Unlike json_dump, this is atomic
|
os.replace(pull_file + ".temp", pull_file) # Atomic write
|
||||||
|
|
||||||
|
|
||||||
def backup_milestones(args, repo_cwd, repository, repos_template):
|
def backup_milestones(args, repo_cwd, repository, repos_template):
|
||||||
@@ -2203,5 +2203,5 @@ def json_dump_if_changed(data, output_file_path):
|
|||||||
temp_file = output_file_path + ".temp"
|
temp_file = output_file_path + ".temp"
|
||||||
with codecs.open(temp_file, "w", encoding="utf-8") as f:
|
with codecs.open(temp_file, "w", encoding="utf-8") as f:
|
||||||
f.write(new_content)
|
f.write(new_content)
|
||||||
os.rename(temp_file, output_file_path) # Atomic on POSIX systems
|
os.replace(temp_file, output_file_path) # Atomic write
|
||||||
return True
|
return True
|
||||||
|
|||||||
6
setup.py
6
setup.py
@@ -33,7 +33,11 @@ setup(
|
|||||||
author="Jose Diaz-Gonzalez",
|
author="Jose Diaz-Gonzalez",
|
||||||
author_email="github-backup@josediazgonzalez.com",
|
author_email="github-backup@josediazgonzalez.com",
|
||||||
packages=["github_backup"],
|
packages=["github_backup"],
|
||||||
scripts=["bin/github-backup"],
|
entry_points={
|
||||||
|
"console_scripts": [
|
||||||
|
"github-backup=github_backup.cli:main",
|
||||||
|
],
|
||||||
|
},
|
||||||
url="http://github.com/josegonzalez/python-github-backup",
|
url="http://github.com/josegonzalez/python-github-backup",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
|||||||
Reference in New Issue
Block a user