본문 바로가기

개발 기록/자동 포스팅 프로그램

최신 커밋 호출 성공

이번에도 꽤나 어려웠다.

 

이번 목표는 깃 허브의 백준 문제가 저장된 repo에서 가장 최근에 커밋된 파일을 따오는 것이었다.

 

여러 번 시행착오를 반복한 이유는 Git에 commit_date가 저장되지 않은 파일들이 존재했기 때문이다.

 

처음에 각 파일들을 읽어들여 commit_date를 비교하여 최신의 것을 가져오려했다.

 

import os
import requests
import base64

def decode_base64(encoded_content):
    try:
        decoded_bytes = base64.b64decode(encoded_content)
        decoded_str = decoded_bytes.decode('utf-8')
        return decoded_str
    except Exception as e:
        print(f'Failed to decode Base64: {str(e)}')
        return None

def get_latest_commit_files(owner, repo, token):
    # GitHub API 엔드포인트
    api_url = f'https://api.github.com/repos/{owner}/{repo}/commits?per_page=1'

    # API 호출
    response = requests.get(api_url, headers={'Authorization': f'token {token}'})

    # 응답 확인
    if response.status_code == 200:
        # 응답에서 최신 커밋의 SHA 가져오기
        latest_commit_sha = response.json()[0]['sha']

        # 다시 API 호출하여 해당 커밋의 파일 목록 가져오기
        api_url = f'https://api.github.com/repos/{owner}/{repo}/git/trees/{latest_commit_sha}?recursive=1'
        response = requests.get(api_url, headers={'Authorization': f'token {token}'})

        # 응답 확인
        if response.status_code == 200:
            # 응답에서 'tree' 키에 해당하는 값 가져오기
            tree_data = response.json().get('tree')

            if tree_data is not None and isinstance(tree_data, list):
                # 마크다운 파일(.md)과 코드 파일(.cc) 중에서 최신 커밋의 것을 선택
                selected_files = [item['path'] for item in tree_data if item['type'] == 'blob' and 
                                   (item['path'].endswith('.md') or item['path'].endswith('.cc'))]

                # 최신 파일 선택
                latest_file = max(selected_files, key=lambda x: tree_data[selected_files.index(x)].get('last_modified', 0))

                # 최신 파일의 내용 가져오기
                file_api_url = f'https://api.github.com/repos/{owner}/{repo}/contents/{latest_file}?ref={latest_commit_sha}'
                file_response = requests.get(file_api_url, headers={'Authorization': f'token {token}'})

                if file_response.status_code == 200:
                    file_data = file_response.json()
                    file_content = decode_base64(file_data['content'])
                    return {latest_file: file_content}
                else:
                    print(f'Failed to fetch file content. Status code: {file_response.status_code}')
            else:
                print('Invalid response format.')
        else:
            print(f'Failed to fetch file list. Status code: {response.status_code}')
    else:
        print(f'Failed to fetch latest commit. Status code: {response.status_code}')

# GitHub repository 정보
owner = 'git546'
repo = 'BOJ'
token = '비밀'

# 함수 호출
file_content = get_latest_commit_files(owner, repo, token)

# 출력
for file, content in file_content.items():
    print(f'File: {file}')
    print(content)
    print('\n' + '-'*50 + '\n')

 

하지만 해당하는 date가 없었기 때문에 commit_date는 제대로 갱신되지 않았던 것이다.

 

그래서 출력으로

repo 최상단의 repo 생성시 생긴 파일이 최신 커밋으로 출력되었다.

 

이후 방향을 바꾸어 파일들에서 date를 읽어내는 대신 commit 기록을 바탕으로 하기로 하였다.

 

def get_repository_latest_commit_sha(owner, repo, token):
    api_url = f'https://api.github.com/repos/{owner}/{repo}/commits'
    headers = {'Authorization': f'token {token}'}

    response = requests.get(api_url, headers=headers)

    if response.status_code == 200:
        commits = response.json()
        latest_commit_sha = commits[0]['sha'] if commits else None
        return latest_commit_sha
    else:
        print(f'Failed to fetch repository commits. Status code: {response.status_code}')
        return None

 

이게 해당 함수이다. 지정된 경로에서 sha 중 가장 최근의 것을 return 하며 없는 경우 에러 코드를 반환한다.

 

