🗺️ sitemap이란?
sitemap이란 검색엔진이 크롤링하여 색인할 수 있도록 모든 웹페이지를 나열한 XML 파일이다. 검색엔진 크롤러가 접근하기 어려운 페이지를 포함한 모든 페이지의 정보를 제공하여 빠짐없이 색인될 수 있도록 돕는다.
주의할 점은 sitemap이 직접적으로 검색엔진 결과의 순위를 올려주지는 않는다는 것이다.
💡 sitemap 생성 방법 (jekyll로 생성한 GitHub 블로그 기준)
아래 설명은 jekyll로 생성한 깃허브 블로그에 sitemap을 생성하는 것을 기준으로 한다. 만약 jekyll로 생성한 깃허브 블로그가 아니라면 아래 방법으로는 sitemap 파일을 생성할 수 없다.
1. 로컬의 .github.io
폴더의 Gemfile
파일에 다음 코드를 추가하기
여기서 Gemfile
은 ruby 프로젝트에 필요한 gem의 목록과 버전을 관리해주는 파일이다. 프로젝트에서 사용하는 라이브러리(이를 gem이라고 부름)를 관리하는 역할을 한다.
Gemfile
을 통해 프로젝트에서 사용할 gem들을 명시하고, 이를 Bundler
라는 툴이 관리해준다. Bundler
는 Gemfile
에 명시된 gem들이 서로 호환되는 버전으로 설치되도록 도와준다.
2. _config.yml
에 다음과 같이 플러그인을 추가하기
plugins:
- jekyll-sitemap
3. 터미널에서 bundle
명령어를 실행하기
bundle
(=== bundle install
) 명령어는 다음과 같은 일을 진행한다.
Gemfile
을 읽어서 필요한 gem들을 확인하고
Gemfile.lock
파일이 있으면, 그 파일에 기록된 정확한 버전을 설치하고,
Gemfile.lock
파일이 없으면 호환 가능한 최신 버전으로 설치한 후 Gemfile.lock
파일을 생성해준다.
그래서 보통 Rails나 Ruby 프로젝트를 처음 설치하거나 업데이트할 때, 자주 쓰는 명령어이다.
즉, bundle
명령어는 Bundler를 이용해서 Gemfile
에 적힌 gem들을 설치한다.
실행 결과 예시:
Bundle complete! 3 Gemfile dependencies, 47 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
터미널에 bundle 실행이 정상적으로 진행됐을 때 결과 이미지:

위와 같이 명령어 실행 결과가 나타났다면, 와우! 당신은 이제 더이상 할게 없다. 바로 4번을 실행해주면 된다.
그러나 만약 bundle 명령어를 실행했는데 아래 이미지와 같이 에러가 발생다면?

그리고 gem install bundler
명령어를 실행해봤는데 또 에러가 발생했다면? 글을 계속 내려 에러를 해결해보자.

🚨 ERROR: While executing gem ... (Gem::FilePermissionError)
에러 해결 방법
에러 발생 원인
해당 에러는 macOS에 기본으로 설치되어 있는 ruby의 gem 디렉토리(/Library/Ruby/Gems/2.6.0
)에 쓰기 권한이 없어서 발생한 것이다. macOS의 기본 ruby는 보안상 사용자에게 gem 설치 권한을 제공하지 않는다.
따라서 gem 설치 시(아래 명령어) 권한 에러가 발생한다.
에러 발생 이미지:

해당 에러의 해결 방법은 두 가지가 있다.
1️⃣ 관리자 권한으로 설치하기
권장하지 않는 방법이다.
단, sudo를 사용하면 관리자 권한으로 명령어가 실행되기 때문에, 만약 설치하려는 gem이나 명령어에 문제가 발생하면 시스템 전체에 영향을 줄 수 있어 위험할 수 있다. 따라서 해당 방법은 권장되지 않는다.
2️⃣ Ruby 버전 관리 도구(rbenv
)를 활용하기
가장 안전하고 권장되는 방법이다.
rbenv
같은 ruby 버전 관리 도구를 사용하면 사용자 디렉토리 내에서 ruby를 별도로 관리할 수 있어서, 시스템 디렉토리에 접근할 필요가 없다. 이 방법을 사용하면 권한 문제를 피할 수 있다.
이 글에서는 rbenv
를 활용하여 에러를 해결할 것이다.
😎 rbenv
를 활용하여 에러 해결하기
-
rbenv
설치
homebrew를 활용하여 rbenv
와 ruby-build
를 설치한다.
brew update
brew install rbenv ruby-build
여기서 ruby-build
는 rbenv
와 함께 사용되는 플러그인으로, 다양한 ruby 버전을 쉽게 설치할 수 있도록 지원해준다. rbenv
와 함께 ruby-build
를 설치하면 ruby 버전 관리와 함께 새로운 ruby 버전 설치도 한 번에 준비할 수 있어서 더 편리하다.
-
설치한 rbenv
와 ruby 버전 확인하기
꼭 필요한 단계는 아니지만, 본격적으로 rbenv
를 사용하여 ruby 버전을 관리하기 전의 상태가 어떤지 확인해줬다.
> rbenv version
* system
> ruby --version
ruby 2.6.10p210
rbenv
를 통해 ruby 버전을 관리하기전 설치되었던 ruby의 버전은 2.6.10
이다. 나는 글 작성 기준 가장 안정된 버전인 3.4.2
로 ruby의 버전을 관리할 계획이다.
실제 터미널 실행 결과 이미지:

