$ glc
gluck
git log, unfolds code into knowledge.
커밋 로그라는 시간의 주름을 따라 접힌 코드가 펼쳐진다.
읽고, 비교하고, 이해하는 행위 끝에 지식이 남는다. (남는다고 믿는다.)
바이너리 이름 glc는 손가락이 기억하는 명령어다. home row에서 벗어나지 않고 g l c.
unfolding과 knowing은 타이핑하기엔 길어서 프로젝트 이름에만 남겨두었다.
그리고 s 하나 더. 커밋과 코드를 뜻으로 검색한다. 다른 git 뷰어에는 없는 키다.
- 언어 Rust
- 라이선스 MIT
- 활성 사용자 1명 (개발자 본인)
- 만족도 100% (표본 1)
- 브라우저 필요 없음 (이 페이지 제외)
왜 만들었나
오픈소스를 공부하는 가장 좋은 방법은 git history를 따라가며 코드가 어떻게 변해왔는지 읽는 것이다.
그런데 git show는 변경만 보여주고, tig는 log에는 훌륭하지만 파일 뷰어로는 아쉽고,
GitHub은 브라우저를 열어야 한다. 커밋을 하나 넘길 때마다 로딩 스피너를 보는 삶은 그만두고 싶었다.
그래서 gluck은 git history를 타임라인으로 삼는다. 각 시점의 파일 트리를 열고, 파일을 읽고, 옆 커밋과 비교한다. 전부 터미널 안에서, 전부 키보드로. 마우스도 되긴 한다. 자랑은 아니다.
시맨틱 검색 이게 다른 점
s 한 글자로, 커밋 메시지·파일 내용·함수 이름을 뜻으로 찾는다.
tig도 gitui도 grep은 잘한다. 그런데 "인덱스 스키마 바뀌면 재빌드하던 그 커밋"을 grep으로 찾으려면 단어를 정확히 기억해야 한다. gluck은 기억이 흐릿해도 된다. 대충 쓰면 대충 맞는 걸 위에 올려준다.
| s | 어느 모드에서든 검색 모달 열기 |
| 타이핑 | 커밋 + 파일 + 심볼을 한 번에, 한국어·영어 섞어도 됨 |
| ^N / ^P | 결과 이동 |
| Enter | 커밋이면 Pick으로, 파일·심볼이면 그 커밋의 View로 바로 점프 |
| I | 인덱스가 없거나 낡았으면 모달 안에서 바로 재빌드 |
| Esc | 닫기 |
어떻게 동작하나
glc index가 HEAD의 파일을 tree-sitter로 심볼 단위로 쪼개고, 커밋 메시지와 함께
BM25 인덱스와 256차원 벡터 인덱스에 넣는다. 질의는 두 인덱스를 동시에 타고 RRF로 합쳐진다.
키워드가 정확하면 BM25가, 뜻만 비슷하면 벡터가 받쳐준다.
쿼리 ├── BM25 (tantivy, 문자 bigram + lowercase) ──→ "에러처리" ≈ "에러 처리" ├── 벡터 (potion-multilingual-128M, 256-dim) ──→ 뜻이 비슷하면 잡힘 └── RRF (k=60) ──→ 순위 융합 ──→ 결과
- 한국어는 형태소 사전 없이 character bigram으로 버틴다. 의외로 잘 버틴다.
path:"src/search/rrf.rs"처럼 경로 한정,"rrf.rs fuse"처럼 파일+함수명 좁히기 가능.- HEAD가 바뀌면 바뀐 만큼만 증분 인덱싱. 스키마가 바뀌면
meta.toml이 알아채고 재빌드를 요구한다. - 첫
glc index는 임베딩 모델(약 500MB)을 HuggingFace 캐시로 받는다. 커피 한 잔이면 충분하다. glc report가 MRR / Recall@k / NDCG@10과 지연시간 p50/p95/p99를 찍어준다. 검색이 나빠지면 숫자가 먼저 안다.
.glc-index/는 .gitignore에 넣자. 인덱스를 커밋하면 인덱스가 인덱스를 인덱싱하는 순환 지옥이 열린다.설치
$ cargo install --git https://github.com/soomtong/gluck$ brew tap soomtong/tap && brew install glc$ glc indexlibopenblas-dev). 시맨틱 검색용 임베딩이 선형대수를 먹고 산다.사용법
$ glc # 현재 디렉토리의 git history 열기 $ glc /path/to/repo # 특정 저장소 열기 $ glc index # 시맨틱 검색 인덱스 빌드 (.glc-index/) $ glc index --force # 인덱스 강제 재빌드 $ glc report # 검색 품질 + 성능 리포트 (MRR / Recall@k / NDCG / p99)
세 가지 모드
Pick ──Enter──→ View ──Tab──→ Diff ↑ ↑ │ │ └────Tab─────┘ └─── Esc/h ─────────────────┘
Pick
커밋 리스트와 인라인 diff 미리보기. 200개씩 게으르게 불러오니 커밋 수만 개짜리 레포도 첫 화면은 즉시 뜬다.
View
그 커밋 시점의 파일 트리와 내용. 폴더 접기, gitignore 필터, 신택스 하이라이트, 줄 번호까지. 그때의 README를 읽는 기분은 꽤 묘하다.
Diff
부모 커밋과의 side-by-side 비교. v로 unified 전환. 긴 줄은 word wrap이 양쪽 패널을 나란히 맞춰준다.
키 치트시트
vim 손가락이면 설명이 필요 없고, 아니면 이 표가 필요하다. 사실 이 페이지의 존재 이유가 이 표다.
공통
| j k ↑ ↓ | 이동 |
| ^N / ^P | 이전(older) / 다음(newer) 커밋 — 선택한 파일은 유지 |
| s | 시맨틱 검색 모달 |
| ^T | 테마 순환 (설정에 저장됨) |
| Tab | View ⇄ Diff |
| Esc | Pick으로 돌아가기 |
| ^D | 디버그 오버레이 |
| q / ^C | 종료 (어느 모드에서든) |
Pick
| Enter / l | 선택 커밋을 View로 |
| Tab | 선택 커밋을 Diff로 |
| / | 커밋 메시지·작성자·해시 prefix 검색 |
| ^F / ^B | 페이지 이동 |
View
| Enter | 파일 열기 / 폴더 접기·펼치기 |
| h / l | 폴더 접기(→부모) / 펼치기(→첫 자식) |
| H / L | 이전 / 다음 변경 파일(*)로 점프 |
| . | .gitignore 필터 토글 |
| u / d | 내용 스크롤 (기본 3줄) |
| J / K | 내용 페이지 스크롤 |
| w | word wrap 토글 (기본 on) |
| Esc / h | 파일 위에서: Pick으로 |
Diff
| j k h l ← → | 변경 파일 이동 |
| v | side-by-side ⇄ unified |
| u / d, J / K | diff 스크롤 / 페이지 |
| w | word wrap 토글 |
시맨틱 검색 모달
s로 연다. 나머지 키는 위 섹션에 있다. 이 페이지의 두 번째 존재 이유.
신택스 하이라이트
tree-sitter 기반. Rust, Go, C, JavaScript / TypeScript / TSX, Swift, Zig, Bash, JSON, YAML, Markdown,
그리고 남들이 굳이 안 하는 어셈블리(.s .asm), 링커 스크립트(.ld), Lisette(.lis)까지.
목록에 없는 확장자는 겸손하게 plain text로 나온다.
테마와 설정
테마는 여섯 개. ^T로 돌리면 바로 저장된다.
plain (기본) catppuccin tokyo-night nord gruvbox one-light
설정 파일은 XDG 경로. 없으면 기본값으로 뜨고, 굳이 만들어주지 않는다.
macOS~/Library/Application Support/gluck/config.toml
Linux~/.config/gluck/config.toml
[theme] name = "plain" # plain | catppuccin | tokyo-night | nord | gruvbox | one-light [ui] scroll_lines = 3 # u/d 한 번에 몇 줄 word_wrap = true # w 키로 토글하면 여기에 저장됨 [search] # index_dir = ".glc-index" # 기본: 레포 루트의 .glc-index/ result_limit = 20 # 모달에 보여줄 결과 수