10632 단어
53 분
Git 형상관리 UTF-8 인코딩 차이로 발생하는 형상 불일치 해결 방법

목차

    Linux 기반 형상관리 솔루션과 Git·Jenkins CI/CD 환경에서 발생한 u8-unix, u8-dos 파일 형식 불일치 문제를 분석하고, 줄바꿈 표준화와 자동화 프로세스를 통해 개선한 기술 사례

    git-형상관리-인코딩-해결
    git-형상관리-인코딩-해결

    1. 들어가며: 동일한 소스 코드인데 형상관리 결과가 달라지는 문제

    기업의 개발 및 운영 환경에서는 Git을 중심으로 소스 코드를 관리하고, Jenkins와 같은 CI/CD 도구를 통해 빌드 및 배포 자동화를 구성하는 경우가 많다.

    하지만 실제 프로젝트에서 형상관리 프로세스를 구축하다 보면 단순히 Git 저장소에 소스 코드를 등록하는 것만으로는 모든 환경에서 동일한 형상을 보장하기 어렵다는 사실을 확인하게 된다.

    이번에 경험한 문제 역시 이러한 환경 차이에서 발생했다.

    Linux 기반 형상관리 솔루션과 Git을 연계하는 과정에서 일부 소스 파일의 형상 검사 결과가 서로 다르게 나타나는 현상이 있었다.

    특히 TypeScript와 TSX 파일을 대상으로 확인한 결과, 코드 자체에는 차이가 없었지만 파일 형식 검사 결과가 다르게 나타났다.

    정상적으로 분류된 파일은 다음과 같았다.

    UTF-8 Unicode text
    

    반면 일부 파일은 다음과 같이 분류됐다.

    UTF-8 Unicode text, with CRLF line terminators
    

    형상관리 솔루션에서는 이를 각각 다음과 같은 형식으로 구분하고 있었다.

    u8-unix
    u8-dos
    

    두 파일 모두 UTF-8 인코딩을 사용하고 있었다.

    그런데도 형상관리 시스템에서는 파일 형식이 다르게 판단됐다.

    처음에는 문자 인코딩 문제로 예상했지만, 실제 원인을 분석한 결과 핵심은 UTF-8 인코딩 자체가 아니라 운영체제별 줄바꿈(Line Ending) 방식의 차이였다.

    이 글에서는 다음 내용을 중심으로 실제 분석 과정과 개선 방향을 정리한다.

    • u8-unix와 u8-dos의 차이
    • Linux에서 UTF-8과 줄바꿈 형식 확인 방법
    • Git 형상관리 시 CRLF와 LF가 문제를 발생시키는 이유
    • dos2unix를 활용한 파일 변환
    • find 명령어를 이용한 하위 디렉터리 일괄 처리
    • .git, .next, node_modules 등 제외 처리
    • Jenkins Pipeline에서의 자동 검증
    • .gitattributes를 활용한 Git 형상 표준화
    • CI/CD 환경에서의 재발 방지 및 개선 방향

    2. 문제 발생 환경과 형상관리 프로세스

    2.1 전체 시스템 구성

    이번 문제는 Linux 기반의 형상관리 및 CI/CD 프로세스를 구성하는 과정에서 확인됐다.

    일반적인 기업 환경에서는 다음과 같은 흐름으로 소스 코드가 전달된다.

    ┌──────────────────────────────┐
    │ 개발자 PC                     │
    │ Windows / Linux / macOS      │
    └──────────────┬───────────────┘
                   │
                   │ Git Push
                   ▼
    ┌──────────────────────────────┐
    │ Git Repository               │
    │ GitLab / Git Server          │
    └──────────────┬───────────────┘
                   │
                   │ Git Checkout
                   ▼
    ┌──────────────────────────────┐
    │ Jenkins CI/CD                │
    │                              │
    │ 1. Checkout                  │
    │ 2. Source Validation         │
    │ 3. Build                     │
    │ 4. Test                      │
    │ 5. Deploy                    │
    └──────────────┬───────────────┘
                   │
                   ▼
    ┌──────────────────────────────┐
    │ Linux 기반 형상관리 솔루션       │
    │ 소스 형식 / 형상 검증           │
    └──────────────┬───────────────┘
                   │
                   ▼
    ┌──────────────────────────────┐
    │ 운영 환경                     │
    │ Linux / Application Server   │
    └──────────────────────────────┘
    

    이 구조에서 중요한 점은 Git 저장소에 기록된 파일과 Jenkins 작업 디렉터리에 체크아웃된 파일이 항상 동일한 바이트 구조를 갖는 것은 아니라는 것이다.

    Git은 설정에 따라 텍스트 파일의 줄바꿈을 자동으로 변환할 수 있다.

    따라서 Git 저장소 내부에는 LF 형식으로 저장된 파일이 개발 환경이나 체크아웃 설정에 따라 CRLF 형식으로 나타날 수 있다.

    반대로 저장소에 CRLF가 포함된 파일이 등록되어 있다면 Linux 환경에서도 그대로 내려받을 수 있다.

    형상관리 솔루션이 파일의 실제 바이트 또는 줄바꿈 형식을 검사하는 경우 이러한 차이가 형상 불일치로 나타날 수 있다.

    2.2 실제 확인한 현상

    문제가 발생한 파일은 주로 다음과 같은 소스 파일이었다.

    *.ts
    *.tsx
    *.js
    *.jsx
    

    특히 TSX 파일을 검사했을 때 파일마다 결과가 다르게 나타났다.

    예를 들어 다음 두 파일이 존재한다고 가정하자.

    apps/DEV/src/components/Header.tsx
    apps/TEST/src/components/Header.tsx
    

    두 파일은 동일한 코드를 포함하고 있어도 실제 저장된 줄바꿈이 다를 수 있다.

    file apps/DEV/src/components/Header.tsx
    file apps/TEST/src/components/Header.tsx
    

    출력 예시:

    apps/DEV/src/components/Header.tsx:
    UTF-8 Unicode text
    
    apps/TEST/src/components/Header.tsx:
    UTF-8 Unicode text, with CRLF line terminators
    

    이러한 차이가 외부 형상관리 솔루션의 검사 기준과 일치하지 않는 상황이었다.

    문제를 해결하기 위해서는 먼저 UTF-8과 LF·CRLF의 차이를 정확하게 이해해야 했다.


    3. u8-unix와 u8-dos의 차이

    3.1 UTF-8 인코딩이란?

    UTF-8은 유니코드 문자를 바이트로 표현하기 위한 문자 인코딩 방식이다.

    현재 Java, JavaScript, TypeScript, React, Next.js 등 대부분의 현대적인 개발 환경에서 널리 사용되고 있다.

    UTF-8은 한글, 영문, 숫자 및 다양한 특수문자를 표현할 수 있다.

    하지만 UTF-8이라는 사실만으로 줄바꿈 형식까지 결정되는 것은 아니다.

    UTF-8 텍스트 파일은 LF 또는 CRLF 줄바꿈을 사용할 수 있다.

    즉 다음 두 파일은 모두 UTF-8 파일일 수 있다.

    UTF-8 + LF
    UTF-8 + CRLF
    

    3.2 LF와 CRLF의 차이

    줄바꿈을 표현하는 방식은 운영체제와 개발 환경에 따라 차이가 있다.

    구분LFCRLF
    명칭Line FeedCarriage Return + Line Feed
    Hex 코드0A0D 0A
    대표 환경Linux / UnixWindows
    일반적인 표기\n\r\n
    형상검사 표기 예시u8-unixu8-dos
    UTF-8 사용 가능가능가능

    Linux와 Unix 계열에서는 일반적으로 LF를 사용한다.

    Windows에서는 일반적으로 CRLF를 사용한다.

    예를 들어 다음과 같은 TypeScript 파일이 있다고 가정하자.

    const message = "Hello Gomdoc";
    
    console.log(message);
    

    화면에 출력되는 내용은 동일하지만 실제 파일의 줄바꿈 바이트는 다를 수 있다.

    LF 방식:

    const message = "Hello Gomdoc"; [0A]
    
    console.log(message);          [0A]
    

    CRLF 방식:

    const message = "Hello Gomdoc"; [0D 0A]
    
    console.log(message);           [0D 0A]
    

    여기에서 [0A]와 [0D 0A]는 설명을 위한 표기다.

    실제 파일에는 해당 문자 대신 각 바이트 값이 저장된다.

    이 차이로 인해 파일의 전체 바이트가 달라지며, 파일의 해시 값을 비교하는 시스템에서는 다른 파일로 판단할 수 있다.

    3.3 u8-unix와 u8-dos는 공식 인코딩 명칭인가?

    정확히 구분해야 할 점이 있다.

    u8-unix와 u8-dos는 UTF-8과 같이 국제 표준으로 정의된 별도의 문자 인코딩 이름이 아니다.

    일부 형상관리 또는 파일 검사 도구에서 UTF-8 텍스트 파일의 줄바꿈 방식에 따라 구분하는 표기다.

    일반적으로 다음과 같이 이해할 수 있다.

    u8-unix = UTF-8 + Unix LF
    
    u8-dos  = UTF-8 + DOS/Windows CRLF
    

    단, 정확한 의미는 사용하는 형상관리 솔루션의 파일 형식 분류 기준을 확인해야 한다.

    이번 문제에서 중요한 것은 서로 다른 문자 인코딩 때문에 발생한 문제가 아니라, UTF-8 파일 내부의 줄바꿈 바이트가 달랐다는 사실이다.


    4. Linux에서 파일 인코딩과 줄바꿈 형식 확인하기

    문제 원인을 확인하기 위해 가장 먼저 사용한 명령어는 file이었다.

    4.1 file 명령어 사용

    file filename.tsx
    

    출력 예시:

    filename.tsx: Unicode text, UTF-8 text
    

    또는 다음과 같은 결과가 나타날 수 있다.

    filename.tsx: Unicode text, UTF-8 text, with CRLF line terminators
    

    여기에서 두 번째 결과는 CRLF 줄바꿈이 포함되어 있음을 의미한다.

    file 명령의 결과 문구는 Linux 배포판과 file 버전에 따라 다를 수 있다.

    따라서 정확한 문자열보다 다음 문구가 나타나는지 확인하는 것이 중요하다.

    CRLF line terminators
    

    4.2 하위 디렉터리 파일 확인

    프로젝트의 파일들이 여러 디렉터리에 분산되어 있다면 find 명령어와 함께 확인할 수 있다.

    find . -type f -name '*.tsx' -exec file {} +
    

    해당 명령어는 현재 디렉터리 아래의 모든 TSX 파일을 검색하여 파일 형식을 출력한다.

    단, 실제 프로젝트에서는 .git이나 .next와 같은 디렉터리까지 검색할 수 있으므로 검색 범위를 제한하는 것이 좋다.

    4.3 Hex 코드로 정확하게 확인

    파일의 실제 바이트를 확인하려면 xxd 명령어를 사용할 수 있다.

    xxd filename.tsx
    

    또는 다음과 같이 확인한다.

    od -An -tx1 -c filename.tsx
    

    정상적인 LF 줄바꿈은 다음 바이트로 나타난다.

    0a
    

    CRLF 줄바꿈은 다음과 같이 나타난다.

    0d 0a
    

    파일 형식 검사 결과가 애매할 때는 Hex 데이터를 확인하는 것이 도움이 된다.

    4.4 줄바꿈만 다른지 비교

    비교 대상 파일이 두 개 있다면 다음 명령어를 사용할 수 있다.

    diff -u file1.tsx file2.tsx
    

    줄바꿈의 영향을 제외하고 비교해야 한다면 다음과 같은 명령도 참고할 수 있다.

    diff --strip-trailing-cr -u file1.tsx file2.tsx
    

    --strip-trailing-cr 옵션은 GNU diff에서 지원하며 줄 끝 CR 문자를 무시한다.

    다만 정확한 파일 무결성 검사가 필요한 경우에는 줄바꿈을 무시하지 않은 원본 파일의 차이도 반드시 확인해야 한다.


    5. Git 형상관리 자체의 줄바꿈 처리 상태 확인

    Linux에서 파일 형식 차이를 확인한 다음에는 Git 설정과 작업 디렉터리의 상태를 확인해야 한다.

    5.1 git ls-files –eol

    Git은 저장소 인덱스와 작업 디렉터리의 줄바꿈 형식을 확인할 수 있는 명령어를 제공한다.

    git ls-files --eol
    

    특정 파일을 지정할 수도 있다.

    git ls-files --eol -- apps/DEV/src/components/Header.tsx
    

    출력 예시:

    i/lf    w/crlf    attr/    apps/DEV/src/components/Header.tsx
    

    각 값은 다음과 같다.

    항목의미
    i/lfGit 인덱스의 파일은 LF 형식
    i/crlfGit 인덱스의 파일은 CRLF 형식
    w/lf작업 디렉터리의 파일은 LF 형식
    w/crlf작업 디렉터리의 파일은 CRLF 형식
    attr/줄바꿈과 관련된 Git 속성 정보

    이 명령어에서 중요한 부분은 i/와 w/의 차이다.

    Git이 저장하는 파일의 형식과 실제 작업 디렉터리의 파일 형식은 서로 다를 수 있다.

    예를 들어 다음과 같은 상태라면 다음과 같이 해석할 수 있다.

    i/lf
    w/crlf
    

    Git 인덱스에서는 LF로 관리되고 있지만 현재 디렉터리에 체크아웃된 파일은 CRLF인 상태다.

    외부 형상관리 솔루션이 작업 디렉터리의 실제 파일을 검사한다면 Git 인덱스가 정상이라고 해도 형상 불일치가 발생할 수 있다.

    5.2 Git core.autocrlf 설정

    Git은 줄바꿈을 자동으로 처리하기 위한 설정을 제공한다.

    git config --get core.autocrlf
    

    설정값에는 다음과 같은 종류가 있다.

    설정값설명
    true텍스트 파일을 Git에 추가할 때 LF로 정규화하고 체크아웃 시 CRLF 사용
    inputGit에 추가할 때 CRLF를 LF로 정규화하며 체크아웃 시 CRLF 자동 변환 없음
    falsecore.autocrlf에 의한 자동 변환을 비활성화

    Linux 기반 CI/CD 환경에서는 다음 설정을 고려할 수 있다.

    git config --local core.autocrlf input
    

    단, core.autocrlf만으로 모든 파일의 줄바꿈 정책이 보장되지는 않는다.

    .gitattributes에서 지정한 파일별 속성도 함께 고려해야 한다.

    5.3 Git 설정 적용 위치 확인

    다음 명령어는 설정값이 어디에서 지정되었는지 확인할 때 유용하다.

    git config --show-origin --get core.autocrlf
    

    전체 설정을 확인하려면 다음 명령을 사용한다.

    git config --list --show-origin
    

    기업 환경에서는 개발자 PC, Jenkins Agent, Git 서버의 설정이 서로 다를 수 있다.

    따라서 단순히 현재 서버의 설정만 확인할 것이 아니라 실제로 소스를 체크아웃하는 환경의 설정을 확인해야 한다.


    6. dos2unix를 이용한 CRLF → LF 변환

    실제 문제를 해결하는 과정에서는 CRLF가 적용된 파일을 LF로 변환하는 작업을 수행했다.

    Linux에서는 dos2unix 명령어를 사용하면 비교적 간단하게 처리할 수 있다.

    6.1 단일 파일 변환

    dos2unix filename.tsx
    

    이 명령어는 일반적인 텍스트 파일의 CRLF 줄바꿈을 LF로 변환한다.

    변환 결과는 다음 명령어로 확인한다.

    file filename.tsx
    

    CRLF 관련 표시가 사라졌는지 확인하면 된다.

    6.2 dos2unix -q 옵션

    CI/CD 작업에서는 여러 파일을 한꺼번에 변환할 수 있기 때문에 로그가 지나치게 출력되지 않도록 처리하는 것이 좋다.

    dos2unix -q filename.tsx
    

    -q는 quiet 옵션이다.

    일반적인 처리 메시지를 출력하지 않도록 설정한다.

    Jenkins에서 대량의 파일을 처리할 경우 불필요한 콘솔 로그를 줄이는 데 도움이 된다.

    6.3 dos2unix 사용 시 주의사항

    여기에서 중요한 점은 dos2unix가 모든 인코딩 문제를 해결하는 도구는 아니라는 것이다.

    예를 들어 다음과 같은 파일이 있다고 가정하자.

    UTF-8 + CRLF
    

    일반적인 dos2unix 변환 결과는 다음과 같다.

    UTF-8 + LF
    

    하지만 원본이 EUC-KR, UTF-16 또는 잘못된 UTF-8 바이트를 포함하고 있다면 단순한 줄바꿈 변환만으로 UTF-8 인코딩 문제가 해결되는 것은 아니다.

    또한 정확한 바이트 값이 중요한 파일에 변환을 적용하면 프로그램 동작이나 검증 결과에 영향을 줄 수 있다.

    따라서 변환 대상은 실제 소스 파일로 제한하는 것이 안전하다.


    7. 하위 디렉터리 전체 파일 변환

    개별 파일만 수정한다면 dos2unix 명령어 하나로 충분하다.

    하지만 기업 프로젝트는 일반적으로 수백 또는 수천 개의 파일로 구성되어 있다.

    따라서 특정 디렉터리 아래의 전체 소스 파일에 일괄 적용할 수 있는 방법이 필요했다.

    7.1 find를 이용한 일괄 변환

    find ./src -type f -name '*.tsx' -exec dos2unix -q {} +
    

    해당 명령어는 src 디렉터리 아래에 존재하는 모든 TSX 파일을 검색하여 변환한다.

    7.2 여러 확장자 처리

    JavaScript와 TypeScript 파일을 함께 처리하려면 다음과 같이 작성할 수 있다.

    find ./src -type f \
      \( -name '*.ts' -o \
         -name '*.tsx' -o \
         -name '*.js' -o \
         -name '*.jsx' \) \
      -exec dos2unix -q {} +
    

    이 명령은 다음 확장자를 대상으로 한다.

    .ts
    .tsx
    .js
    .jsx
    

    7.3 모든 파일을 변환하면 안 되는 이유

    다음과 같은 명령은 주의가 필요하다.

    find . -type f -exec dos2unix {} +
    

    이 명령은 현재 디렉터리 아래의 모든 일반 파일을 대상으로 한다.

    파일 형식에 관계없이 변환을 시도하므로 바이너리 파일이나 Git 내부 파일까지 검색 대상에 포함될 수 있다.

    일부 파일은 dos2unix가 자동으로 건너뛰더라도 불필요한 처리가 발생한다.

    따라서 실제 운영 환경에서는 확장자와 제외 디렉터리를 명확하게 지정해야 한다.


    8. .git, .next, node_modules 디렉터리 제외

    이번 문제를 처리하는 과정에서 중요했던 부분 중 하나는 특정 디렉터리를 제외하는 방식이었다.

    Next.js 프로젝트에서는 빌드 시 .next 디렉터리가 생성된다.

    또한 모노레포나 다중 애플리케이션 환경에서는 .next가 여러 위치에 존재할 수 있다.

    예를 들어 다음과 같은 구조다.

    workspace/
    ├── .git/
    ├── apps/
    │   ├── DEV/
    │   │   ├── src/
    │   │   └── .next/
    │   ├── TEST/
    │   │   ├── src/
    │   │   └── .next/
    │   └── PROD/
    │       ├── src/
    │       └── .next/
    ├── node_modules/
    └── Jenkinsfile
    

    이 환경에서는 다음과 같은 디렉터리를 제외해야 한다.

    .git
    .next
    node_modules
    dist
    build
    coverage
    

    8.1 find -not -path 방식

    단순히 특정 경로를 제외할 때는 다음과 같은 방법을 사용할 수 있다.

    find . -type f \
      -not -path './.git/*' \
      -not -path './node_modules/*'
    

    하지만 이러한 방식은 복잡한 프로젝트 구조에서 제외 대상 관리가 어려워질 수 있다.

    특히 .next 디렉터리가 여러 위치에 존재한다면 경로 패턴을 별도로 고려해야 한다.

    8.2 find -prune 방식

    디렉터리 내부 검색 자체를 제외하려면 -prune을 사용하는 것이 효과적이다.

    find . \
      \( -type d \( \
          -name '.git' -o \
          -name '.next' -o \
          -name 'node_modules' -o \
          -name 'dist' -o \
          -name 'build' -o \
          -name 'coverage' \
        \) -prune \) \
      -o \
      \( -type f -name '*.tsx' -print \)
    

    이 방식은 .next 디렉터리가 어느 위치에 있든 이름이 일치하면 내부 검색을 건너뛴다.

    따라서 다음과 같은 모든 경로를 처리할 수 있다.

    apps/DEV/.next/
    apps/TEST/.next/
    apps/PROD/.next/
    

    8.3 -prune과 -not -path의 차이

    -not -path는 조건에 일치하는 경로를 검색 결과에서 제외하는 방식이다.

    반면 -prune은 지정한 디렉터리 내부로 내려가지 않도록 검색을 제어한다.

    따라서 대용량 프로젝트에서 불필요한 디렉터리 순회를 줄이는 데 도움이 된다.

    특히 node_modules나 .next처럼 파일 수가 많은 디렉터리를 제외해야 한다면 -prune 방식이 적합하다.


    9. Jenkins @tmp 임시 디렉터리 제외

    Jenkins를 사용하면 일반적인 프로젝트 디렉터리 외에도 작업 과정에서 임시 디렉터리가 생성될 수 있다.

    예를 들면 다음과 같다.

    CMM@tmp
    BUILD@tmp
    JOB_NAME@tmp
    

    이러한 디렉터리는 Jenkins 작업 환경과 단계에 따라 이름이 달라질 수 있다.

    따라서 특정 이름을 고정해서 제외하는 방식은 적합하지 않다.

    9.1 @tmp 패턴 처리

    -name '*@tmp'
    

    이 조건은 디렉터리 이름이 @tmp로 끝나는 경우 일치한다.

    이름에 @tmp가 포함된 모든 경우를 제외하고 싶다면 다음과 같이 사용할 수 있다.

    -name '*@tmp*'
    

    9.2 Jenkins 임시 디렉터리 제외 명령어

    find . \
      \( -type d \( \
          -name '.git' -o \
          -name '.next' -o \
          -name 'node_modules' -o \
          -name '*@tmp' \
        \) -prune \) \
      -o \
      \( -type f -name '*.tsx' -print \)
    

    이 방법은 Jenkins Pipeline의 sh 단계에서도 사용할 수 있다.

    9.3 Jenkins Groovy 문자열과 백슬래시 주의

    Jenkins Pipeline에서 다음과 같은 형태로 쉘을 실행할 때는 주의가 필요하다.

    sh '''
        find . \
          -type f \
          -name '*.tsx'
    '''
    

    Groovy의 문자열 처리 과정에서 백슬래시가 영향을 받을 수 있다.

    따라서 복잡한 쉘 명령어를 Pipeline 내부에 직접 작성해야 한다면 이스케이프 처리를 정확히 해야 한다.

    가장 유지보수하기 좋은 방법 중 하나는 별도의 쉘 스크립트 파일로 분리하는 것이다.

    예를 들어 다음과 같이 작성한다.

    sh './scripts/check-line-endings.sh'
    

    이렇게 하면 Groovy 문자열과 Shell 문법이 혼합되면서 발생하는 문제를 줄일 수 있다.


    10. 파일 변환 전 대상 파일 수 확인

    운영 환경에서 파일을 대량 변환할 때는 바로 변환을 실행하기보다 실제 대상 파일 수를 먼저 확인하는 것이 좋다.

    10.1 전체 TSX 파일 개수

    find ./src -type f -name '*.tsx' | wc -l
    

    일반적인 프로젝트에서는 충분히 사용할 수 있는 방법이다.

    10.2 GNU find를 이용한 개수 확인

    파일명에 공백이나 개행이 포함될 수 있는 환경에서는 다음과 같은 방법을 사용할 수 있다.

    find ./src -type f -name '*.tsx' -printf '.' | wc -c
    

    이 명령은 GNU find에서 사용할 수 있다.

    10.3 CRLF 파일만 검색

    Python을 활용하면 파일 바이트를 직접 검사하여 CRLF 파일만 확인할 수 있다.

    from pathlib import Path
    
    root = Path("./src")
    
    extensions = {".ts", ".tsx", ".js", ".jsx"}
    
    for path in root.rglob("*"):
        if not path.is_file():
            continue
    
        if path.suffix.lower() not in extensions:
            continue
    
        data = path.read_bytes()
    
        if b"\r\n" in data:
            print(path)
    

    이 예제는 src 디렉터리 내부에서 지정된 확장자를 가진 파일을 검색한다.

    파일 내부에 CRLF 바이트가 존재하면 해당 경로를 출력한다.

    대용량 파일이 많다면 모든 파일을 메모리에 읽는 방식 대신 스트리밍 검사나 Git 추적 파일 목록 기반 검사로 개선할 수 있다.


    11. .gitattributes를 이용한 근본적인 해결

    dos2unix는 현재 발생한 파일 형식 문제를 해결할 수 있는 방법이다.

    그러나 매번 변환 명령어를 실행하는 방식만으로는 동일한 문제가 반복될 가능성을 완전히 제거하기 어렵다.

    따라서 Git 저장소 자체에 줄바꿈 정책을 정의하는 방법을 함께 검토해야 한다.

    여기에 사용하는 파일이 .gitattributes다.

    11.1 .gitattributes 설정 예제

    프로젝트 루트에 다음 파일을 생성한다.

    .gitattributes
    

    설정 예제는 다음과 같다.

    # 텍스트 파일 자동 판별
    * text=auto
    
    # Java / JavaScript / TypeScript
    *.java text eol=lf
    *.js text eol=lf
    *.jsx text eol=lf
    *.ts text eol=lf
    *.tsx text eol=lf
    
    # 설정 및 웹 리소스
    *.json text eol=lf
    *.yml text eol=lf
    *.yaml text eol=lf
    *.xml text eol=lf
    *.html text eol=lf
    *.css text eol=lf
    *.sh text eol=lf
    
    # Windows 스크립트
    *.bat text eol=crlf
    *.cmd text eol=crlf
    
    # 바이너리 파일
    *.png -text
    *.jpg -text
    *.jpeg -text
    *.gif -text
    *.ico -text
    *.zip -text
    *.jar -text
    *.pdf -text
    

    이 설정을 통해 개발 소스 파일의 줄바꿈 정책을 LF로 통일할 수 있다.

    단, 기존 저장소에 포함된 파일의 상태와 작업 디렉터리의 상태는 별도로 확인해야 한다.

    11.2 기존 Git 파일 정규화

    .gitattributes를 적용한 이후 기존 파일을 정규화하려면 다음 명령어를 사용할 수 있다.

    git add --renormalize .
    

    이 명령은 현재 적용된 Git 속성에 따라 파일을 다시 인덱스에 등록하도록 처리한다.

    변경 내용을 확인한다.

    git status
    

    스테이징된 변경 내용을 확인한다.

    git diff --cached --stat
    

    공백 관련 문제를 확인한다.

    git diff --cached --check
    

    이상이 없다면 별도 커밋으로 관리한다.

    git commit -m "chore: normalize source line endings"
    

    여기에서 주의할 점은 git add --renormalize가 Git 인덱스의 정규화를 수행한다는 것이다.

    작업 디렉터리의 모든 파일을 강제로 다시 체크아웃하는 명령어는 아니다.

    따라서 실제 형상관리 솔루션이 작업 디렉터리를 검사하는 경우에는 별도의 LF 검사도 필요하다.

    11.3 EditorConfig도 함께 사용

    개발자 편집기에서부터 LF 형식을 사용하도록 설정하면 형상관리 정책의 일관성을 높일 수 있다.

    프로젝트 루트에 .editorconfig 파일을 생성한다.

    root = true
    
    [*]
    charset = utf-8
    end_of_line = lf
    insert_final_newline = true
    
    [*.{bat,cmd}]
    end_of_line = crlf
    

    .editorconfig를 지원하는 IDE에서는 파일 저장 시 해당 설정을 적용할 수 있다.

    다만 모든 편집기와 도구가 동일하게 설정을 지원하는 것은 아니므로 Git의 .gitattributes 정책과 함께 사용하는 것이 좋다.


    12. Jenkins CI/CD에서 CRLF 자동 검증

    형상관리 정책을 표준화한 이후에는 Jenkins에서 사전 검증하는 구조를 적용할 수 있다.

    권장하는 흐름은 다음과 같다.

    Git Checkout
        │
        ▼
    파일 형식 검사
        │
        ├── UTF-8 유효성 검사
        │
        ├── CRLF 존재 여부 확인
        │
        └── Git 속성 정책 확인
        │
        ▼
    형상관리 검증
        │
        ▼
    소스 빌드
        │
        ▼
    테스트
        │
        ▼
    배포
    

    이렇게 구성하면 운영 배포 전에 형상 불일치 가능성을 검출할 수 있다.

    12.1 Python 기반 CRLF 검사 스크립트

    다음과 같은 파일을 생성한다.

    scripts/check-line-endings.py
    

    코드는 다음과 같다.

    from pathlib import Path
    import sys
    
    ROOT = Path(".")
    EXTENSIONS = {
        ".java",
        ".js",
        ".jsx",
        ".ts",
        ".tsx",
        ".json",
        ".yml",
        ".yaml",
        ".sh",
    }
    
    EXCLUDED_DIRS = {
        ".git",
        ".next",
        "node_modules",
        "dist",
        "build",
        "coverage",
    }
    
    invalid_files = []
    encoding_errors = []
    
    for path in ROOT.rglob("*"):
        if not path.is_file():
            continue
    
        if any(
            part in EXCLUDED_DIRS or part.endswith("@tmp")
            for part in path.parts[:-1]
        ):
            continue
    
        if path.suffix.lower() not in EXTENSIONS:
            continue
    
        data = path.read_bytes()
    
        try:
            data.decode("utf-8")
        except UnicodeDecodeError:
            encoding_errors.append(str(path))
            continue
    
        if b"\r\n" in data:
            invalid_files.append(str(path))
    
    if encoding_errors:
        print("[ERROR] Invalid UTF-8 files:")
        for name in encoding_errors:
            print(name)
    
    if invalid_files:
        print("[ERROR] CRLF detected:")
        for name in invalid_files:
            print(name)
    
    if encoding_errors or invalid_files:
        sys.exit(1)
    
    print("[SUCCESS] UTF-8 and LF validation passed.")
    

    이 스크립트는 다음 항목을 검사한다.

    1. 지정된 확장자의 소스 파일인지 확인
    2. 제외 디렉터리에 포함되는지 확인
    3. UTF-8 디코딩이 가능한지 확인
    4. CRLF 줄바꿈이 존재하는지 확인
    5. 문제가 발견되면 종료 코드 1 반환

    Jenkins에서는 종료 코드가 0이 아니면 해당 단계를 실패 처리할 수 있다.

    12.2 Jenkinsfile 적용

    pipeline {
        agent any
    
        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }
    
            stage('Validate Source Encoding') {
                steps {
                    sh '''
                        python3 scripts/check-line-endings.py
                    '''
                }
            }
    
            stage('Build') {
                steps {
                    echo 'Starting build process'
                }
            }
    
            stage('Test') {
                steps {
                    echo 'Starting test process'
                }
            }
    
            stage('Deploy') {
                steps {
                    echo 'Starting deployment'
                }
            }
        }
    }
    

    이 구조에서는 줄바꿈 형식 검증을 통과한 이후에만 빌드와 배포가 진행된다.

    단, 실제 운영 환경에서는 Build, Test, Deploy 단계에 프로젝트별 빌드 및 배포 명령을 적용해야 한다.


    13. Jenkins에서 자동 변환과 자동 검증 중 무엇이 좋은가?

    문제를 해결하는 방법은 크게 두 가지다.

    방법 1. Jenkins에서 dos2unix 자동 변환

    Git Checkout 이후 dos2unix를 실행한다.

    Checkout
       │
       ▼
    dos2unix
       │
       ▼
    Build
       │
       ▼
    Deploy
    

    장점은 기존 파일을 자동으로 LF 형식으로 변환할 수 있다는 점이다.

    하지만 소스 파일을 Jenkins 작업 디렉터리에서 직접 변경한다는 문제가 있다.

    실제 Git 저장소에서 가져온 소스와 빌드에 사용된 소스의 바이트가 달라질 수 있다.

    또한 형상관리 시스템의 검증 기준에 따라 변경된 파일이 별도의 형상 변경으로 인식될 수도 있다.

    방법 2. Git 저장소에서 정책 통일 후 Jenkins 검증

    Developer
       │
       ▼
    .gitattributes / .editorconfig
       │
       ▼
    Git Repository
       │
       ▼
    Jenkins Checkout
       │
       ▼
    UTF-8 / LF Validation
       │
       ▼
    Build
       │
       ▼
    Deploy
    

    이 방식은 소스의 형식을 Git 저장소와 개발 환경에서 관리하고, Jenkins에서는 형식 검증에 집중한다.

    장기적인 운영 관점에서는 두 번째 방식이 일반적으로 더 적합하다.

    다만 기존 형상관리 솔루션의 구조를 즉시 변경하기 어렵다면 Jenkins에서 제한적인 파일 변환을 수행하는 방식도 현실적인 대응책이 될 수 있다.

    중요한 것은 자동 변환이 원본 형상과 빌드 형상 사이에 어떤 차이를 만드는지 명확하게 관리하는 것이다.


    14. 실제 개선 과정에서 정리한 핵심 사항

    이번 문제를 분석하면서 단순히 dos2unix 명령어로 파일을 변환하는 것보다 중요한 것은 전체 형상관리 프로세스의 일관성이라는 점을 확인했다.

    처음에는 개별 파일의 형식 차이를 확인하는 수준에서 출발했다.

    그러나 실제 운영 환경에서는 하위 디렉터리 전체의 파일 검색, Git 내부 디렉터리 제외, Next.js 빌드 산출물 제외, Jenkins 임시 디렉터리 처리 등의 문제도 함께 고려해야 했다.

    개선 과정을 단계별로 정리하면 다음과 같다.

    14.1 1단계: 형상 불일치 원인 확인

    file filename.tsx
    

    파일이 UTF-8인지, CRLF가 존재하는지 확인했다.

    14.2 2단계: 실제 바이트 확인

    xxd filename.tsx
    

    LF와 CRLF의 바이트 차이를 확인했다.

    14.3 3단계: Git 상태 확인

    git ls-files --eol
    

    Git 인덱스와 작업 디렉터리의 줄바꿈 상태를 비교했다.

    14.4 4단계: 줄바꿈 변환

    dos2unix -q filename.tsx
    

    문제 파일을 LF 형식으로 변환했다.

    14.5 5단계: 대량 처리 방법 개선

    find ./src -type f -name '*.tsx' -exec dos2unix -q {} +
    

    여러 파일을 일괄 처리할 수 있도록 구성했다.

    14.6 6단계: 불필요한 디렉터리 제외

    find . \
      \( -type d \( \
          -name '.git' -o \
          -name '.next' -o \
          -name 'node_modules' -o \
          -name '*@tmp' \
        \) -prune \) \
      -o \
      \( -type f -name '*.tsx' -print \)
    

    검색 범위에서 제외할 디렉터리를 명확하게 정의했다.

    14.7 7단계: Git 및 CI/CD 표준화 방향 도출

    일시적인 파일 변환 외에도 .gitattributes, .editorconfig, Jenkins 사전 검증을 활용할 수 있도록 운영 표준화 방향을 정리했다.

    여기서 .gitattributes와 Jenkins 검증 자동화는 실제 적용 범위에 따라 별도 개선 단계로 관리할 수 있다.


    15. 개선 전후 프로세스 비교

    비교 항목기존 방식개선 방향
    파일 형식Windows/Linux 환경별 차이UTF-8 + LF 표준화
    형상 검사형상 솔루션에서 차이 발생사전 검사 체계
    문제 분석개별 파일 수동 확인file, xxd, Git 명령 활용
    변환 방식파일 단위 수동 처리find + dos2unix 일괄 처리
    제외 경로경로별 개별 처리-prune 공통 정책
    Jenkins 임시 파일검색 대상 포함 가능@tmp 패턴 제외
    Git 설정개발 환경별 차이 가능.gitattributes 적용 검토
    재발 방지문제 발생 이후 조치Git 정책 및 CI 검증

    이러한 개선은 단순히 특정 파일의 오류를 해결하는 것 이상의 의미가 있다.

    형상관리 정책을 일관되게 유지하면 불필요한 파일 변경과 코드 리뷰의 혼란을 줄일 수 있다.

    또한 개발 환경이 서로 달라도 빌드 및 배포 과정에서 예측 가능한 소스 형식을 유지하는 데 도움이 된다.

    다만 실제 개선 효과를 수치로 표현하려면 적용 전후의 검증 오류 건수, 형상 검사 시간, 재작업 시간 등을 별도로 측정해야 한다.


    16. 운영 환경에서 주의해야 할 사항

    16.1 CRLF에서 LF로 변경해도 항상 안전한가?

    대부분의 일반적인 Java, JavaScript, TypeScript 소스 파일에서는 줄바꿈 형식 변경이 코드의 의미에 영향을 주지 않는다.

    하지만 모든 파일에 대해 안전하다고 단정할 수는 없다.

    다음과 같은 경우에는 별도의 검증이 필요하다.

    • 문자열 리터럴에 줄바꿈이 의미를 갖는 경우
    • 테스트 데이터와 스냅샷 파일
    • 바이너리 형식으로 관리되는 파일
    • 해시 또는 디지털 서명을 사용하는 파일
    • 파일 전체의 바이트 값을 비교하는 검증 로직
    • 일부 셸 스크립트 및 외부 시스템 연동 파일

    따라서 줄바꿈 변경 이후에는 관련 빌드와 테스트를 수행하는 것이 좋다.

    16.2 .git 디렉터리는 반드시 제외

    .git 디렉터리에는 Git 객체와 인덱스, 참조 정보 등이 저장된다.

    이 데이터를 임의로 변경하면 저장소에 문제가 발생할 수 있다.

    일괄 파일 변환 시 .git은 반드시 제외하는 것이 안전하다.

    16.3 node_modules와 .next는 변환 대상에서 제외

    node_modules는 외부 의존성 패키지가 저장되는 디렉터리다.

    .next는 Next.js의 빌드 과정에서 생성되는 산출물 및 캐시 디렉터리다.

    일반적인 소스 형상 정규화 작업에서는 이러한 디렉터리들을 제외해야 한다.

    16.4 Git 변경 내역 확인

    대량 변환 전에는 현재 변경 사항을 확인하는 것이 좋다.

    git status
    

    변환 후에는 변경된 파일을 확인한다.

    git diff --stat
    

    줄바꿈 차이를 무시한 상태에서 실질적인 코드 변경이 있는지 참고하려면 다음 명령어도 사용할 수 있다.

    git diff --ignore-space-at-eol
    

    단, 실제 형상 변경을 확인할 때는 줄바꿈을 무시하지 않은 원본 diff도 함께 확인해야 한다.

    16.5 줄바꿈 정규화와 기능 개발은 분리

    형상 표준화 작업은 가급적 별도의 커밋으로 관리하는 것이 좋다.

    예를 들어 다음과 같은 메시지를 사용할 수 있다.

    chore: normalize UTF-8 line endings
    

    이렇게 하면 추후 형상 이력 분석이나 코드 리뷰에서 줄바꿈 변경과 기능 변경을 쉽게 구분할 수 있다.


    17. 자주 묻는 질문 FAQ

    Q1. u8-unix와 u8-dos는 무엇이 다른가?

    일반적으로 UTF-8 텍스트 파일의 줄바꿈 방식에 따른 차이다.

    u8-unix는 LF, u8-dos는 CRLF 형식을 나타내는 데 사용될 수 있다.

    두 형식 모두 UTF-8 텍스트일 수 있으며, 실제 분류 기준은 사용 중인 도구에 따라 확인해야 한다.

    Q2. Linux에서 CRLF 파일을 확인하는 방법은?

    다음 명령어를 사용할 수 있다.

    file filename.tsx
    

    출력 결과에 CRLF line terminators가 표시되는지 확인한다.

    정확한 바이트 확인은 xxd나 od 명령어를 사용한다.

    Q3. Git에서 CRLF와 LF 차이를 확인할 수 있는가?

    다음 명령어를 사용한다.

    git ls-files --eol
    

    Git 인덱스와 작업 디렉터리의 줄바꿈 형식을 확인할 수 있다.

    Q4. dos2unix를 하위 디렉터리에 일괄 실행하는 방법은?

    다음 명령어를 사용할 수 있다.

    find ./src -type f -name '*.tsx' -exec dos2unix -q {} +
    

    특정 확장자의 소스 파일만 선택해서 처리하는 것이 안전하다.

    Q5. find에서 모든 .next 디렉터리를 제외하려면?

    다음과 같이 -prune과 -name 조건을 함께 사용할 수 있다.

    find . -type d -name '.next' -prune
    

    실제 파일 검색 명령에서는 다른 제외 조건 및 파일 출력 조건과 조합해야 한다.

    Q6. Git에서 줄바꿈을 LF로 통일하는 방법은?

    .gitattributes에 다음과 같이 설정할 수 있다.

    *.ts text eol=lf
    *.tsx text eol=lf
    *.js text eol=lf
    *.jsx text eol=lf
    

    기존 파일의 인덱스 정규화에는 git add --renormalize .를 사용할 수 있다.

    Q7. Jenkins에서 dos2unix를 자동으로 실행해도 되는가?

    가능하지만 주의가 필요하다.

    작업 디렉터리의 소스를 직접 수정하므로 Git 저장소의 원본 형상과 실제 빌드 형상이 달라질 수 있다.

    따라서 장기적으로는 Git의 줄바꿈 정책을 통일하고 Jenkins에서 검증하는 방식을 권장한다.

    Q8. UTF-8 파일에서 CRLF를 LF로 변경하면 빌드에 문제가 없는가?

    일반적인 소스 파일에서는 대부분 문제가 없지만 모든 경우에 안전한 것은 아니다.

    정확한 바이트가 중요한 파일이나 테스트 데이터에서는 영향이 발생할 수 있다.

    변환 후 빌드 및 테스트를 수행하는 것이 좋다.

    Q9. Git diff에 변경이 없는데 외부 형상관리 솔루션에서 오류가 발생할 수 있는가?

    가능하다.

    Git은 저장소 또는 인덱스 기준으로 줄바꿈을 정규화할 수 있지만 외부 형상관리 솔루션은 작업 디렉터리에 존재하는 파일의 실제 바이트를 검사할 수 있다.

    따라서 Git의 비교 결과와 외부 솔루션의 형상 검사 결과가 다르게 나타날 수 있다.

    Q10. .gitattributes와 .editorconfig를 함께 사용해야 하는가?

    두 파일은 서로 다른 역할을 한다.

    .gitattributes는 Git이 파일을 저장하고 체크아웃할 때 적용할 속성을 정의한다.

    .editorconfig는 지원하는 개발 도구에서 파일 저장 시 적용할 문자셋과 줄바꿈 등의 규칙을 정의한다.

    두 설정을 함께 적용하면 Git 저장소와 개발자 편집기 간 일관성을 높일 수 있다.


    18. 결론: 형상관리의 핵심은 소스 코드의 일관성

    이번 문제는 처음에는 일부 TypeScript와 TSX 파일에서 발생하는 단순한 형상 불일치 현상으로 보였다.

    하지만 원인을 분석하는 과정에서 UTF-8 인코딩, LF와 CRLF 줄바꿈, Git 정규화 설정, Linux 작업 디렉터리의 파일 형식, Jenkins 실행 환경 사이의 관계를 확인할 수 있었다.

    핵심은 소스 코드의 내용이 동일하더라도 파일의 실제 바이트가 다를 수 있다는 점이다.

    특히 형상관리 솔루션이 파일의 인코딩 또는 줄바꿈 형식을 검증하는 환경에서는 이러한 차이가 오류로 이어질 수 있다.

    문제를 해결하기 위해서는 다음과 같은 접근이 필요하다.

    첫째, 현상보다 실제 파일의 바이트 구조를 먼저 확인해야 한다.

    file, xxd, git ls-files --eol과 같은 명령어를 이용하면 파일 형식과 Git 상태를 구분해서 분석할 수 있다.

    둘째, 일시적인 문제 해결과 재발 방지를 구분해야 한다.

    dos2unix를 활용하면 현재 발생한 CRLF 문제를 해결할 수 있다.

    하지만 장기적으로는 .gitattributes와 같은 저장소 정책을 이용하는 것이 보다 일관된 방법이다.

    셋째, 자동화 프로세스에서는 처리 대상을 명확하게 정의해야 한다.

    .git, .next, node_modules, Jenkins 임시 디렉터리 등을 제외하지 않은 대량 파일 작업은 불필요한 변경이나 성능 저하를 발생시킬 수 있다.

    넷째, CI/CD 환경에서는 형상 검증을 가능한 한 앞 단계에서 수행해야 한다.

    소스 코드의 형식 문제를 빌드 또는 배포 단계 이후에 발견하는 것보다 체크아웃 직후에 검증하는 것이 문제 분석과 관리에 유리하다.

    Git과 Jenkins를 활용한 CI/CD 자동화는 단순히 빌드 및 배포 명령어를 실행하는 작업이 아니다.

    개발자가 작성한 소스가 어떤 형태로 Git에 저장되고, 어떤 방식으로 Jenkins에서 체크아웃되며, 실제 형상관리 솔루션과 운영 환경에 전달되는지 전체 흐름을 이해하고 관리해야 한다.

    이번 사례는 작은 줄바꿈 차이가 실제 기업 환경에서 형상관리 프로세스의 안정성과 일관성에 영향을 줄 수 있다는 점을 확인한 경험이었다.

    궁극적으로 중요한 것은 오류가 발생할 때마다 파일을 수정하는 것이 아니라, 개발 환경의 차이와 관계없이 일관된 소스 형상을 유지할 수 있도록 Git 및 CI/CD 프로세스를 설계하는 것이다.


    참고 자료


    Gomdoc 기술 블로그

    DevOps · Linux · Git · Jenkins · CI/CD · Kubernetes · Cloud Architecture

    실제 기업 개발 및 운영 환경에서 경험한 기술 이슈와 해결 과정을 정리합니다.

    소개자로 : https://gomdoc.com/about


    원문: https://gomdoc.com/git-%ed%98%95%ec%83%81%ea%b4%80%eb%a6%ac-crlf-lf-utf8-jenkins-fix/

    Advertisement
    Git 형상관리 UTF-8 인코딩 차이로 발생하는 형상 불일치 해결 방법
    https://gomdoc.com/git-형상관리-crlf-lf-utf8-jenkins-fix/
    저자
    곰독
    게시일
    2026-11-03
    라이선스
    CC BY-NC-SA 4.0