-
rbenv
를 활용하여 Ruby 버전 설치 (버전: 3.4.2
)
실제 터미널 실행 결과 이미지:

설치가 정상적으로 되었다면, version 확인을 했을 때 아래와 같이 출력된다.
> rbenv version
system
* 3.4.2
-
rbenv
로 global 버전을 3.4.2
로 설정 및 확인
rbenv
로 global 버전 또한 3.4.2
로 설정해준다.
> rbenv global 3.4.2
> rbenv version
3.4.2 (set by /Users/사용자이름/.rbenv/version)
> ruby --version
ruby 3.4.2 (2025-02-15 revision d2930f8e7a)
global 버전 설정 이전에 ruby 버전을 확인해보면 이전에 설치되어있던 버전으로 뜬다. (rbenv
의 버전을 3.4.2
로 설치한 후 임에도.)
global 버전 설정을 한 후 ruby 버전을 다시 확인하면 3.4.2
버전으로 설치되어 있는 것을 확인할 수 있다.
실제 터미널 실행 결과 이미지:

-
쉘 설정 파일에 rbenv
설정 추가
쉘 설정 파일을 수정하기 위해 아래 코드를 터미널에 실행하여 쉘 설정 파일을 vscode로 열어준다.
그리고 .zshrc
파일을 열어 아래 코드를 추가한다.
[[ -d ~/.rbenv ]] && \
export PATH=${HOME}/.rbenv/bin:${PATH} && \
eval "$(rbenv init -)"
코드 설명

[[ -d ~/.rbenv ]]
: rbenv가 설치되어 있는지 확인한다. rbenv가 설치되어 있지 않다면 이후 명령어들이 실행되지 않아 불필요한 오류를 방지할 수 있다.
export PATH=${HOME}/.rbenv/bin:${PATH}
: rbenv 명령어를 터미널에서 인식할 수 있도록 경로를 추가한다.
eval "$(rbenv init -)"
: rbenv 초기화 스크립트를 실행하여 Ruby 버전 관리 기능을 활성화한다.
이 코드는 rbenv
가 설치된 경우에만 rbenv
의 경로를 추가하고 초기화를 진행하여, 이후에 ruby 버전 관리를 원활하게 할 수 있도록 도와준다.
즉, 이 코드를 쉘 설정 파일(.zshrc
등)에 추가하면, 매번 터미널을 열 때마다 rbenv
가 자동으로 초기화되어 ruby 버전을 쉽게 관리할 수 있게 된다. 만약 이 코드를 추가하지 않으면, rbenv
명령어를 인식하지 못하거나, ruby 버전 전환 등의 기능을 제대로 사용할 수 없게 된다.
-
변경한 쉘 설정 적용
자, 이제 모든 작업은 끝이 났다. 이제 정상적으로 작동하는지 확인해보자.
bundle
이 정상적으로 실행되는 지 실행하기
이제 다시 블로그 루트 디렉토리에서 bundle
명령어를 실행한다.
> bundle
Bundle complete! 3 Gemfile dependencies, 47 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
위와 같은 실행 결과가 나타났다면 정상적으로 bundle
설치가 완료된 것이다!
4. jekyll-sitemap
플러그인 설치하기
아래 명령어를 실행하여 jekyll-sitemap
플러그인을 설치해보자. 해당 플러그인은 jekyll의 sitemap을 자동으로 만들어준다.
gem install jekyll-sitemap
아래와 같이 응답 결과가 출력되었다면 정상적으로 플러그인이 설치가 된 것이다.
Successfully installed jekyll-sitemap-1.4.0
1 gem installed
실제 실행 결과 이미지:

5. GitHub에 변경사항을 커밋하고 푸시하기
변경사항을 github에 push하면 자동으로 사이트맵이 생성된다.
로컬에서는 파일이 보이지 않을 수 있지만, 실제 블로그 사이트에서 확인을 해보면 sitemap.xml
파일이 정상적으로 생성된 것을 확인할 수 있다.

