Created
August 1, 2025 19:29
-
-
Save DavidMStraub/9ea640f84c84e27546020147edd08ba5 to your computer and use it in GitHub Desktop.
Upload a file to a Github repository using the Github API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Upload a file to a Github repository using the Github API. | |
| Requirements: | |
| - Install the `httpx` and `click` libraries: `pip install click httpx` | |
| - Set your GitHub token, repository owner, and repository name in the script. | |
| - Get a GitHub personal access token with permissions to write repo contents | |
| (https://github.com/settings/personal-access-tokens) | |
| Usage: | |
| ``` | |
| python md2gh.py file-to-upload --token github_pat_XXXX \ | |
| --owner YourGithubUser --repo your-repo-name | |
| ``` | |
| """ | |
| import base64 | |
| import hashlib | |
| import logging | |
| import pathlib | |
| import click | |
| import httpx | |
| def hash_blob_sha1(file_content: bytes) -> str: | |
| """Compute the SHA1 of file contents like git would.""" | |
| size = len(file_content) | |
| header = f"blob {size}\0".encode("utf-8") | |
| store = header + file_content | |
| return hashlib.sha1(store).hexdigest() | |
| @click.command() | |
| @click.argument( | |
| "filepath", type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path) | |
| ) | |
| @click.option("--token", required=True, help="Github Personal Access Token") | |
| @click.option("--owner", required=True, help="Github Repository Owner") | |
| @click.option("--repo", required=True, help="Github Repository Name") | |
| @click.option("--folder", required=False, help="Github Repository Subfolder") | |
| def main(filepath: pathlib.Path, token: str, owner: str, repo: str, folder: str | None) -> None: | |
| """Upload the file `filepath` a Guthub repo.""" | |
| file_content = filepath.read_bytes() | |
| encoded_content = base64.b64encode(file_content).decode("utf-8") | |
| if folder: | |
| path = f"{folder}/{filepath.name}" | |
| else: | |
| path = filepath.name | |
| api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}" | |
| payload = { | |
| "message": f"Add new blog post: {filepath.name}", | |
| "content": encoded_content, | |
| } | |
| headers = { | |
| "Authorization": f"Bearer {token}", | |
| "Accept": "application/vnd.github+json", | |
| } | |
| logging.debug("Checking if remote file exists") | |
| try: | |
| response = httpx.get(api_url, headers=headers) | |
| except Exception as exc: | |
| print(f"Error connecting to Github: {exc}") | |
| return | |
| if response.status_code == 200: | |
| remote_sha = response.json()["sha"] | |
| local_sha = hash_blob_sha1(file_content) | |
| if remote_sha == local_sha: | |
| print(f"File '{filepath.name}' already exists and content is identical.") | |
| return | |
| logging.debug("Updating remote file") | |
| payload["sha"] = remote_sha | |
| logging.debug("Uploading file") | |
| response = httpx.put(api_url, headers=headers, json=payload) | |
| data = response.json() | |
| if response.status_code in {200, 201}: | |
| print(f"Successfully uploaded file. View at {data['content']['html_url']}") | |
| else: | |
| print(f"Error uploading file: {data}") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment