github-backup 0.54.0__py3-none-any.whl → 0.56.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
github_backup/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.54.0"
1
+ __version__ = "0.56.0"
@@ -0,0 +1,13 @@
1
+ """Allow running as: python -m github_backup"""
2
+
3
+ import sys
4
+
5
+ from github_backup.cli import main
6
+ from github_backup.github_backup import logger
7
+
8
+ if __name__ == "__main__":
9
+ try:
10
+ main()
11
+ except Exception as e:
12
+ logger.error(str(e))
13
+ sys.exit(1)
@@ -1,4 +1,5 @@
1
- #!python
1
+ #!/usr/bin/env python
2
+ """Command-line interface for github-backup."""
2
3
 
3
4
  import logging
4
5
  import os
@@ -9,6 +10,7 @@ from github_backup.github_backup import (
9
10
  backup_repositories,
10
11
  check_git_lfs_install,
11
12
  filter_repositories,
13
+ get_auth,
12
14
  get_authenticated_user,
13
15
  logger,
14
16
  mkdir_p,
@@ -35,8 +37,15 @@ logging.basicConfig(level=logging.INFO, handlers=[stdout_handler, stderr_handler
35
37
 
36
38
 
37
39
  def main():
40
+ """Main entry point for github-backup CLI."""
38
41
  args = parse_args()
39
42
 
43
+ if args.private and not get_auth(args):
44
+ logger.warning(
45
+ "The --private flag has no effect without authentication. "
46
+ "Use -t/--token, -f/--token-fine, or -u/--username to authenticate."
47
+ )
48
+
40
49
  if args.quiet:
41
50
  logger.setLevel(logging.WARNING)
42
51
 
@@ -561,7 +561,7 @@ def get_github_host(args):
561
561
 
562
562
 
563
563
  def read_file_contents(file_uri):
564
- return open(file_uri[len(FILE_URI_PREFIX) :], "rt").readline().strip()
564
+ return open(file_uri[len(FILE_URI_PREFIX):], "rt").readline().strip()
565
565
 
566
566
 
567
567
  def get_github_repo_url(args, repository):
@@ -1038,7 +1038,7 @@ def download_attachment_file(url, path, auth, as_app=False, fine=False):
1038
1038
  bytes_downloaded += len(chunk)
1039
1039
 
1040
1040
  # Atomic rename to final location
1041
- os.rename(temp_path, path)
1041
+ os.replace(temp_path, path)
1042
1042
 
1043
1043
  metadata["size_bytes"] = bytes_downloaded
1044
1044
  metadata["success"] = True
@@ -1459,7 +1459,7 @@ def download_attachments(
1459
1459
 
1460
1460
  # Rename to add extension (already atomic from download)
1461
1461
  try:
1462
- os.rename(filepath, final_filepath)
1462
+ os.replace(filepath, final_filepath)
1463
1463
  metadata["saved_as"] = os.path.basename(final_filepath)
1464
1464
  except Exception as e:
1465
1465
  logger.warning(
@@ -1490,7 +1490,7 @@ def download_attachments(
1490
1490
  manifest_path = os.path.join(attachments_dir, "manifest.json")
1491
1491
  with open(manifest_path + ".temp", "w") as f:
1492
1492
  json.dump(manifest, f, indent=2)
1493
- os.rename(manifest_path + ".temp", manifest_path) # Atomic write
1493
+ os.replace(manifest_path + ".temp", manifest_path) # Atomic write
1494
1494
  logger.debug(
1495
1495
  "Wrote manifest for {0} #{1}: {2} attachments".format(
1496
1496
  item_type_display, number, len(attachment_metadata_list)
@@ -1565,16 +1565,22 @@ def retrieve_repositories(args, authenticated_user):
1565
1565
  repos.extend(gists)
1566
1566
 
1567
1567
  if args.include_starred_gists:
1568
- starred_gists_template = "https://{0}/gists/starred".format(
1569
- get_github_api_host(args)
1570
- )
1571
- starred_gists = retrieve_data(
1572
- args, starred_gists_template, single_request=False
1573
- )
1574
- # flag each repo as a starred gist for downstream processing
1575
- for item in starred_gists:
1576
- item.update({"is_gist": True, "is_starred": True})
1577
- repos.extend(starred_gists)
1568
+ if not authenticated_user.get("login") or args.user.lower() != authenticated_user["login"].lower():
1569
+ logger.warning(
1570
+ "Cannot retrieve starred gists for '%s'. GitHub only allows access to the authenticated user's starred gists.",
1571
+ args.user,
1572
+ )
1573
+ else:
1574
+ starred_gists_template = "https://{0}/gists/starred".format(
1575
+ get_github_api_host(args)
1576
+ )
1577
+ starred_gists = retrieve_data(
1578
+ args, starred_gists_template, single_request=False
1579
+ )
1580
+ # flag each repo as a starred gist for downstream processing
1581
+ for item in starred_gists:
1582
+ item.update({"is_gist": True, "is_starred": True})
1583
+ repos.extend(starred_gists)
1578
1584
 
1579
1585
  return repos
1580
1586
 
@@ -1666,9 +1672,10 @@ def backup_repositories(args, output_directory, repositories):
1666
1672
  repo_url = get_github_repo_url(args, repository)
1667
1673
 
1668
1674
  include_gists = args.include_gists or args.include_starred_gists
1675
+ include_starred = args.all_starred and repository.get("is_starred")
1669
1676
  if (args.include_repository or args.include_everything) or (
1670
1677
  include_gists and repository.get("is_gist")
1671
- ):
1678
+ ) or include_starred:
1672
1679
  repo_name = (
1673
1680
  repository.get("name")
1674
1681
  if not repository.get("is_gist")
@@ -1804,7 +1811,7 @@ def backup_issues(args, repo_cwd, repository, repos_template):
1804
1811
 
1805
1812
  with codecs.open(issue_file + ".temp", "w", encoding="utf-8") as f:
1806
1813
  json_dump(issue, f)
1807
- os.rename(issue_file + ".temp", issue_file) # Unlike json_dump, this is atomic
1814
+ os.replace(issue_file + ".temp", issue_file) # Atomic write
1808
1815
 
1809
1816
 
1810
1817
  def backup_pulls(args, repo_cwd, repository, repos_template):
@@ -1879,7 +1886,7 @@ def backup_pulls(args, repo_cwd, repository, repos_template):
1879
1886
 
1880
1887
  with codecs.open(pull_file + ".temp", "w", encoding="utf-8") as f:
1881
1888
  json_dump(pull, f)
1882
- os.rename(pull_file + ".temp", pull_file) # Unlike json_dump, this is atomic
1889
+ os.replace(pull_file + ".temp", pull_file) # Atomic write
1883
1890
 
1884
1891
 
1885
1892
  def backup_milestones(args, repo_cwd, repository, repos_template):
@@ -2017,12 +2024,9 @@ def fetch_repository(
2017
2024
  ):
2018
2025
  if bare_clone:
2019
2026
  if os.path.exists(local_dir):
2020
- clone_exists = (
2021
- subprocess.check_output(
2022
- ["git", "rev-parse", "--is-bare-repository"], cwd=local_dir
2023
- )
2024
- == b"true\n"
2025
- )
2027
+ clone_exists = subprocess.check_output(
2028
+ ["git", "rev-parse", "--is-bare-repository"], cwd=local_dir
2029
+ ) == b"true\n"
2026
2030
  else:
2027
2031
  clone_exists = False
2028
2032
  else:
@@ -2037,11 +2041,14 @@ def fetch_repository(
2037
2041
  "git ls-remote " + remote_url, stdout=FNULL, stderr=FNULL, shell=True
2038
2042
  )
2039
2043
  if initialized == 128:
2040
- logger.info(
2041
- "Skipping {0} ({1}) since it's not initialized".format(
2042
- name, masked_remote_url
2044
+ if ".wiki.git" in remote_url:
2045
+ logger.info(
2046
+ "Skipping {0} wiki (wiki is enabled but has no content)".format(name)
2047
+ )
2048
+ else:
2049
+ logger.info(
2050
+ "Skipping {0} (repository not accessible - may be empty, private, or credentials invalid)".format(name)
2043
2051
  )
2044
- )
2045
2052
  return
2046
2053
 
2047
2054
  if clone_exists:
@@ -2083,11 +2090,13 @@ def fetch_repository(
2083
2090
  git_command.pop()
2084
2091
  logging_subprocess(git_command, cwd=local_dir)
2085
2092
  else:
2086
- if lfs_clone:
2087
- git_command = ["git", "lfs", "clone", remote_url, local_dir]
2088
- else:
2089
- git_command = ["git", "clone", remote_url, local_dir]
2093
+ git_command = ["git", "clone", remote_url, local_dir]
2090
2094
  logging_subprocess(git_command)
2095
+ if lfs_clone:
2096
+ git_command = ["git", "lfs", "fetch", "--all", "--prune"]
2097
+ if no_prune:
2098
+ git_command.pop()
2099
+ logging_subprocess(git_command, cwd=local_dir)
2091
2100
 
2092
2101
 
2093
2102
  def backup_account(args, output_directory):
@@ -2196,5 +2205,5 @@ def json_dump_if_changed(data, output_file_path):
2196
2205
  temp_file = output_file_path + ".temp"
2197
2206
  with codecs.open(temp_file, "w", encoding="utf-8") as f:
2198
2207
  f.write(new_content)
2199
- os.rename(temp_file, output_file_path) # Atomic on POSIX systems
2208
+ os.replace(temp_file, output_file_path) # Atomic write
2200
2209
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-backup
3
- Version: 0.54.0
3
+ Version: 0.56.0
4
4
  Summary: backup a github user or organization
5
5
  Home-page: http://github.com/josegonzalez/python-github-backup
6
6
  Author: Jose Diaz-Gonzalez
@@ -245,6 +245,8 @@ When you use the ``--lfs`` option, you will need to make sure you have Git LFS i
245
245
 
246
246
  Instructions on how to do this can be found on https://git-lfs.github.com.
247
247
 
248
+ LFS objects are fetched for all refs, not just the current checkout, ensuring a complete backup of all LFS content across all branches and history.
249
+
248
250
 
249
251
  About Attachments
250
252
  -----------------
@@ -311,11 +313,11 @@ If the incremental argument is used, this will result in the next backup only re
311
313
 
312
314
  It's therefore recommended to only use the incremental argument if the output/result is being actively monitored, or complimented with periodic full non-incremental runs, to avoid unexpected missing data in a regular backup runs.
313
315
 
314
- 1. **Starred public repo hooks blocking**
316
+ **Starred public repo hooks blocking**
315
317
 
316
- Since the ``--all`` argument includes ``--hooks``, if you use ``--all`` and ``--all-starred`` together to clone a users starred public repositories, the backup will likely error and block the backup continuing.
318
+ Since the ``--all`` argument includes ``--hooks``, if you use ``--all`` and ``--all-starred`` together to clone a users starred public repositories, the backup will likely error and block the backup continuing.
317
319
 
318
- This is due to needing the correct permission for ``--hooks`` on public repos.
320
+ This is due to needing the correct permission for ``--hooks`` on public repos.
319
321
 
320
322
 
321
323
  "bare" is actually "mirror"
@@ -331,6 +333,8 @@ Starred gists vs starred repo behaviour
331
333
 
332
334
  The starred normal repo cloning (``--all-starred``) argument stores starred repos separately to the users own repositories. However, using ``--starred-gists`` will store starred gists within the same directory as the users own gists ``--gists``. Also, all gist repo directory names are IDs not the gist's name.
333
335
 
336
+ Note: ``--starred-gists`` only retrieves starred gists for the authenticated user, not the target user, due to a GitHub API limitation.
337
+
334
338
 
335
339
  Skip existing on incomplete backups
336
340
  -----------------------------------
@@ -388,6 +392,25 @@ Debug an error/block or incomplete backup into a temporary directory. Omit "incr
388
392
  github-backup -f $FINE_ACCESS_TOKEN -o /tmp/github-backup/ -l debug -P --all-starred --starred --watched --followers --following --issues --issue-comments --issue-events --pulls --pull-comments --pull-commits --labels --milestones --repositories --wikis --releases --assets --pull-details --gists --starred-gists $GH_USER
389
393
 
390
394
 
395
+ Restoring from Backup
396
+ =====================
397
+
398
+ This tool creates backups only, there is no inbuilt restore command.
399
+
400
+ **Git repositories, wikis, and gists** can be restored by pushing them back to GitHub as you would any git repository. For example, to restore a bare repository backup::
401
+
402
+ cd /tmp/white-house/repositories/petitions/repository
403
+ git push --mirror git@github.com:WhiteHouse/petitions.git
404
+
405
+ **Issues, pull requests, comments, and other metadata** are saved as JSON files for archival purposes. The GitHub API does not support recreating this data faithfully, creating issues via the API has limitations:
406
+
407
+ - New issue/PR numbers are assigned (original numbers cannot be set)
408
+ - Timestamps reflect creation time (original dates cannot be set)
409
+ - The API caller becomes the author (original authors cannot be set)
410
+ - Cross-references between issues and PRs will break
411
+
412
+ These are GitHub API limitations that affect all backup and migration tools, not just this one. Recreating issues with these limitations via the GitHub API is an exercise for the reader. The JSON backups remain useful for searching, auditing, or manual reference.
413
+
391
414
 
392
415
  Development
393
416
  ===========
@@ -0,0 +1,11 @@
1
+ github_backup/__init__.py,sha256=pLF99dIRilisWyZd9CliPPWqIlV81Xy8XtGEMuThTaM,23
2
+ github_backup/__main__.py,sha256=3yorCiC63maaxbjdtaeAs-G-yAAxKSO3mprhGfa1Fho,271
3
+ github_backup/cli.py,sha256=zMUU0pTnh9KPqv8K--KNpUNf-yaA1qWeI6AdC1KVrn8,2361
4
+ github_backup/github_backup.py,sha256=wlVrA50q5iP8BbJ_6WIIMEP7MKl_B3cKm2zvEtNYMS4,80777
5
+ github_backup-0.56.0.dist-info/licenses/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
6
+ github_backup-0.56.0.dist-info/METADATA,sha256=0N-CqtE9r3amkSHGkrgR3YeG7wylthaM0Q2RDct5vbc,24230
7
+ github_backup-0.56.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ github_backup-0.56.0.dist-info/entry_points.txt,sha256=cLPKK7MqC77QVb6HnT2mnyL_f-ciavJG_Y9vVHlCF68,57
9
+ github_backup-0.56.0.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
10
+ github_backup-0.56.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
11
+ github_backup-0.56.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ github-backup = github_backup.cli:main
@@ -1,9 +0,0 @@
1
- github_backup/__init__.py,sha256=GL361gEztfNayz_pZjsGe3Es4PhpuAOve1lCqPCbSZs,23
2
- github_backup/github_backup.py,sha256=-7oiAGDfavMxSiqWgaiyS5vYq31kFxOXgEH0Vw5MTIo,80149
3
- github_backup-0.54.0.data/scripts/github-backup,sha256=hC59RgEKx2RbHKC-eQkKfB3JXfiNIvIO4VAXiodSkLw,2004
4
- github_backup-0.54.0.dist-info/licenses/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
5
- github_backup-0.54.0.dist-info/METADATA,sha256=CJyhg2JeoygmRFtyGbyjs0PLOoy04IkdC4mLnu1WOnM,22810
6
- github_backup-0.54.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- github_backup-0.54.0.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
8
- github_backup-0.54.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
9
- github_backup-0.54.0.dist-info/RECORD,,