📚 참고
📄 Session Storage
Session Storage(세션 스토리지) 는 탭(세션) 단위로 데이터를 저장하는 저장소이다. 브라우저 탭을 닫으면 데이터가 사라진다.
- 데이터 지속성: 브라우저 탭을 닫으면 삭제. (세션 동안만 유지)
- 저장 용량: 약 5MB
- 도메인 단위 저장: 같은 도메인 내에서만 접근 가능하지만, 탭별로 구분됨.
- 보안: 서버와 자동으로 주고받지 않음.
- 사용 예시: 임시 로그인 정보, 폼 데이터 임시 저장 (탭을 닫으면 사라지는 데이터)
⚒️ 예제 코드
// 데이터 저장
sessionStorage.setItem('sessionKey', '123456');
// 데이터 가져오기
const sessionData = sessionStorage.getItem('sessionKey');
console.log(sessionData); // "123456"
// 데이터 삭제
sessionStorage.removeItem('sessionKey');
// 전체 삭제
sessionStorage.clear();
🏠 Local Storage
Local Storage(로컬 스토리지) 는 브라우저에 데이터를 영구적으로 저장하는 저장소이다. 사용자가 브라우저를 닫고 다시 열어도 데이터가 유지된다.
- 데이터 지속성: 브라우저를 닫아도 유지. (영구 저장)
- 저장 용량: 약 5MB
- 도메인 단위 저장: 같은 도메인 내에서는 모든 페이지에서 접근 가능, 탭 별로 공유 가능.
- 보안: 서버와 자동으로 주고받지 않음. → 보안이 비교적 높음
- 사용 예시: 사용자의 다크 모드 설정, 자동 로그인 유지
⚒️ 예제 코드
// 데이터 저장
localStorage.setItem('username', 'soha');
// 데이터 가져오기
const username = localStorage.getItem('username');
console.log(username); // "soha"
// 데이터 삭제
localStorage.removeItem('username');
// 전체 삭제
localStorage.clear();
🍪 Cookie
Cookie(쿠키) 는 작은 데이터를 클라이언트에 저장하고 서버와 자동으로 주고받을 수 있는 저장소이다. 주로 사용자 인증과 세션 관리를 위해 사용된다.
- 데이터 지속성: 유효기간 설정 가능. (기본적으로 브라우저 종료 시 삭제)
- 저장 용량: 약 4KB (가장 작음)
- 도메인 단위 저장: 특정 도메인 및 경로에서만 접근 가능, 탭 별로 공유 가능.
- 보안: 기본적으로 HTTP 요청마다 서버와 자동으로 주고받음. → 보안 설정이 중요함 (
Secure
, HttpOnly
, SameSite
등 옵션 활용)
- 사용 예시: 세션 유지, 사용자 인증 (JWT 토큰), 방문 기록
⚒️ 예제 코드 (프론트엔드에서 쿠키 저장)
// 쿠키 저장 (expires로 유효기간 설정 가능)
document.cookie = 'username=soha; expires=Fri, 31 Dec 2025 23:59:59 GMT; path=/';
// 쿠키 가져오기
console.log(document.cookie); // "username=soha"
// 쿠키 삭제 (유효기간을 과거로 설정)
document.cookie = 'username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
이 쿠키는 example.com(현재 실행한 도메인)
내 모든 페이지에서 접근 가능하다. 다른 탭에서 document.cookie
를 실행해도 같은 username=soha
값을 얻을 수 있다.
expires(유효 일자)
나 max-age(만료 기간)
옵션이 지정되어있지 않으면 브라우저가 닫힐 때 쿠키도 함께 삭제된다. 이런 쿠키를 세션 쿠키(session cookie) 라고 한다.
⛔️ 도메인 제한 (domain 속성)
-
서브 도메인까지 공유하기
document.cookie = 'user=soha; domain=example.com';
example.com
뿐만 아니라 sub.example.com
에서도 이 쿠키를 사용할 수 있다.
-
서브도메인에서는 쿠키를 못 쓰게 하기
document.cookie = 'user=soha; domain=example.com';
// 혹은
document.cookie = 'user=soha;'; // 실행한 도메인(example.com)에서만 사용 가능
example.com
에서만 쿠키를 사용할 수 있고, sub.example.com
에서는 사용할 수 없다.
⛔️ 경로 제한 (path 속성)
-
/admin
경로에서만 쿠키 사용하기
document.cookie = 'role=admin; path=/admin';
이 쿠키는 https://example.com/admin
페이지에서만 접근 가능하고,
https://example.com/user
에서는 document.cookie
로 가져올 수 없다.
-
/(루트)
에서 설정하면 모든 경로에서 사용 가능
document.cookie = 'theme=dark; path=/';
example.com
의 모든 경로에서 쿠키를 사용할 수 있다.
⛔️ https 통신 제한 (secure 속성)
-
https로 통신하는 경우에만 쿠키 전송
// (https:// 로 통신하고 있다고 가정 중)
document.cookie = 'user=soha; secure';
secure
옵션을 설정한 경우 https://example.com
에서 설정한 쿠키는 http://example.com
에서 접근할 수 없다. 쿠키에 민감한 내용이 저장되어 암호화되지 않은 HTTP 연결을 통해 전달되는 걸 원치 않을 경우 사용하면 좋다.
-
프로토콜 상관 없이 쿠키 전송
document.cookie = 'user=soha;';
secure
옵션이 없으면 http://example.com
에서 생성한 쿠키를 https://example.com
에서 읽을 수 있다. 즉, 서로 쿠키를 공유하게 된다. 쿠키는 도메인만 확인하고 프로토콜을 따지지 않기 때문이다.
🚨 주의
쿠키 저장은 프론트엔드에서도 가능하고, 백엔드에서도 가능하다.
하지만 보안이 중요한 쿠키 (예: 로그인 토큰) 는 보통 백엔드에서 설정하는게 안전하다. 브라우저에서 document.cookie
로 쉽게 접근할 수 있기 때문에, 악성 스크립트(XSS 공격)에 노출될 위험이 있기 때문이다.
⚒️ 예제 코드 (백엔드에서 쿠키 저장 예제 / Express.js)
백엔드에서는 응답 헤더에 Set-Cookie를 추가해서 쿠키를 저장한다.
res.cookie('token', 'secureToken123', {
httpOnly: true,
secure: true,
sameSite: 'Strict',
maxAge: 1000 * 60 * 60 * 24, // 1일 동안 유지
});
httpOnly: true
→ JS에서 접근 불가능 (XSS 공격 방지)
secure: true
→ HTTPS에서만 쿠키 전송
sameSite: "Strict"
→ CSRF 공격 방지
maxAge
→ 쿠키 유효 시간 설정 (밀리초 단위)
백엔드에서 이렇게 설정하면 클라이언트에서는 document.cookie
로 접근할 수 없고, HTTP 요청 시 자동으로 쿠키가 전송된다.
📝 정리
특징 |
Session Storage |
Local Storage |
Cookie |
데이터 지속성 |
탭을 닫으면 삭제 |
브라우저 닫아도 유지 |
설정한 유효기간까지 유지 |
저장 용량 |
약 5MB |
약 5MB |
약 4KB (가장 작음) |
브라우저에서 접근 |
sessionStorage API 사용 |
localStorage API 사용 |
document.cookie 사용 |
서버와의 통신 |
서버로 전송되지 않음 |
서버로 전송되지 않음 |
HTTP 요청 시 자동 전송 가능 |
보안 |
비교적 안전 |
비교적 안전 |
HttpOnly , Secure 설정 필요 |
사용 예시 |
임시 데이터 저장, 입력 폼 보존 |
사용자 설정 저장, 다크 모드 |
로그인 세션 유지, 인증 토큰 저장 |
🤔 어떤 걸 써야 할까?
- 탭을 닫으면 사라져야 하는 데이터 →
Session Storage
- 장기적인 데이터 저장 →
Local Storage
- 서버와 자동으로 데이터를 주고받아야 하는 경우 →
Cookie
📚 참고
github에서 pr을 생성하고 해당 pr들을 merge를 하려고 클릭하면 다양한 방식의 merge 방법이 존재하는 것을 볼 수 있다.

