{"id":13472458,"url":"https://github.com/josegonzalez/python-github-backup","last_synced_at":"2026-02-18T23:00:40.197Z","repository":{"id":12258887,"uuid":"14876085","full_name":"josegonzalez/python-github-backup","owner":"josegonzalez","description":"backup a github user or organization","archived":false,"fork":false,"pushed_at":"2026-02-11T20:26:06.000Z","size":942,"stargazers_count":1504,"open_issues_count":12,"forks_count":257,"subscribers_count":24,"default_branch":"master","last_synced_at":"2026-02-12T04:31:00.996Z","etag":null,"topics":["github-backup"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/josegonzalez.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2013-12-02T21:56:07.000Z","updated_at":"2026-02-11T22:53:32.000Z","dependencies_parsed_at":"2024-03-16T20:52:10.354Z","dependency_job_id":"2312c0bb-b927-4144-84c0-80e857bbb134","html_url":"https://github.com/josegonzalez/python-github-backup","commit_stats":{"total_commits":350,"total_committers":79,"mean_commits":4.430379746835443,"dds":0.6971428571428571,"last_synced_commit":"6ca8030648ea031eda51180f00fcaace83bc7e4e"},"previous_names":[],"tags_count":89,"template":false,"template_full_name":null,"purl":"pkg:github/josegonzalez/python-github-backup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josegonzalez%2Fpython-github-backup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josegonzalez%2Fpython-github-backup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josegonzalez%2Fpython-github-backup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josegonzalez%2Fpython-github-backup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/josegonzalez","download_url":"https://codeload.github.com/josegonzalez/python-github-backup/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josegonzalez%2Fpython-github-backup/sbom","scorecard":{"id":533306,"data":{"date":"2025-08-11","repo":{"name":"github.com/josegonzalez/python-github-backup","commit":"a9e48f8c4e2c7238f1e13e7cb0183f5809fc2168"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5,"checks":[{"name":"Code-Review","score":3,"reason":"Found 1/3 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"27 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/docker.yml:36","Warn: no topLevel permission defined: .github/workflows/automatic-release.yml:1","Warn: no topLevel permission defined: .github/workflows/docker.yml:1","Warn: no topLevel permission defined: .github/workflows/lint.yml:1","Warn: no topLevel permission defined: .github/workflows/tagged-release.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/automatic-release.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/automatic-release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/automatic-release.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/automatic-release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/docker.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/docker.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:46: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/docker.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/docker.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:52: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/docker.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:60: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/docker.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:71: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/docker.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/lint.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/lint.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/tagged-release.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/josegonzalez/python-github-backup/tagged-release.yml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:1: pin your Docker image by updating python:3.9.18-slim to python:3.9.18-slim@sha256:aa7b73608abcfb021247bbb4c111435234a0459298a6da610681097a54ca2c2a","Warn: pipCommand not pinned by hash: Dockerfile:9-10","Warn: pipCommand not pinned by hash: Dockerfile:13-14","Warn: pipCommand not pinned by hash: release:23","Warn: pipCommand not pinned by hash: .github/workflows/automatic-release.yml:35","Warn: pipCommand not pinned by hash: .github/workflows/lint.yml:30","Warn: pipCommand not pinned by hash: .github/workflows/lint.yml:30","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   6 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   6 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/docker.yml:33"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T06:16:15.305Z","repository_id":12258887,"created_at":"2025-08-20T06:16:15.305Z","updated_at":"2025-08-20T06:16:15.305Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29597852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T22:25:43.180Z","status":"ssl_error","status_checked_at":"2026-02-18T22:25:42.766Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github-backup"],"created_at":"2024-07-31T16:00:54.812Z","updated_at":"2026-02-18T23:00:40.183Z","avatar_url":"https://github.com/josegonzalez.png","language":"Python","funding_links":[],"categories":["Python","others","HarmonyOS"],"sub_categories":["Windows Manager"],"readme":"=============\ngithub-backup\n=============\n\n|PyPI| |Python Versions|\n\nThe package can be used to backup an *entire* `Github \u003chttps://github.com/\u003e`_ organization, repository or user account, including starred repos, issues and wikis in the most appropriate format (clones for wikis, json files for issues).\n\nRequirements\n============\n\n- Python 3.10 or higher\n- GIT 1.9+\n\nInstallation\n============\n\nUsing PIP via PyPI::\n\n    pip install github-backup\n\nUsing PIP via Github (more likely the latest version)::\n\n    pip install git+https://github.com/josegonzalez/python-github-backup.git#egg=github-backup\n    \n*Install note for python newcomers:*\n\nPython scripts are unlikely to be included in your ``$PATH`` by default, this means it cannot be run directly in terminal with ``$ github-backup ...``, you can either add python's install path to your environments ``$PATH`` or call the script directly e.g. using ``$ ~/.local/bin/github-backup``.*\n\nBasic Help\n==========\n\nShow the CLI help output::\n\n    github-backup -h\n\nCLI Help output::\n\n    github-backup [-h] [-t TOKEN_CLASSIC] [-f TOKEN_FINE] [-q] [--as-app]\n                  [-o OUTPUT_DIRECTORY] [-l LOG_LEVEL] [-i]\n                  [--incremental-by-files]\n                  [--starred] [--all-starred] [--starred-skip-size-over MB]\n                  [--watched] [--followers] [--following] [--all]\n                  [--issues] [--issue-comments] [--issue-events] [--pulls]\n                  [--pull-comments] [--pull-commits] [--pull-details]\n                  [--labels] [--hooks] [--milestones] [--security-advisories]\n                  [--repositories] [--bare] [--no-prune] [--lfs] [--wikis]\n                  [--gists] [--starred-gists] [--skip-archived] [--skip-existing]\n                  [-L [LANGUAGES ...]] [-N NAME_REGEX] [-H GITHUB_HOST]\n                  [-O] [-R REPOSITORY] [-P] [-F] [--prefer-ssh] [-v]\n                  [--keychain-name OSX_KEYCHAIN_ITEM_NAME]\n                  [--keychain-account OSX_KEYCHAIN_ITEM_ACCOUNT]\n                  [--releases] [--latest-releases NUMBER_OF_LATEST_RELEASES]\n                  [--skip-prerelease] [--assets]\n                  [--skip-assets-on [SKIP_ASSETS_ON ...]] [--attachments]\n                  [--throttle-limit THROTTLE_LIMIT]\n                  [--throttle-pause THROTTLE_PAUSE]\n                  [--exclude [EXCLUDE ...]] [--retries MAX_RETRIES]\n                  USER\n\n    Backup a github account\n\n    positional arguments:\n      USER                  github username\n\n    options:\n      -h, --help            show this help message and exit\n      -t, --token TOKEN_CLASSIC\n                            personal access, OAuth, or JSON Web token, or path to\n                            token (file://...)\n      -f, --token-fine TOKEN_FINE\n                            fine-grained personal access token (github_pat_....),\n                            or path to token (file://...)\n      -q, --quiet           supress log messages less severe than warning, e.g.\n                            info\n      --as-app              authenticate as github app instead of as a user.\n      -o, --output-directory OUTPUT_DIRECTORY\n                            directory at which to backup the repositories\n      -l, --log-level LOG_LEVEL\n                            log level to use (default: info, possible levels:\n                            debug, info, warning, error, critical)\n      -i, --incremental     incremental backup\n      --incremental-by-files\n                            incremental backup based on modification date of files\n      --starred             include JSON output of starred repositories in backup\n      --all-starred         include starred repositories in backup [*]\n      --starred-skip-size-over MB\n                            skip starred repositories larger than this size in MB\n      --watched             include JSON output of watched repositories in backup\n      --followers           include JSON output of followers in backup\n      --following           include JSON output of following users in backup\n      --all                 include everything in backup (not including [*])\n      --issues              include issues in backup\n      --issue-comments      include issue comments in backup\n      --issue-events        include issue events in backup\n      --pulls               include pull requests in backup\n      --pull-comments       include pull request review comments in backup\n      --pull-commits        include pull request commits in backup\n      --pull-details        include more pull request details in backup [*]\n      --labels              include labels in backup\n      --hooks               include hooks in backup (works only when\n                            authenticated)\n      --milestones          include milestones in backup\n      --security-advisories\n                            include security advisories in backup\n      --repositories        include repository clone in backup\n      --bare                clone bare repositories\n      --no-prune            disable prune option for git fetch\n      --lfs                 clone LFS repositories (requires Git LFS to be\n                            installed, https://git-lfs.github.com) [*]\n      --wikis               include wiki clone in backup\n      --gists               include gists in backup [*]\n      --starred-gists       include starred gists in backup [*]\n      --skip-archived       skip project if it is archived\n      --skip-existing       skip project if a backup directory exists\n      -L, --languages [LANGUAGES ...]\n                            only allow these languages\n      -N, --name-regex NAME_REGEX\n                            python regex to match names against\n      -H, --github-host GITHUB_HOST\n                            GitHub Enterprise hostname\n      -O, --organization    whether or not this is an organization user\n      -R, --repository REPOSITORY\n                            name of repository to limit backup to\n      -P, --private         include private repositories [*]\n      -F, --fork            include forked repositories [*]\n      --prefer-ssh          Clone repositories using SSH instead of HTTPS\n      -v, --version         show program's version number and exit\n      --keychain-name OSX_KEYCHAIN_ITEM_NAME\n                            OSX ONLY: name field of password item in OSX keychain\n                            that holds the personal access or OAuth token\n      --keychain-account OSX_KEYCHAIN_ITEM_ACCOUNT\n                            OSX ONLY: account field of password item in OSX\n                            keychain that holds the personal access or OAuth token\n      --releases            include release information, not including assets or\n                            binaries\n      --latest-releases NUMBER_OF_LATEST_RELEASES\n                            include certain number of the latest releases; only\n                            applies if including releases\n      --skip-prerelease     skip prerelease and draft versions; only applies if\n                            including releases\n      --assets              include assets alongside release information; only\n                            applies if including releases\n      --skip-assets-on [SKIP_ASSETS_ON ...]\n                            skip asset downloads for these repositories\n      --attachments         download user-attachments from issues and pull\n                            requests\n      --throttle-limit THROTTLE_LIMIT\n                            start throttling of GitHub API requests after this\n                            amount of API requests remain\n      --throttle-pause THROTTLE_PAUSE\n                            wait this amount of seconds when API request\n                            throttling is active (default: 30.0, requires\n                            --throttle-limit to be set)\n      --exclude [EXCLUDE ...]\n                            names of repositories to exclude\n      --retries MAX_RETRIES\n                            maximum number of retries for API calls (default: 5)\n\nUsage Details\n=============\n\nAuthentication\n--------------\n\nGitHub requires token-based authentication for API access. Password authentication was `removed in November 2020 \u003chttps://developer.github.com/changes/2020-02-14-deprecating-password-auth/\u003e`_.\n\nThe positional argument ``USER`` specifies the user or organization account you wish to back up.\n\n**Fine-grained tokens** (``-f TOKEN_FINE``) are recommended for most use cases, especially long-running backups (e.g. cron jobs), as they provide precise permission control.\n\n**Classic tokens** (``-t TOKEN``) are `slightly less secure \u003chttps://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#personal-access-tokens-classic\u003e`_ as they provide very coarse-grained permissions.\n\n\nFine Tokens\n~~~~~~~~~~~\n\nYou can \"generate new token\", choosing the repository scope by selecting specific repos or all repos. On Github this is under *Settings -\u003e Developer Settings -\u003e Personal access tokens -\u003e Fine-grained Tokens*\n\nCustomise the permissions for your use case, but for a personal account full backup you'll need to enable the following permissions:\n\n**User permissions**: Read access to followers, starring, and watching.\n\n**Repository permissions**: Read access to contents, issues, metadata, pull requests, and webhooks.\n\n\nGitHub Apps\n~~~~~~~~~~~\n\nGitHub Apps are ideal for organization backups in CI/CD. Tokens are scoped to specific repositories and expire after 1 hour.\n\n**One-time setup:**\n\n1. Create a GitHub App at *Settings -\u003e Developer Settings -\u003e GitHub Apps -\u003e New GitHub App*\n2. Set a name and homepage URL (can be any URL)\n3. Uncheck \"Webhook \u003e Active\" (not needed for backups)\n4. Set permissions (same as fine-grained tokens above)\n5. Click \"Create GitHub App\", then note the **App ID** shown on the next page\n6. Under \"Private keys\", click \"Generate a private key\" and save the downloaded file\n7. Go to *Install App* in your app's settings\n8. Select the account/organization and which repositories to back up\n\n**CI/CD usage with GitHub Actions:**\n\nStore the App ID as a repository variable and the private key contents as a secret, then use ``actions/create-github-app-token``::\n\n    - uses: actions/create-github-app-token@v1\n      id: app-token\n      with:\n        app-id: ${{ vars.APP_ID }}\n        private-key: ${{ secrets.APP_PRIVATE_KEY }}\n\n    - run: github-backup myorg -t ${{ steps.app-token.outputs.token }} --as-app -o ./backup --all\n\nNote: Installation tokens expire after 1 hour. For long-running backups, use a fine-grained personal access token instead.\n\n\nPrefer SSH\n~~~~~~~~~~\n\nIf cloning repos is enabled with ``--repositories``, ``--all-starred``, ``--wikis``, ``--gists``, ``--starred-gists`` using the ``--prefer-ssh`` argument will use ssh for cloning the git repos, but all other connections will still use their own protocol, e.g. API requests for issues uses HTTPS.\n\nTo clone with SSH, you'll need SSH authentication setup `as usual with Github \u003chttps://docs.github.com/en/authentication/connecting-to-github-with-ssh\u003e`_, e.g. via SSH public and private keys.\n\n\nUsing the Keychain on Mac OSX\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nNote: On Mac OSX the token can be stored securely in the user's keychain. To do this:\n\n1. Open Keychain from \"Applications -\u003e Utilities -\u003e Keychain Access\"\n2. Add a new password item using \"File -\u003e New Password Item\"\n3. Enter a name in the \"Keychain Item Name\" box. You must provide this name to github-backup using the --keychain-name argument.\n4. Enter an account name in the \"Account Name\" box, enter your Github username as set above. You must provide this name to github-backup using the --keychain-account argument.\n5. Enter your Github personal access token in the \"Password\" box\n\nNote:  When you run github-backup, you will be asked whether you want to allow \"security\" to use your confidential information stored in your keychain. You have two options:\n\n1. **Allow:** In this case you will need to click \"Allow\" each time you run `github-backup`\n2. **Always Allow:** In this case, you will not be asked for permission when you run `github-backup` in future. This is less secure, but is required if you want to schedule `github-backup` to run automatically\n\n\nGithub Rate-limit and Throttling\n--------------------------------\n\n\"github-backup\" will automatically throttle itself based on feedback from the Github API. \n\nTheir API is usually rate-limited to 5000 calls per hour. The API will ask github-backup to pause until a specific time when the limit is reset again (at the start of the next hour). This continues until the backup is complete.\n\nDuring a large backup, such as ``--all-starred``, and on a fast connection this can result in (~20 min) pauses with bursts of API calls periodically maxing out the API limit. If this is not suitable `it has been observed \u003chttps://github.com/josegonzalez/python-github-backup/issues/76#issuecomment-636158717\u003e`_ under real-world conditions that overriding the throttle with ``--throttle-limit 5000 --throttle-pause 0.6`` provides a smooth rate across the hour, although a ``--throttle-pause 0.72`` (3600 seconds [1 hour] / 5000 limit) is theoretically safer to prevent large rate-limit pauses.\n\n\nAbout Git LFS\n-------------\n\nWhen you use the ``--lfs`` option, you will need to make sure you have Git LFS installed.\n\nInstructions on how to do this can be found on https://git-lfs.github.com.\n\nLFS objects are fetched for all refs, not just the current checkout, ensuring a complete backup of all LFS content across all branches and history.\n\n\nAbout Attachments\n-----------------\n\nWhen you use the ``--attachments`` option with ``--issues`` or ``--pulls``, the tool will download user-uploaded attachments (images, videos, documents, etc.) from issue and pull request descriptions and comments. In some circumstances attachments contain valuable data related to the topic, and without their backup important information or context might be lost inadvertently.\n\nAttachments are saved to ``issues/attachments/{issue_number}/`` and ``pulls/attachments/{pull_number}/`` directories, where ``{issue_number}`` is the GitHub issue number (e.g., issue #123 saves to ``issues/attachments/123/``). Each attachment directory contains:\n\n- The downloaded attachment files (named by their GitHub identifier with appropriate file extensions)\n- If multiple attachments have the same filename, conflicts are resolved with numeric suffixes (e.g., ``report.pdf``, ``report_1.pdf``, ``report_2.pdf``)\n- A ``manifest.json`` file documenting all downloads, including URLs, file metadata, and download status\n\nThe tool automatically extracts file extensions from HTTP headers to ensure files can be more easily opened by your operating system.\n\n**Supported URL formats:**\n\n- Modern: ``github.com/user-attachments/{assets,files}/*``\n- Legacy: ``user-images.githubusercontent.com/*`` and ``private-user-images.githubusercontent.com/*``\n- Repo files: ``github.com/{owner}/{repo}/files/*`` (filtered to current repository)\n- Repo assets: ``github.com/{owner}/{repo}/assets/*`` (filtered to current repository)\n\n**Repository filtering** for repo files/assets handles renamed and transferred repositories gracefully. URLs are included if they either match the current repository name directly, or redirect to it (e.g., ``willmcgugan/rich`` redirects to ``Textualize/rich`` after transfer).\n\n**Fine-grained token limitation:** Due to a GitHub platform limitation, fine-grained personal access tokens (``github_pat_...``) cannot download attachments from private repositories directly. This affects both ``/assets/`` (images) and ``/files/`` (documents) URLs. The tool implements a workaround for image attachments using GitHub's Markdown API, which converts URLs to temporary JWT-signed URLs that can be downloaded. However, this workaround only works for images - document attachments (PDFs, text files, etc.) will fail with 404 errors when using fine-grained tokens on private repos. For full attachment support on private repositories, use a classic token (``-t``) instead of a fine-grained token (``-f``). See `#477 \u003chttps://github.com/josegonzalez/python-github-backup/issues/477\u003e`_ for details.\n\n\nAbout security advisories\n-------------------------\n\nGitHub security advisories are only available in public repositories. GitHub does not provide the respective API endpoint for private repositories.\n\nTherefore the logic is implemented as follows:\n- Security advisories are included in the `--all` option.\n- If only the `--all` option was provided, backups of security advisories are skipped for private repositories.\n- If the `--security-advisories` option is provided (on its own or in addition to `--all`), a backup of security advisories is attempted for all repositories, with graceful handling if the GitHub API doesn't return any.\n\n\nRun in Docker container\n-----------------------\n\nTo run the tool in a Docker container use the following command:\n\n    sudo docker run --rm -v /path/to/backup:/data --name github-backup ghcr.io/josegonzalez/python-github-backup -o /data $OPTIONS $USER\n\nGotchas / Known-issues\n======================\n\nAll is not everything\n---------------------\n\nThe ``--all`` argument does not include: cloning private repos (``-P, --private``), cloning forks (``-F, --fork``), cloning starred repositories (``--all-starred``), ``--pull-details``, cloning LFS repositories (``--lfs``), cloning gists (``--gists``) or cloning starred gist repos (``--starred-gists``). See examples for more.\n\nStarred repository size\n-----------------------\n\nUsing the ``--all-starred`` argument to clone all starred repositories may use a large amount of storage space.\n\nTo see your starred repositories sorted by size (requires `GitHub CLI \u003chttps://cli.github.com\u003e`_)::\n\n    gh api user/starred --paginate --jq 'sort_by(-.size)[]|\"\\(.full_name) \\(.size/1024|round)MB\"'\n\nTo limit which starred repositories are cloned, use ``--starred-skip-size-over SIZE`` where SIZE is in MB. For example, ``--starred-skip-size-over 500`` will skip any starred repository where the git repository size (code and history) exceeds 500 MB. Note that this size limit only applies to the repository itself, not issues, release assets or other metadata. This filter only affects starred repositories; your own repositories are always included regardless of size.\n\nFor finer control, avoid using ``--assets`` with starred repos, or use ``--skip-assets-on`` for specific repositories with large release binaries.\n\nAlternatively, consider just storing links to starred repos in JSON format with ``--starred``.\n\nIncremental Backup\n------------------\n\nUsing (``-i, --incremental``) will only request new data from the API **since the last run (successful or not)**. e.g. only request issues from the API since the last run. \n\nThis means any blocking errors on previous runs can cause a large amount of missing data in backups.\n\nUsing (``--incremental-by-files``) will request new data from the API **based on when the file was modified on filesystem**. e.g. if you modify the file yourself you may miss something.\n\nStill saver than the previous version.\n\nSpecifically, issues and pull requests are handled like this.\n\nKnown blocking errors\n---------------------\n\nSome errors will block the backup run by exiting the script. e.g. receiving a 403 Forbidden error from the Github API.\n\nIf the incremental argument is used, this will result in the next backup only requesting API data since the last blocked/failed run. Potentially causing unexpected large amounts of missing data.\n\nIt'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.\n\n**Starred public repo hooks blocking**\n\nSince 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.\n\nThis is due to needing the correct permission for ``--hooks`` on public repos.\n\n\n\"bare\" is actually \"mirror\"\n---------------------------\n\nUsing the bare clone argument (``--bare``) will actually call git's ``clone --mirror`` command. There's a subtle difference between `bare \u003chttps://www.git-scm.com/docs/git-clone#Documentation/git-clone.txt---bare\u003e`_ and `mirror \u003chttps://www.git-scm.com/docs/git-clone#Documentation/git-clone.txt---mirror\u003e`_ clone.\n\n*From git docs \"Compared to --bare, --mirror not only maps local branches of the source to local branches of the target, it maps all refs (including remote-tracking branches, notes etc.) and sets up a refspec configuration such that all these refs are overwritten by a git remote update in the target repository.\"*\n\n\nStarred gists vs starred repo behaviour\n---------------------------------------\n\nThe 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.\n\nNote: ``--starred-gists`` only retrieves starred gists for the authenticated user, not the target user, due to a GitHub API limitation.\n\n\nSkip existing on incomplete backups\n-----------------------------------\n\nThe ``--skip-existing`` argument will skip a backup if the directory already exists, even if the backup in that directory failed (perhaps due to a blocking error). This may result in unexpected missing data in a regular backup.\n\n\nUpdates use fetch, not pull\n---------------------------\n\nWhen updating an existing repository backup, ``github-backup`` uses ``git fetch`` rather than ``git pull``. This is intentional - a backup tool should reliably download data without risk of failure. Using ``git pull`` would require handling merge conflicts, which adds complexity and could cause backups to fail unexpectedly.\n\nWith fetch, **all branches and commits are downloaded** safely into remote-tracking branches. The working directory files won't change, but your backup is complete.\n\nIf you look at files directly (e.g., ``cat README.md``), you'll see the old content. The new data is in the remote-tracking branches (confusingly named \"remote\" but stored locally). To view or use the latest files::\n\n    git show origin/main:README.md           # view a file\n    git merge origin/main                    # update working directory\n\nAll branches are backed up as remote refs (``origin/main``, ``origin/feature-branch``, etc.).\n\nIf you want to browse files directly without merging, consider using ``--bare`` which skips the working directory entirely - the backup is just the git data.\n\nSee `#269 \u003chttps://github.com/josegonzalez/python-github-backup/issues/269\u003e`_ for more discussion.\n\n\nGithub Backup Examples\n======================\n\nBackup all repositories, including private ones using a classic token::\n\n    export ACCESS_TOKEN=SOME-GITHUB-TOKEN\n    github-backup WhiteHouse --token $ACCESS_TOKEN --organization --output-directory /tmp/white-house --repositories --private\n\nUse a fine-grained access token to backup a single organization repository with everything else (wiki, pull requests, comments, issues etc)::\n\n    export FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN\n    ORGANIZATION=docker\n    REPO=cli\n    # e.g. git@github.com:docker/cli.git\n    github-backup $ORGANIZATION -P -f $FINE_ACCESS_TOKEN -o . --all -O -R $REPO\n\nQuietly and incrementally backup useful Github user data (public and private repos with SSH) including; all issues, pulls, all public starred repos and gists (omitting \"hooks\", \"releases\" and therefore \"assets\" to prevent blocking). *Great for a cron job.* ::\n\n    export FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN\n    GH_USER=YOUR-GITHUB-USER\n\n    github-backup -f $FINE_ACCESS_TOKEN --prefer-ssh -o ~/github-backup/ -l error -P -i --all-starred --starred --watched --followers --following --issues --issue-comments --issue-events --pulls --pull-comments --pull-commits --labels --milestones --security-advisories --repositories --wikis --releases --assets --attachments --pull-details --gists --starred-gists $GH_USER\n    \nDebug an error/block or incomplete backup into a temporary directory. Omit \"incremental\" to fill a previous incomplete backup. ::\n\n    export FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN\n    GH_USER=YOUR-GITHUB-USER\n\n    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\n\nPipe a token from stdin to avoid storing it in environment variables or command history (Unix-like systems only)::\n\n    my-secret-manager get github-token | github-backup user -t file:///dev/stdin -o /backup --repositories\n\nRestoring from Backup\n=====================\n\nThis tool creates backups only, there is no inbuilt restore command.\n\n**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::\n\n    cd /tmp/white-house/repositories/petitions/repository\n    git push --mirror git@github.com:WhiteHouse/petitions.git\n\n**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:\n\n- New issue/PR numbers are assigned (original numbers cannot be set)\n- Timestamps reflect creation time (original dates cannot be set)\n- The API caller becomes the author (original authors cannot be set)\n- Cross-references between issues and PRs will break\n\nThese 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.\n\n\nDevelopment\n===========\n\nThis project is considered feature complete for the primary maintainer @josegonzalez. If you would like a bugfix or enhancement, pull requests are welcome. Feel free to contact the maintainer for consulting estimates if you'd like to sponsor the work instead.\n\nContibuters\n-----------\n\nA huge thanks to all the contibuters!\n\n.. image:: https://contrib.rocks/image?repo=josegonzalez/python-github-backup\n   :target: https://github.com/josegonzalez/python-github-backup/graphs/contributors\n   :alt: contributors\n\nTesting\n-------\n\nTo run the test suite::\n\n    pip install pytest\n    pytest\n\nTo run linting::\n\n    pip install flake8\n    flake8 --ignore=E501\n\n\n.. |PyPI| image:: https://img.shields.io/pypi/v/github-backup.svg\n   :target: https://pypi.python.org/pypi/github-backup/\n.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/github-backup.svg\n   :target: https://github.com/josegonzalez/python-github-backup\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosegonzalez%2Fpython-github-backup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjosegonzalez%2Fpython-github-backup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosegonzalez%2Fpython-github-backup/lists"}