def get_commit_files(owner, repo, commit_sha, token):
    api_url = f'https://api.github.com/repos/{owner}/{repo}/commits/{commit_sha}'
    response = requests.get(api_url, headers={'Authorization': f'token {token}'})

    if response.status_code == 200:
        commit_info = response.json()
        files_changed = commit_info.get('files', [])

        MD_content = None
        CC_content = None

        for file_change in files_changed:
            file_status = file_change['status']  # 추가('added'), 수정('modified'), 삭제('removed')
            file_path = file_change['filename']  # 파일 경로

            if file_path.endswith('.md') and file_status == 'added':
                MD_content = get_file_content(owner, repo, file_path, token)

            elif file_path.endswith('.cc') and file_status == 'added':
                CC_content = get_file_content(owner, repo, file_path, token)

        return MD_content, CC_content

    else:
        print(f'Failed to fetch commit information. Status code: {response.status_code}')
        return None, None

 

이를 활용하여 해당 커밋된 날짜에 커밋된 파일을 content에 담아 반환하는 함수를 만들었다. commit 상태가 추가에 해당하는 경우에만 경로에 따라 해당 파일을 지정된 변수에 담아 반환해 준다.

 

위 함수 중간에 사용된 get_file_content는 지정된 path의 파일을 가져와 반환하는 함수로

def get_file_content(owner, repo, file_path, token):
    api_url = f'https://api.github.com/repos/{owner}/{repo}/contents/{file_path}'
    headers = {'Authorization': f'token {token}'}

    response = requests.get(api_url, headers=headers)

    if response.status_code == 200:
        file_data = response.json()
        file_content = base64.b64decode(file_data['content']).decode('utf-8')
        return file_content
    else:
        print(f'파일 내용을 불러오지 못했습니다. 상태 코드: {response.status_code}')
        return None

 

의 구조로 되어있다.

 

이를 다음과 같이 활용하며 최신 커밋된 파일들의 내용을 출력해 확인할 수 있다.

# 함수 호출
latest_commit_sha = get_repository_latest_commit_sha(owner, repo, token)
if latest_commit_sha:
    print(f'Latest commit SHA: {latest_commit_sha}')
    MD_content, CC_content = get_commit_files(owner, repo, latest_commit_sha, token)

    # 출력 또는 다른 작업 수행
    if MD_content is not None:
        print(f'Markdown 파일 내용:\n{MD_content}')
    else:
        print('Markdown 파일 내용을 불러오지 못했습니다.')

    if CC_content is not None:
        print(f'C++ 파일 내용:\n{CC_content}')
    else:
        print('C++ 파일 내용을 불러오지 못했습니다.')

else:
    print('Failed to get the latest commit SHA.')

 

더보기

Markdown 파일 내용:
# [Silver IV] 순서쌍의 곱의 합 - 13900 

[문제 링크](https://www.acmicpc.net/problem/13900) 

### 성능 요약

메모리: 3464 KB, 시간: 44 ms

### 분류

수학, 누적 합

### 제출 일자

2023년 12월 19일 01:26:58

### 문제 설명

<p>N개의 정수 중 서로 다른 위치의 두 수를 뽑는 모든 경우의 두 수의 곱의 합을 구하라.</p>

<p>예를 들어 N = 3이고 주어진 정수가 2, 3, 4일 때, 두 수를 뽑는 모든 경우는 (2, 3), (2, 4), (3, 4)이며 이때 각각의 곱은 6, 8, 12이다. 따라서 총합은 26이다.</p>

### 입력 

 <p>첫 번째 줄에는 입력 받을 정수의 개수 N(<strong>2 ≤ N ≤ 100,000</strong>)</p>

<p>두 번째 줄에는 N 개의 정수가 주어진다. 이때 입력 받는 정수들의 범위는 <strong>0이상 10,000 이하이다.</strong></p>

### 출력 

 <p>모든 경우의 곱의 합을 출력한다.</p>


C++ 파일 내용:
#include <iostream>
#define fastio ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;

int main() {
int N;
cin >> N;
long arr[100000];
for (int i = 0; i < N; ++i) {
cin >> arr[i];
}
long answer = 0;
long psum[100000];

psum[0] = arr[0];

for (int i = 1; i < N; ++i) {
psum[i] = psum[i - 1] + arr[i];
}

for (int i = 0; i < N - 1; ++i) {
answer += arr[i] * (psum[N - 1] - psum[i]);
}

cout << answer;

}

 

그리고 출력해본 결과 가장 최신의 파일이 불러와진 것을 확인할 수 있었다.