각 merge 방법들이 무엇인지 알아보도록 하자.
📋 목차
🫱🏻🫲🏼 Git Merge (feat. fast-forward)
git merge
는 서로 다른 브랜치의 변경 사항을 하나의 브랜치에 통합하는 명령이다.
이 방식은 두 브랜치의 히스토리를 모두 보존하며, 보통 새로운 merge 커밋을 생성한다.
단, 대상 브랜치(main)가 feature 브랜치의 기반(commit) 상태로 아무런 추가 변경 사항이 없을 경우에는 fast-forward merge
가 발생한다. fast-forward merge는 별도의 merge 커밋 없이 브랜치 포인터만 앞으로 이동시켜 히스토리를 단순하게 유지한다.
✍🏻 커밋 기록
일반 Merge의 경우, feature 브랜치의 모든 커밋이 그대로 남고 새로운 merge 커밋이 추가된다.
반면, fast-forward Merge의 경우는 main 브랜치의 포인터만 이동되므로, merge 커밋이 생성되지 않고 feature 브랜치의 커밋들이 그대로 이어진다.
🪢 커밋 기록 예시
일반 Merge
Merge 전:
A --- B --- C (main)
\
D --- E --- F (feature)
Merge 후:
A --- B --- C --- M (main)
\ /
D --- E --- F (feature)
여기서 M 커밋은 merge 커밋으로, feature 브랜치의 커밋(D, E, F)과 main의 커밋(C)을 모두 포함한다. 따라서 main 브랜치에는 feature 브랜치의 각 커밋이 그대로 기록되어 있다.
fast-forward Merge:
fast-forward merge가 가능할 경우에 옵션을 사용하여 원하는 형태로 merge가 가능하다.
Merge 전:
A --- B --- C (main)
\
D --- E (feature)
Merge 후:
-
fast-forward 병합 (--no-ff
옵션 미사용):
A --- B --- C --- D --- E (main)
위 경우, merge 커밋이 생성되지 않는다.
-
--no-ff
옵션 사용:
A --- B --- C --- M (main)
/
D --- E (feature)
여기서 M 커밋은 feature 브랜치의 변경사항을 포함하는 merge 커밋으로, feature가 병합되었다는 사실을 명확하게 기록한다.
💬 명령어
일반 Merge
git checkout main
git merge feature
위 명령어는 feature 브랜치의 변경 사항을 main 브랜치에 병합하며, fast-forward가 가능한 경우 별도의 merge 커밋 없이 포인터만 이동한다.
강제 Merge (fast-forward 방지)
git checkout main
git merge --no-ff feature
위 명령어는 fast-forward가 가능한 상황에서도 merge 커밋을 강제로 생성하여 병합 기록을 명확하게 남긴다.
📝 실제 커밋 로그 예시
일반 Merge (merge 커밋 생성)
* 3e1d2f0 (HEAD -> main) Merge branch 'feature'
|\
| * 9c8b7a6 (feature) Add feature part 3
| * 8b7a6c5 Add feature part 2
| * 7a6c5b4 Add feature part 1
* | 6d5c4b3 Fix typo in main file
* | 5c4b3a2 Update documentation
* | 4b3a291 Initial commit
fast-forward Merge (merge 커밋 없이 포인터 이동)
* 9c8b7a6 (HEAD -> main, feature) Add feature part 3
* 8b7a6c5 Add feature part 2
* 7a6c5b4 Add feature part 1
* 6d5c4b3 Fix typo in main file
* 5c4b3a2 Update documentation
* 4b3a291 Initial commit
🔖 Git Rebase
git rebase
는 feature 브랜치의 커밋들을 대상 브랜치(main)의 최신 커밋 이후로 재배치하는 명령이다.
이를 통해 커밋 히스토리가 선형으로 정리되어 깔끔하게 보인다. 다만, 이미 공유된 커밋에 대해 rebase를 수행하면 충돌이나 협업에 문제가 발생할 수 있으므로 주의하여 사용해야 한다.
✍🏻 커밋 기록
Rebase를 사용하면 feature 브랜치의 커밋들이 새로운 커밋(해시가 변경됨)으로 재작성되어 main 브랜치에 순서대로 기록된다. 이때 merge 커밋은 생성되지 않으며, 커밋 간의 관계가 단순한 선형 구조를 이룬다.
🪢 커밋 기록 예시
Rebase 전:
A --- B --- C (main)
\
D --- E --- F (feature)
Rebase 후 (feature 브랜치가 main의 최신 커밋 뒤로 재배치됨):
이후 fast-forward 방식으로 main에 병합한다면, main 브랜치는 아래와 같이 된다.
A --- B --- C --- D' --- E' --- F' (main)
feature 브랜치의 각 커밋(D’, E’, F’)이 하나씩 순서대로 main 브랜치에 들어간다.
rebase and merge시, 충돌 주의
다른 개발자가 로컬에서 아직 rebase 이전의 feature 브랜치를 기반으로 작업하여 추가 커밋을 진행한 상태라고 가정해보자.
rebase된 feature 브랜치(D’, E’, F’)와 기존 로컬 브랜치(D, E, F)의 히스토리 차이로 인해 병합 시 충돌이나 혼란이 발생할 수 있다. feature 브랜치의 커밋들이 rebase를 통해 해시가 변경된 새로운 커밋을 가지기 때문이다.
다른 개발자가 가진 rebase가 실행되지 않은 feature 브랜치에 새로운 커밋을 찍고 rebase된 브랜치(공유됨)에 merge를 실행할 경우 커밋의 해시 값이 다르기 때문에 충돌이 발생한다.
💬 명령어
-
Rebase 수행
git checkout feature
git rebase main
위 명령어는 feature 브랜치의 커밋들을 main 브랜치의 최신 커밋 뒤로 재배치한다.
-
충돌 해결 후 rebase 계속 진행
만약 rebase 과정에서 충돌이 발생하면, 충돌을 해결한 후 위 명령어로 rebase를 이어간다.
-
Rebase 완료 후 main에 통합 (fast-forward)
git checkout main
git merge feature
rebase가 완료된 feature 브랜치는 main 브랜치에 fast-forward 방식으로 병합할 수 있다.
📝 실제 커밋 로그 예시
* 9c8b7a6 (HEAD -> main, feature) Add feature part 3 (rebased)
* 8b7a6c5 Add feature part 2 (rebased)
* 7a6c5b4 Add feature part 1 (rebased)
* 6d5c4b3 Fix typo in main file
* 5c4b3a2 Update documentation
* 4b3a291 Initial commit
✊ Git Squash Merge
git squash merge
는 feature 브랜치의 여러 커밋들을 하나의 커밋으로 압축하여 대상 브랜치에 병합하는 방식이다.
이 방법을 사용하면 기능 개발 과정의 중간 커밋들을 합쳐서 깔끔한 커밋 이력을 유지할 수 있다. 단, 개별 커밋의 세부 기록은 남지 않으므로, 상세한 변경 이력을 확인하기 어렵다는 단점이 있다.
✍🏻 커밋 기록
Squash Merge를 수행하면, feature 브랜치의 여러 커밋이 하나의 커밋으로 압축되어 main 브랜치에 반영된다. 이 경우, feature 브랜치의 세부적인 커밋 내역은 사라지고, 하나의 통합 커밋으로 나타난다.
🪢 커밋 기록 예시
Squash Merge 전:
A --- B --- C (main)
\
D --- E --- F (feature)
Squash Merge 후:
A --- B --- C --- S (main)
여기서 S 커밋은 feature 브랜치의 변경 사항(D, E, F)을 모두 합친 하나의 커밋이다. 따라서 main 브랜치에는 feature 브랜치의 개별 커밋들이 아닌, 하나의 압축된 커밋만 기록된다.
💬 명령어
git checkout main
git merge --squash feature
git commit -m "Squash merge: feature 브랜치의 변경 사항 통합"
위 명령어는 먼저 main 브랜치로 전환한 후 feature 브랜치의 모든 변경 사항을 하나로 합치고, 마지막으로 커밋 메시지를 작성하여 하나의 커밋으로 통합한다.
📝 실제 커밋 로그 예시
* abcdef0 (HEAD -> main) Squash merge: feature 브랜치의 변경 사항 통합
* 6d5c4b3 Fix typo in main file
* 5c4b3a2 Update documentation
* 4b3a291 Initial commit
위와 같이 Git Merge, Rebase, Squash Merge 방식은 각각의 목적과 상황에 따라 사용되며, 커밋 기록 방식에도 차이가 있다. 프로젝트의 관리 방식과 기록의 세부사항 유지 여부에 따라 적절한 방식을 선택하여 사용하면 된다.
📝 정리
병합 방식 |
설명 |
장점 |
단점 |
git merge |
두 브랜치의 변경사항을 병합하며 별도의 merge commit 생성 |
작업 내역이 명확하게 남음 |
히스토리가 복잡해질 수 있음 |
rebase merge |
커밋들을 대상 브랜치 위에 재배치하여 선형 히스토리 유지 |
깔끔하고 추적이 쉬운 히스토리 |
공유된 커밋에 사용 시 혼란 발생 가능 |
squash merge |
여러 커밋을 하나로 합쳐 이력을 단순하게 정리 |
커밋 이력이 간결해짐 |
세부 변경 내역 확인이 어려워짐 |
📚 참고
👛 Git stash
git stash
는 현재 작업 중인 변경 사항을 임시로 저장하는 명령어다. 작업 도중 코드 변경 사항이 있지만 바로 커밋할 수 없는 상황에서 유용하게 사용할 수 있다.
언제 사용할까?
- 다른 브랜치로 이동해야 할 때: 작업 도중 브랜치를 변경해야 하지만 아직 변경 사항을 커밋할 준비가 안 된 경우 stash를 사용한다.
- 작업 중 코드 백업: 임시로 코드 변경 사항을 저장하고, 나중에 복원하여 이어서 작업할 때 사용한다.
- 실험적인 코드 작성: 특정 코드 시도를 stash에 저장해두고 이후 필요에 따라 되돌리거나 삭제할 수 있다.
사용 방법
변경 사항 임시 저장
변경 사항이 모두 stash에 저장되고 워킹 디렉터리가 깨끗한 상태가 된다.
아래는 git stash
와 같이 사용할 수 있는 다양한 옵션들이다.
-
--keep-index
옵션
스테이징된(인덱스된) 변경 사항은 유지하고 워킹 디렉터리 변경만 stash에 저장한다.
기본적으로 git stash
는 스테이징된 변경 사항도 stash에 포함하지만, 이 옵션은 스테이징된 변경 사항을 보존하고 워킹 디렉터리 변경만 저장하는 특징이 있다.
-
--all
옵션
워킹 디렉터리의 모든 변경 사항(추적된 파일 + 추적되지 않은 파일)을 stash에 저장한다.
기본적으로 git stash
는 추적된 파일만 저장하므로, 추가적으로 .gitignore
에 포함된 파일이나 추적되지 않은 파일까지 임시 저장하려면 --all
옵션이 필요하다.
-
--include-untracked
or -u
옵션
추적 중이지 않은 파일을 같이 저장한다.
기본적으로 git stash
는 추적 중인 파일만 저장한다.
-
--patch
옵션
수정된 모든 사항을 저장하지 않는다. 대신 대화형 프롬프트가 뜨며 변경된 데이터 중 저장할 것과 저장하지 않을 것을 지정할 수 있다.
$ git stash --patch
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
return `#{git_cmd} 2>&1`.chomp
end
end
+
+ def show(treeish = 'master')
+ command("git show #{treeish}")
+ end
end
test
Stash this hunk [y,n,q,a,d,/,e,?]? y
Saved working directory and index state WIP on master: 1b65b17 added the index file
특정 메시지를 추가해 저장
저장된 stash 목록에 설명이 추가되어 구분하기 쉽다.
git stash save "작업 내용 설명 메시지"
저장된 stash 목록 확인
각 stash에 대한 식별 정보가 출력된다.
stash 복원
가장 최근 stash를 워킹 디렉터리에 적용한다.
--index
옵션
Staged 상태까지 적용한다.
기본적으로 stash를 적용할 때 Staged 상태였던 파일을 자동으로 다시 Staged 상태로 만들어 주지 않는다. 따라서 해당 옵션을 사용해야 원래 작업하던 상태로 돌아올 수 있다.
특정 stash 적용
stash 목록에서 특정 인덱스(stash@{번호}
)에 해당하는 항목을 적용한다.
git stash apply stash@{2}
stash 삭제
특정 stash 항목을 삭제한다.
apply
옵션은 단순히 stash를 적용하는 것뿐이다. stash는 여전히 목록에 남아 있다. git stash drop
명령을 사용하여 해당 stash를 제거한다.
stash 복원 후 제거
stash를 적용한 후 해당 stash를 목록에서 제거한다.
모든 stash 삭제
모든 stash 항목을 삭제한다.
🧹 Git clean
git clean
은 Git 저장소에서 추적되지 않은 파일(Untracked files)을 삭제하는 명령어다. 추적되지 않은 파일이란 Git이 관리하지 않는 파일(예: 새로 생성한 파일 또는 빌드 결과물)을 의미한다.
언제 사용할까?
- 빌드 파일 제거: 빌드 과정에서 생성된 파일을 깔끔하게 제거할 때 사용한다.
- 불필요한 파일 정리: 프로젝트에 더 이상 필요하지 않은 임시 파일이나 테스트 파일을 제거할 때 유용하다.
- 코드 환경 초기화: 작업 환경을 깔끔하게 초기화하여 불필요한 파일이 문제를 일으키지 않도록 할 때 사용한다.
사용하는 방법
추적되지 않은 파일 삭제
untracked 파일을 삭제한다.
-f(force)
옵션은 파일 삭제를 강제하는 옵션이다.
기본적으로 git clean
은 -f
옵션 없이 동작하지 않기 때문에 실제로 사용하는 경우가 없다. Git은 실수로 파일을 삭제하는 것을 방지하기 위해 이 명령어를 반드시 강제 옵션과 함께 사용하도록 설계했다.
디렉터리까지 삭제
디렉터리와 파일 모두 삭제한다. 해당 명령 옵션은 하위 디렉토리까지 모두 지워버린다.
-d (directory)
옵션은 디렉터리 삭제를 허용한다. 이 옵션이 없으면 파일만 삭제되며 디렉터리는 남는다.
삭제 대상 확인
삭제될 파일 목록만 미리 확인할 수 있다.
-n(dry run)
옵션은 실제로 파일을 삭제하지 않고 어떤 파일이 삭제될지 미리 보여준다.
강제 삭제
.gitignore
에 포함된 파일과 디렉터리도 삭제한다.
-x(exclude .gitignore)
옵션은 .gitignore
에 포함된 파일도 강제로 삭제한다.
특정 경로만 삭제
지정된 경로(예: build
폴더)만 정리한다.
🍽️ stash와 clean 비교 정리
구분 |
git stash |
git clean |
목적 |
변경 사항 임시 저장 |
추적되지 않은 파일 삭제 |
작업 대상 |
Git이 추적하는 변경 파일 |
Git이 추적하지 않는 파일 |
명령어 |
git stash , git stash pop 등 |
git clean -f , git clean -df 등 |
사용 상황 |
브랜치 이동 또는 코드 백업 |
불필요한 파일 제거 및 환경 초기화 |
영향 |
변경 사항이 보존됨 |
삭제된 파일은 복구 불가 |
📝 정리
- stash는 변경 사항을 보존하며 나중에 복원.
- clean은 추적되지 않은 파일을 깔끔하게 삭제하여 작업 환경을 정리하는데 적합.
📚 참고
코딩 테스트를 준비하기 위해서 코테 문제 풀이 인증 스터디에 참가하게 되었다. 여기서 백준허브
라는 크롬 확장자를 새롭게 알게 되었다.
예전부터 사용하던 깃허브 코딩테스트 레포지토리에 해당 확장자를 연결하여 사용하는데.. 세상에? 디렉토리 구조랑 파일명, 그리고 리드미가 생성… 되고 있다! 기존에 사용해오던 구조와 너무나도 달랐기 때문에 나는 바로 커스텀을 할 수 있는 방법을 찾게 되었다.
그리고 해당 글은 나 같이 커스텀하고 싶은 사람들을 위해 글을 적어둔다.
🎨 백준허브 크롬 확장 프로그램 커스텀
🚨 커스텀 방법을 알아보기에 앞서 주의할 점
백준허브 코드를 살펴보면 각 문제풀이 사이트(프로그래머스, 백준 등)의 폴더마다 파일명을 동일하지만 내부 함수명이 좀 다르다. (구조도 조금 다른 것 같기도)
따라서, 프로그래머스가 아닌 경우에는 다음 내용을 참고만 하는 것이 좋다. 실제로 나의 경우에도 백준 사이트만 커스텀한 분의 글을 그대로 따라했다가.. 버그가 발생했다.. 이건 아래 글 참고
그리고 꼭! 아래 글을 끝까지 정독하고 참고하기를 추천한다. 아니면.. 버그를 만날 수 있다.. 그러니 진짜 꼭 끝까지 글을 읽고 참고해야 한다!
그러면 이제 어떻게 백준허브를 커스텀 할 수 있는지 차근차근 살펴보자.
백준허브 깃허브 레포지토리를 fork
아래 링크가 백준허브 깃허브 레포지토리이다. 해당 링크로 접속하여 자신의 깃허브에 fork하면 된다.
🔗 GitHub - BaekjoonHub/BaekjoonHub: 백준 자동 푸시 익스텐션(Auto Git Push for BOJ)
로컬에 해당 fork한 레포를 clone하고 열어 주기
원하는 문제풀이 사이트 폴더의 parsing.js로 이동
나의 경우에는 프로그래머스에서 문제를 대부분 풀이하기 때문에, programmers 폴더의 parsing.js를 수정해줬다.
커스텀하고 싶은 내용 수정 | makeData
함수
이동한 파일의 makeData
함수를 살펴보면 쉽게 커스텀을 진행할 수 있다. 아래는 친절하게 주석으로 작성해두신 내용이다.
- directory : 레포에 기록될 폴더명
- message : 커밋 메시지
- fileName : 파일명
- readme : README.md에 작성할 내용
- code : 소스코드 내용
makeData
함수에서 변경하고 싶은 부분을 본인의 입맛에 맞게 수정하면 된다.
나의 경우에는 거의 모든 것을 바꿔줬다. directory
, message
, fileName
을 변경해줬는데, Readme.md는 같이 업로드 하지 않을 것이기 때문에 커밋 메세지에 Date(문제 풀이한 날짜 및 시각)가 포함될 수 있도록 수정해줬다.
이때, Date가 출력되는 포맷도 변경해주기 위해서 해당 폴더의 utils 폴더 내부에 있는 getDateString
의 return 값도 변경해줬다.
Readme 업로드 막기 | uploadfunctions.js의 upload 함수
나의 경우에는 Readme.md 파일을 업로드하지 않을 예정이라 아래 코드 한 줄을 주석처리 해줬다. 그리고 이 주석은.. 엄청난 파장을 불러오는데..😱
const readme = await git.createBlob(readmeText, ${directory}/README.md); // readme 파일
확장 프로그램 이름 변경 | manifest.json의 name
커스텀한 내용의 확장 프로그램으로 새롭게 크롬에 설치를 해줘야, 커스텀한 내용이 적용된다. 그렇기 때문에 확장 프로그램 이름을 변경해주지 않으면 이게 커스텀한 건지… 안한건지.. 헷갈릴 수 있다. 내가 그랬다
그래서 아래와 같이 manifest.json 파일에서 name
부분을 원하는 이름으로 변경해주면 된다. 나는 뒤에 Custom만 추가로 넣어줬다.
"name": "백준허브(BaekjoonHub) Custom",
🐉 커스텀한 확장 프로그램 크롬에 적용하기
자, 이제 커스텀을 끝냈다. 그러면 이 커스텀한 코드를 어떻게 우리가 크롬에 사용할 수 있는지를 알아보자.
확장 프로그램 관리 페이지에 접속
본인의 크롬 브라우저에 다음과 같이 url을 넣어주면 확장 프로그램 관리 페이지에 바로 접근할 수 있다.
chrome://extensions/
개발자 모드 토글을 클릭

