import os import sys from dotenv import load_dotenv import requests load_dotenv() FORGEJO_INSTANCE = os.getenv("FORGEJO_INSTANCE", "https://codeberg.org") FORGEJO_API_TOKEN = os.getenv("FORGEJO_API_TOKEN") API_GET_ISSUE = "{instance}/api/v1/repos/{owner}/{repo}/issues/{index}" API_GET_ISSUE = "{instance}/api/v1/repos/{owner}/{repo}/issues/{index}" API_PATCH_ISSUE = "{instance}/api/v1/repos/{owner}/{repo}/issues/{index}" API_CREATE_ISSUE = "{instance}/api/v1/repos/{owner}/{repo}/issues" API_GET_COMMENTS = "{instance}/api/v1/repos/{owner}/{repo}/issues/{index}/comments" API_CREATE_COMMENT = "{instance}/api/v1/repos/{owner}/{repo}/issues/{index}/comments" def move_issue(source_owner, source_repo, issue_number, destination_owner, destination_repo): get_issue_url = API_GET_ISSUE.format(instance=FORGEJO_INSTANCE, owner=source_owner, repo=source_repo, index=issue_number) # print(get_issue_url) response = requests.get(get_issue_url, params={"access_token": FORGEJO_API_TOKEN}) json_response = response.json() # Not 100% certain this can ever give a message, but create below does on error message = json_response.pop('message', False) if message: print(message) sys.exit("Error getting issue.") else: old_issue_url = json_response['html_url'] milestone = json_response.pop('milestone', {}) if milestone: print(f"We have removed the milestone {milestone['title']} from this issue (milestones are specific to repositories).") # Note that pop has already thrown out the milestone from json_response assignees = json_response.pop('assignees', {}) if assignees: # 'assignee' is deprecated; remove it. json_response.pop('assignee', None) # Forgejo's API *gives* us assignee objects but only *accepts* strings. # Specifically, we need to replace the dict with its 'login' key. json_response['assignees'] = [] for assignee in assignees: json_response['assignees'].append(assignee['login']) print(f"Creating issue {json_response['title']} now…") create_issue_url = API_CREATE_ISSUE.format(instance=FORGEJO_INSTANCE, owner=destination_owner, repo=destination_repo) # print(create_issue_url) headers = {"Authorization": f"token {FORGEJO_API_TOKEN}"} created_response = requests.post(create_issue_url, headers=headers, data=json_response) json_response = created_response.json() message = json_response.pop('message', False) if message: print(message) sys.exit("Error creating issue.") else: new_issue_url = json_response['html_url'] print(f"Issue created: {new_issue_url}") new_issue_number = json_response['number'] print(f"new_issue_number = {new_issue_number}") new_issue_id = json_response['id'] print(f"new_issue_id = {new_issue_id}") print(f"Moving issue comments.") get_comments_url = API_GET_COMMENTS.format(instance=FORGEJO_INSTANCE, owner=source_owner, repo=source_repo, index=issue_number) create_comment_url = API_CREATE_COMMENT.format(instance=FORGEJO_INSTANCE, owner=destination_owner, repo=destination_repo, index=new_issue_number) comments = requests.get(get_comments_url, params={"access_token": FORGEJO_API_TOKEN}) json_comments = comments.json() for comment in json_comments: new_comment = requests.post(create_comment_url, headers=headers, data=comment) json_new_comment = new_comment.json() message = json_new_comment.pop('message', False) if message: print(message) sys.exit("Error creating comment.") moved_to_message = { 'body': f"*Issue moved to:* {new_issue_url}" } moved_from_message = { 'body': f"*Issue moved here from:* {old_issue_url}" } new_comment = requests.post(create_comment_url, headers=headers, data=moved_from_message) create_old_comment_url = API_CREATE_COMMENT.format(instance=FORGEJO_INSTANCE, owner=source_owner, repo=source_repo, index=issue_number) new_old_comment = requests.post(create_old_comment_url, headers=headers, data=moved_to_message) # And finally, close the old issue. # If we are confident the API is remaining stabel we can just use the same path # with patch, post, and get when the path is the same which is the case here. # Note though we often change the owner and repo and index when making the URL! body = { "state": "closed" } patch_issue_url = API_PATCH_ISSUE.format(instance=FORGEJO_INSTANCE, owner=source_owner, repo=source_repo, index=issue_number) # Note that sending body as JSON here is needed for 'state' to be changed. closed_response = requests.patch(patch_issue_url, headers=headers, json=body, params={"access_token": FORGEJO_API_TOKEN}) # print(closed_response.json()) if __name__=="__main__": # '0' comes in as move_issue.py when this is called as `python move_issue.py` source_owner = sys.argv[1] source_repo = sys.argv[2] issue_number = sys.argv[3] destination_owner = sys.argv[4] destination_repo = sys.argv[5] move_issue(source_owner, source_repo, issue_number, destination_owner, destination_repo)