git log, unfolds code into knowledge.
커밋 로그라는 시간의 주름을 따라 접힌 코드가 펼쳐진다 — 읽고, 비교하고, 이해하는 행위 끝에 지식이 남는다.
바이너리 이름
glc는 손가락이 기억하는 명령어: home row에서 벗어나지 않고g l c. gluck은 그 뒤에 unfolding과 knowing을 더한, 프로젝트의 온전한 이름이다.
Git history file viewer — 터미널에서 git history의 파일을 탐색하고 읽는 TUI 도구.
홈페이지(키 치트시트 포함): https://soomtong.github.io/gluck/
cargo install --git https://github.com/soomtong/gluckIntel Mac(x86_64)용 바이너리는 GitHub Actions 지원 중단으로 제공하지 않습니다. Intel Mac 사용자는 아래 Cargo 설치 방법을 이용해 주세요.
brew tap soomtong/tap
brew install glcLinux용 바이너리는 별도로 제공되지 않습니다. Rust 툴체인이 설치되어 있다면 직접 빌드할 수 있습니다.
cargo install --git https://github.com/soomtong/gluck또는 소스를 받아 빌드:
git clone https://github.com/soomtong/gluck
cd gluck
cargo build --release
# 빌드 결과: ./target/release/glcglc # 현재 디렉토리의 git history 열기
glc /path/to/repo # 특정 저장소 열기
glc index # 시맨틱 검색 인덱스 빌드
glc index --force # 인덱스 강제 재빌드
glc ignore # 기본 제외 목록으로 .glcignore 생성 (--force: 덮어쓰기)| 키 | 동작 |
|---|---|
j / k / ↑ / ↓ |
커밋 이동 |
^N / ^P |
이전(older)/다음(newer) 커밋 이동 |
Enter / l |
선택 커밋 View 모드 |
Tab |
선택 커밋 Diff 모드 |
/ |
커밋 검색 |
s |
시맨틱 검색 모달 열기 (glc index 선행 필요) |
^T |
색상 테마 전환 |
q |
종료 |
| 키 | 동작 |
|---|---|
j / k / ↑ / ↓ |
파일 트리 이동 |
u / d |
내용 스크롤 (3줄) |
J / K |
내용 페이지 스크롤 |
H / L / ← / → |
이전/다음 변경 파일(* 표시)로 이동 |
. |
.gitignore 파일 필터 토글 |
w |
긴 줄 word wrap 토글 (기본 on) |
Tab |
Diff 모드 전환 |
Esc / h |
Pick 모드 |
^N / ^P |
이전(older)/다음(newer) 커밋 이동 |
s |
시맨틱 검색 모달 열기 |
a / A |
화면(또는 선택 영역)을 LLM에게 설명 요청 / 자세히 |
| 키 | 동작 |
|---|---|
h / l / ← / → |
변경 파일 이동 |
j / k |
diff 내용 스크롤 (1줄, Ask 패널이 열려 있으면 답변 스크롤) |
u / d |
diff 내용 스크롤 (3줄) |
J / K / ↑ / ↓ |
diff 내용 페이지 스크롤 |
v |
side-by-side / unified 토글 |
w |
긴 줄 word wrap 토글 (기본 on) |
Tab |
View 모드 |
Esc |
Pick 모드 |
^N / ^P |
이전(older)/다음(newer) 커밋 쌍 이동 |
s |
시맨틱 검색 모달 열기 |
a / A |
화면(또는 선택 영역)을 LLM에게 설명 요청 / 자세히 |
s 키를 누르면 커밋 메시지와 파일/심볼을 동시에 검색하는 모달이 열립니다.
- 모달 내에서
ctrl+n/p로 결과 탐색,Enter로 선택 - Enter: 선택한 결과가 커밋이면 Pick 모드, 파일·심볼이면 View 모드로 자동 전환
- Esc: 모달 닫기
- 인덱스가 없으면
I키로 빌드 가능: force rebuild index
사용 전 glc index로 인덱스를 먼저 빌드할 수 있습니다.
처음 보는 언어, 낯선 이디엄, 변경 의도를 TUI 안에서 바로 물어봅니다. View/Diff 모드에서 동작합니다.
a: 지금 화면에 보이는 diff/코드를 설명 (요약 · 변경 의도 · 낯선 구문/이디엄 · 리뷰 포인트)- 마우스로 줄을 드래그해 선택한 뒤
a: 선택 블록을 구문 단위로 설명 (무엇을 하나 · 구문 해설 · 왜 이렇게 썼나 · 관련 개념). side-by-side에서는 드래그한 pane(old/new)의 코드만 보냅니다. A: 같은 요청을 더 자세한 수준(deep)으로- 답변은 하단 패널에 스트리밍됩니다.
[/]로 페이지 스크롤, 휠로 3줄 스크롤 (Diff 모드에서는j/k가 평소 diff 1줄 스크롤이고, 패널이 열려 있는 동안은 답변 1줄 스크롤),r재요청,Esc닫기 (선택이 있으면Esc가 먼저 선택을 해제) - 같은 화면/선택/수준으로 다시 물으면 최근 답변 16개를 담는 메모리 캐시에서 바로 보여 줍니다 (패널에
cached표시, 추가 과금 없음).r은 캐시를 무시하고 새로 요청합니다.
OpenAI 호환 Chat Completions API를 사용합니다 (OpenRouter, DeepSeek, Gemini, Groq, Cerebras, Mistral, OpenAI 프리셋, 로컬 Ollama 등). 설정 파일 [ask]의 값이 우선이고, 비어 있는 항목은 프리셋 기본값과 환경변수(OPENROUTER_API_KEY, DEEPSEEK_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, MISTRAL_API_KEY, OPENAI_API_KEY)로 채웁니다. provider를 비워 두면 이 순서대로 키가 있는 첫 프로바이더를 고릅니다. 요청할 때만 화면 내용이 전송되며, 패널 제목에 provider/model과 키 출처(config/env)가 표시됩니다.
프롬프트를 모델별로 비교하려면 cargo run --example ask_probe -- [파일명 일부] [--deep] [--dry]로 HEAD diff에 대한 프롬프트와 답변을 확인할 수 있습니다.
하이브리드 검색(hybrid search)으로 BM25 키워드 검색과 벡터 의미 검색을 결합합니다.
쿼리 입력
├── BM25 (Tantivy ngram_2_2 + LowerCaser) ──→ 키워드 매칭 결과
├── Embedding (potion-multilingual-128M) ──→ 벡터 검색 결과
└── RRF (Reciprocal Rank Fusion) ──→ 점수 융합 → 최종 결과
- BM25 검색: Tantivy의 character bigram 토크나이저(
ngram_2_2)로 한국어·영문 키워드를 동시에 매칭합니다.LowerCaser필터로 영문 대소문자가 정규화되어ParseConfig를parseconfig로도 찾을 수 있습니다.- 한국어: pg_bigm 스타일 character bigram. 형태소 사전 없이 작동하며
"에러 처리"↔"에러처리"처럼 띄어쓰기 변형에도 부분 매칭됩니다. - 필드 한정 쿼리:
path:"src/search/error.rs"같이 경로 정확 매칭을 지원합니다. - Symbol 검색: 심볼은
path::symbol_name형태로 인덱싱되어"error.rs handle"처럼 파일 범위와 함수명을 한 쿼리로 좁힐 수 있습니다.
- 한국어: pg_bigm 스타일 character bigram. 형태소 사전 없이 작동하며
- 벡터 검색: 쿼리를 256차원 임베딩으로 변환한 뒤 turbovec 4-bit 양자화 인덱스에서 유사도를 계산합니다.
- RRF 융합: 두 검색 결과를 Reciprocal Rank Fusion(k=60)으로 병합하여 최종 순위를 결정합니다.
- 오타 교정: 영문 쿼리 단어가 인덱스 어휘에 없으면 한 글자 누락·인접 글자 뒤바뀜·같은 글자 중복 같은 타이핑 실수로 보고 가장 가까운 단어를 BM25 쿼리에 덧붙입니다 (
is_binray_blob→binary). 철자가 맞는 단어는 건드리지 않습니다. - 정답 없음 표시: 쿼리 단어가 파일·심볼 이름과 경로에 하나도 맞지 않고 벡터 유사도도 고르게 낮으면, 결과는 그대로 보여 주되 제목에
· no strong match를 붙입니다. 이 저장소와 무관한 질문(예:django orm migrations)을 알아보기 위한 표시입니다.
glc index는 커밋 메시지와 HEAD의 파일을 대상으로 인덱스를 빌드합니다.
- 커밋 메시지: 제목과 본문을 인덱싱. 임베딩에는 그 커밋이 바꾼 파일 경로(최대 20개)를 덧붙여 "어느 파일을 고친 커밋인지"도 의미 검색에 반영
- 파일: 16KB 미만은 전체 파일(
WholeFile), 그보다 크면 tree-sitter로 심볼(함수, 메서드, 타입) 단위로 분할하여 인덱싱 - 심볼 추출 지원 언어: Rust, Python, JavaScript, TypeScript, TSX, Go, Swift. 그 외 파일은 크기 기준으로만 분할
레포 루트에 .glcignore를 두면 해당 파일을 시맨틱 검색 인덱스에서 뺍니다. 문법은 .gitignore와 같습니다 (디렉터리 패턴, */** glob, ! 부정, # 주석).
glc ignore를 실행하면 기본 제외 목록이 담긴 .glcignore가 레포 루트에 생성됩니다. 인덱스는 HEAD에 커밋된 파일만 대상으로 하므로, 기본 목록은 커밋되어 있지만 검색에는 잡음인 것들입니다.
- vendored 코드:
vendor/,third_party/,external/ - lockfile:
Cargo.lock,package-lock.json,yarn.lock,pnpm-lock.yaml,go.sum,poetry.lock,uv.lock등 - 빌드 산출물과 minified 파일:
dist/,build/,out/,*.min.js,*.min.css,*.map
이미 파일이 있으면 덮어쓰지 않습니다 (glc ignore --force로 덮어쓰기). 생성 후 프로젝트에 맞게 고치면 됩니다. 직접 작성한 예:
# 생성물이나 검색에 방해되는 문서
vendor/
docs/reports/
*.lock
!Cargo.lock
- HEAD의 파일 내용(파일·심볼 문서)에만 적용되고, 커밋 메시지는 항상 인덱싱됩니다.
- 커밋하지 않은 작업 트리의
.glcignore를 읽습니다. - 내용이 바뀌면 다음
glc index(또는 모달의I)에서 자동으로 전체 재빌드합니다.
.glc-index/
├── meta.toml # 버전, HEAD OID, 토크나이저 식별자, 도큐먼트 수, 인덱싱 시각, .glcignore 해시
├── bm25/ # Tantivy BM25 인덱스 (ngram_2_2 + LowerCaser, 구조화 필드)
└── vectors/ # turbovec 4-bit 양자화 벡터 인덱스
인덱스 스키마 또는 토크나이저가 변경되면 meta.toml의 식별자가 코드와 어긋나서 glc가 자동으로 재인덱싱을 안내합니다. glc index --force로 재구축하세요.
glc index 최초 실행 시 임베딩 모델(minishlab/potion-multilingual-128M, 약 500MB)을 자동으로 다운로드합니다.
모델은 HuggingFace Hub 기본 캐시에 저장됩니다:
- macOS / Linux:
~/.cache/huggingface/hub/
같은 머신에서 다른 레포를 인덱싱할 때는 캐시에서 즉시 로드되므로 재다운로드가 없습니다.
.glc-index/는 .gitignore에 추가하는 것을 권장합니다:
echo '.glc-index/' >> .gitignore
설정 파일은 XDG config 경로에 저장됩니다.
- macOS:
~/Library/Application Support/gluck/config.toml - Linux:
~/.config/gluck/config.toml
[theme]
# 색상 테마: plain (기본), catppuccin, tokyo-night, nord, gruvbox, one-light
name = "plain"
[ui]
# u/d 키 스크롤 줄 수 (기본: 3)
scroll_lines = 3
# View/Diff 긴 줄 word wrap (기본: true, `w` 키로 토글 시 저장됨)
word_wrap = true
[ask]
# 활성 프로바이더. 비우면 환경변수 키가 있는 첫 프리셋 자동 선택
provider = "deepseek"
# 답변 언어 (Korean이면 섹션 제목도 한국어)
language = "Korean"
# brief | normal | deep (A 키는 항상 deep)
detail = "normal"
# 0 = detail 기본값 (500 / 1200 / 2500)
max_tokens = 0
# 음수면 temperature 필드를 보내지 않음 (거부하는 모델용)
temperature = 0.2
# 선택 영역과 함께 보내는 앞뒤 줄 수
extra_context_lines = 20
max_context_chars = 12000
# 비우면 내장 system 프롬프트 ({language} 치환)
system_prompt = ""
[ask.providers.deepseek]
# 생략 시 DEEPSEEK_API_KEY. base_url / model 생략 시 프리셋 기본값
api_key = "sk-..."
[ask.providers.local]
base_url = "http://localhost:11434/v1"
model = "qwen2.5-coder:7b"설정 파일에 API 키를 저장하면 저장 시 파일 권한을 0600으로 맞추고, 다른 사용자가 읽을 수 있는 상태면 경고 로그를 남깁니다.
오픈소스 프로젝트를 공부할 때 가장 좋은 방법 중 하나는 git history를 따라가며 코드가 어떻게 변해왔는지 읽는 것이다. 하지만 기존 도구들은 각각 한계가 있다:
- git log / git show — 변경사항은 볼 수 있지만 해당 시점의 전체 파일을 읽기 불편하다
- tig — log 탐색에 훌륭하지만 파일 뷰어로는 부족하다
- GitHub/GitLab — 브라우저가 필요하고 커밋 간 이동이 느리다
gluck은 git history를 타임라인으로 삼아, 각 시점의 파일을 읽고 비교하는 데 집중한다.
- 커밋 리스트를 탐색하고 선택
- 파일 단위로 변경 이력 필터링
- 커밋 메시지, 작성자, 날짜 기반 검색
- 선택한 커밋 시점의 전체 파일 내용 조회
- 파일 트리 탐색 (해당 커밋 기준)
- syntax highlight 지원
- 두 커밋 간 변경사항 비교 (side-by-side / unified)
- 파일 단위 diff, 특정 함수/블록 단위 diff
- 변경된 파일만 필터링하여 보기
- tree-sitter 기반 syntax highlight, 터미널 색상 테마 지원
- 지원 언어
- 시스템: C, C++, Zig, Rust, Odin, Go, Assembly, GNU linker script, LLVM IR
- 스크립트: Python, Ruby, Lua, JavaScript, TypeScript/TSX (node, bun), Bash/Zsh, PHP, Perl
- JVM: Java, Kotlin, Scala, Clojure
- BEAM: Erlang, Elixir, Gleam
- 기타: Janet, Swift, Lisette, Markdown, JSON, YAML
- 지원하지 않는 확장자는 plain text로 표시
모든 grammar는 glc 바이너리 하나에 정적으로 링크된다. 그래서 grammar가 늘수록 바이너리가 커진다. 위 언어 목록 기준 release 빌드는 약 52MB이고, 그중 Kotlin, Scala, C++, Ruby, Odin, PHP, Elixir, LLVM IR이 약 24MB를 차지한다.
크기의 대부분은 tree-sitter parse table이다. grammar마다 parser.c 안에 거대한 상수 배열로 생성되므로, strip으로 디버그 심볼을 없애도 3MB 정도만 줄어든다.
Helix도 tree-sitter를 쓰지만 hx 바이너리(약 27MB)에는 parser가 들어 있지 않다. grammar는 runtime/grammars/*.so로 따로 빌드되고(25.07.1 기준 245개, 195MB), 필요한 언어만 실행 중에 동적 로딩한다. 같은 grammar는 Helix의 .so와 gluck에 링크된 결과물이 거의 같은 크기다(예: Kotlin 약 5.5MB). 크기를 바이너리에서 runtime 디렉터리로 옮긴 차이일 뿐이다.
gluck은 아래 이유로 정적 링크를 유지한다.
- 파일 하나로 배포된다. Homebrew tap, Windows 빌드,
cargo install에 grammar 빌드나 배포 단계가 필요 없다. - 설치 크기 전체로 보면 오히려 작다. 52MB이고, Helix는 바이너리와 runtime을 합쳐 약 220MB다.
- 동적 로딩으로 바꾸려면 grammar별
.dylib/.dll빌드, 설치 경로 관리,libloading, release workflow와 Homebrew formula 변경이 함께 필요하다.
크기가 문제가 되면 먼저 큰 grammar를 cargo feature로 분리하는 방안을 검토하고, 동적 로딩은 그다음에 별도 설계로 다룬다.
- 오픈소스 프로젝트를 처음 공부할 때 초기 커밋부터 따라가며 코드 읽기
- 특정 기능이 언제, 어떻게 추가되었는지 추적
- 버그 수정 커밋을 찾아 어떤 변경이 있었는지 확인
- 프로젝트 문서(history 내 README, docs 등)의 변천사 읽기