압축해제된 확장 프로그램을 로드합니다.
클릭
위 이미지를 참고하여 넘버링된 순서에 맞게 클릭해주면 된다.
clone했던 폴더 업로드
위에서 수정을 진행해줬던 폴더를 업로드 한다. 그럼 끝이다.

커스텀 폴더가 잘 적용되었는지 확인하기
아래 이미지와 같이 이미지 하단에 빨간 아이콘이 존재하고 변경했던 확장 프로그램 이름이 잘 반영되어있다면? 커스텀했던 백준허브가 우리의 크롬 확장 프로그램으로 잘 들어온 것이다.

이후에는 기존에 백준허브를 사용했던 것과 동일하게 원하는 레포지토리를 생성 혹은 연결해주면 끝이다. 사용 방법은 기존 백준허브와 완전히 동일하다.
그럼 어디 커스텀 백준허브가 잘 작동되었는지 확인해보자!
.
.
.
🚨 업로드 되지 않는 버그 발생
… 그렇다. 버그가 발생했다… 간단하게 뚝딱 될 줄 알았는데.. 어… 역시 뭘 하던 버그는 마주칠 수 밖에 없는 문제인 것 같다… 하하..
위에 내가 설명한 대로 수정을 진행하면 아래와 같이 설정한 레포지토리에 정상적으로 커밋되지 않는 문제가 발생한다.

