Last updated: 2022. 02. 06

서론

최근 github을 열심히 관리하던 나는 하나의 문제에 직면하게 되었다. 바로 용량이 큰 프로젝트를 github에 올리기 힘들다는 것이었다.

대용량 파일을 올리는 법을 찾아본 결과 나는 LFS 라는 것을 알게되었고, 문제를 해결한 듯 하였다. 참고

그러나 얼마 지나지 않아 github에서 다음과 같은 메일이 왔다.

We wanted to let you know that you’ve used 80% of your data plan for Git LFS on your personal account lys7aves. No immediate action is necessary, but you might want to consider purchasing additional data packs to cover your bandwidth and storage usage:

https://github.com/account/billing/data/upgrade

Current usage as of 26 Jan 2022 12:22PM UTC:

Bandwidth: 0.0 GB / 1 GB (0%) Storage: 0.82 GB / 1 GB (82%)

LFS로 개인이 올릴 수 있는 용량이 최대 1GB라는 것이다. 다행히 이번에는 1GB 미만으로 올렸지만 다음에 추가로 올리고자 하였을 때 문제가 발생할 것임이 틀림 없었다.

이러한 문제를 해결하기 위해 나는 한가지 방법을 떠올렸는데,

바로 데이터들을 요약하여 github에 올리는 것이었다.

프로젝트를 아직 많이 해보지는 않았지만, 단순히 코드만으로 100MB 이상의 코드를 짜기는 쉽지 않으며, 전체 용량도 커지기 쉽지 않다. 즉, 프로젝트 파일의 용량이 커지는 것은 모두 데이터 때문이라고 생각했다. github에 내가 사용한 모든 데이터들을 올릴 수 있으면 좋겠지만, 어쩔 수 없이 용량을 줄여야 한다면 코드를 없애는 것보다 데이터를 줄이는 것이 가장 효과적일 것이라고 판단하였다.

수많은 데이터들을 일일이 확인할 수 없다고 판단하였으며 이를 코드로 하나 만들어 놓으면 이후 인공지능 프로젝트를 github에 올릴 때도 편할 것이라는 생각이 들었다.

요구사항

프로그램을 만들기 전에 어떤 기능이 필요한지 정리해보았다.

아래의 규칙을 만족하도록 프로그램을 작성하였다.

  1. 폴더는 전부 복사한다.
  2. 한 폴더에 100개 이상의 파일이 있다면, 한 종류(확장자)의 파일 당 최대 100개까지만 복사한다.
  3. 100MB가 넘는 파일들은 파일 이름만 복사한다.

데이터를 단순히 확장자 기준으로 잘라버리는게 마음에 걸리지만 데이터의 이름과 유형은 모두 제각각이기에 내 수준에서는 이를 모두 고려할 수는 없었다. 내 기준 확장자가 최선이었다 ㅜㅜ

그리고 3번 규칙에 의해 우리는 더 이상 LFS를 쓸 필요가 없어졌다.

구현 포인트

파이썬의 os 모듈을 살펴보면 파일 관련해서 쓸만한 함수들이 많은 것을 확인할 수 있다.

여기서 사용한 유용한 함수들을 간단히 소개시켜주겠다.

  • os.path.exists(file_path) : file_path 경로의 파일이 있는지 확인해준다.
  • os.makedirs(dir_path) : dir_path 에 디렉토리를 만들어준다.
  • os.listdir(dir_path) : dir_path 디렉토리에 있는 파일 리스트를 가져와준다.
  • os.path.isdir(path) : path 에 해당하는 것이 디렉토리인지 확인해준다.
  • os.path.splitext(file_path) : file_path 를 [파일 이름, 확장자]로 분리해준다.
  • os.path.getsize(path) : path 의 크기를 알려준다.

또한 파일을 복사할 때에는 shutil 모듈을 사용했는데, 이 중 메타데이터까지 복사해주는 copy2 함수를 사용하였다.

python - 파일 관련 함수

위의 함수들과 DFS에 대한 지식만 있다면 구현은 간단하다.