원인을 찾기 위해서 수정했던 파일 중에 문제를 일으킬 만한 것을 살펴보았지만 아무리 봐도 없었다.. 그러던 중…
문제 원인 발견
서얼마.. readme
를 주석처리 해서.. 이게 혹시 원인..?이란 생각이 들어서 주석을 해제하고 시도하니.. 다음과 같이 정상적으로 커밋이 되었다.


♻️ Readme 파일을 업로드하지 않도록 하기 (Retry)
일단 위에서 설명했던 5번의 코드는 주석을 진행해도 된다. 다만, 같이 해줘야 하는 작업이 있다.
treeSHA
상수 수정
아래와 같이 treeSHA
의 상수 값을 수정해줘야 한다.
readme
값을 주석처리 했기 때문에 현재 readme
라는 상수는 존재하지 않게 되었다. 그런데 treeSHA
에서 readme
를 사용하려고 하고 있다. 어라라? 존재하지 않는 값을 사용한다..? 역시.. 말이 안되는 행위이다..
따라서 treeSHA에서 readme 값을 제거해준다. 그러면 사라진 readme 라는 상수는 사용되지 않아 여기서 문제가 발생하지 않을 것이다.
// 변경전
const treeSHA = await git.createTree(refSHA, [source, readme]);
// 변경후
const treeSHA = await git.createTree(refSHA, [source]);
const commitSHA = await git.createCommit(commitMessage, treeSHA, refSHA);
await git.updateHead(ref, commitSHA);
+) 추가로 source
값 하나만 존재하니까 배열로 안 감싸도 되지 않을까? 라는 생각을 한 나는.. [source]
가 아닌 source
로 작성했는데 이러면 안된다.. 확장 프로그램에서는 커밋되었다는 초록색 check 표시가 뜨는데, 페이크이다.. 실제 레포지토리에 가면 커밋되어있지 않다.
updateObjectDataFromPath 주석
여기도 역시 readme
라는 상수는 사라졌는데 사용하고 있으니 동일하게 주석처리를 해주면 된다!
/* stats의 값을 갱신합니다. */
updateObjectDatafromPath(stats.submission, `${hook}/${source.path}`, source.sha);
// updateObjectDatafromPath(stats.submission, `${hook}/${readme.path}`, readme.sha);
✌🏻 결과
원하는 디렉토리 형태와 파일명, 그리고 Readme.md 없이 잘 커밋되어있는 것을 확인할 수 있다.

실제 커스텀한 백준허브 깃허브 리포지토리
혹시 어떻게 코드를 변경했는지 참고하고 싶다면 아래 링크에 접속하여 참고하면 된다.
참고로 나의 경우에는 기존의 백준허브 코드를 지우지 않고 주석처리 해뒀다. 그래서 다소.. 코드가.. 좀.. 더러울 것이다.
🔗 GitHub - soi-ha/BaekjoonHubCustom: 백준 자동 푸시 익스텐션(Auto Git Push for BOJ) 커스텀 repo
✍🏻 생각 끄적이기
사실 readme 상수를 주석처리 하면서도 음.. 아래에서 사용하고 있는데 이거 문제 생길 것 같은데.. 근데 참고한 글은 냅다 주석해도 되었다는 걸 보면 괜찮겠지? 라는 생각을 가지고 그냥 냅다 따라했다. 그랬다가 나의 1시간을 순삭당했는데..
이번에 정말 온 몸으로 깨달은 것은 무작정 글을 믿고 따라하면 안된다는 것이다. 사람은 사고를 하는 동물 아닌가? 그러니 해당 글은 참고만 하고 생각을 하면서 개발을 해야 하는 것을 잊지 말아야겠다. 이래서.. 생각 없이 무작정 사용하는 것을 지양하고 무언가를 할 때 꼭 어떤 이유에서 이렇게 코드를 작성했는지 생각하라는게.. 이래서구나~ 라는 걸 깨닫게 되는 과정이었다.
그리고 해당 코드가 타입스크립트가 아니라 자바스크립트라.. 이게 생각보다 불편하더라. 해당 값의 형태가.. string인지.. 꼭 object 형태여야 하는 건지.. 이런 것을 알 수가 없었다. 타입추론이 안되니 굉장히 불편하구나를 1년동안 ts만 쓰면서 처음으로 깨달았다.
📚 참고 문서