복사하고자 하는 디렉토리의 파일 리스트를 받아온 뒤, 리스트에 있는게 파일이면 규칙에 맞게 복사하고, 디렉토리라면 재귀적으로 처리해주면 된다. 단 이때 주의해야 할 점은 재귀함수를 호출할 때, 단순히 새로운 디렉토리를 호출하는 것이 아니라 “src + new + ‘/’“와 같이 상위 디렉토리를 앞에 붙여 경로를 올바르게 넘겨줘야 한다는 것이다.

코드

코드는 아래와 같이 파이썬으로 구현하였다.

import os
import shutil


def zip_directory(src='./', des='../zip/'):
    # 디렉토리 이름이 '/'로 끝나지 않으면 끝에 '/' 삽입
    if src[-1] != '/': src += '/'
    if des[-1] != '/': des += '/'
    
    # src와 des 경로 존재 확인
    # src 경로가 없거나 des 경로를 만들 수 없다면 종료
    # des 경로가 없다면 des 경로 생성
    if not os.path.exists(src):
        print ('Error: %s does not exist' %(src))
        return
    try:
        if not os.path.exists(des):
            os.makedirs(des)
    except OSError:
        print('Error: Creating directory. ' + des)
        return
        
    
    # README 작성
    f = open(des+"README.md", 'w')
    f.write("This directory is the compressed directory of the \"%s\" directory.\n" %(src))
    f.write("Files and directories in this directory follow the rules below.\n")
    f.write("\n")
    f.write("rule 1. All of the directories are copied.\n")
    f.write("rule 2. If there are more than 100 files in a directory, only 100 files of the same type are copied.\n")
    f.write("rule 3. Files over 100MB are copied only by the file name.\n")
    f.write("\t(The file will contain the phrase \"This file is more than 100MB.\").\n")
    f.close()
    
    # rule에 따라 src 파일들을 des로 복사
    global error_list
    error_list = ""
    copy_directory(src, des)
    
    # README 에 error list 추가
    f = open(des+"README.md", 'a')
    f.write("\n")
    f.write("*** Error ***\n")
    f.write(error_list)
    f.close()

    print("Success to zip the directory")
    
    
def copy_directory(src, des):
    global error_list
    
    # src 디렉토리에 있는 파일 목록 가져오기
    file_list = os.listdir(src)
    
    extension_count = {}
    for file in file_list:
        # file이 폴더인 경우
        if os.path.isdir(src + file):
            # des/file/ 디렉토리 생성
            try:
                if not os.path.exists(des + file):
                    os.makedirs(des + file)
            except OSError:
                print('Error: Creating directory. ' + des + file)
                error_list += 'Error: Creating directory. ' + des + file + '\n'
                continue
            
            # src/file/ 파일들을 des/file/ 로 복사
            copy_directory(src+file+'/', des+file+'/')
            
        # file이 폴더가 아닌 파일인 경우
        else:
            # 확장자 추출
            extension = os.path.splitext(file)[1]
            
            # 처음 나온 확장자면 사전에 추가
            if extension not in extension_count:
                extension_count[extension] = 0
            
            # 해당 확장자의 파일이 100개가 넘으면 패스
            if extension_count[extension] >= 100:
                continue
                
            # 파일이 100MB 이하면 파일 복사
            MB = 1024 * 1024
            if os.path.getsize(src+file) < 100 * MB:
                shutil.copy2(src+file, des+file)
            
            # 파일이 100MB 이상이면 파일 이름만 복사
            else:
                print("! %s is more than 100MB.\n" %(src+file))
                error_list += "! %s is more than 100MB.\n" %(src+file)
                f = open(des+file, 'w')
                f.write("This file is more than 100MB.")
                f.close()
            
            # 확장자 파일 카운트 증가
            extension_count[extension] += 1
            
            
def main():
    src = input("src: ")
    des = input("des: ")
    
    zip_directory(src, des)
    
    
if __name__ == "__main__":
    main()

실행 결과

image-20220205184337153

프로젝트 중 하나인 DYS 라는 프로젝트를 압축한 결과 52.9GB 폴더가 1.20GB로 줄어든 것을 볼 수 있다.

크기가 큰 중요한 데이터가 복사가 안된 경우도 있지만, 오류 리스트로 출력해놨으므로 꼭 중요한 파일은 직접 복사하면 된다.