$ glc

gluck

git log, unfolds code into knowledge.

커밋 로그라는 시간의 주름을 따라 접힌 코드가 펼쳐진다.
읽고, 비교하고, 이해하는 행위 끝에 지식이 남는다. (남는다고 믿는다.)

바이너리 이름 glc는 손가락이 기억하는 명령어다. home row에서 벗어나지 않고 g l c. unfolding과 knowing은 타이핑하기엔 길어서 프로젝트 이름에만 남겨두었다.

그리고 s 하나 더. 커밋과 코드를 뜻으로 검색한다. 다른 git 뷰어에는 없는 키다.

왜 만들었나

오픈소스를 공부하는 가장 좋은 방법은 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) ──→ 순위 융합 ──→ 결과
.glc-index/.gitignore에 넣자. 인덱스를 커밋하면 인덱스가 인덱스를 인덱싱하는 순환 지옥이 열린다.

설치

Cargo · macOS / Linux, Rust 툴체인 있으면 어디서든
$ cargo install --git https://github.com/soomtong/gluck
Homebrew · macOS Apple Silicon 전용. Intel Mac은 위를 보시라
$ brew tap soomtong/tap && brew install glc
인덱스 · 시맨틱 검색을 쓰려면 레포에서 한 번
$ glc index
Linux 빌드에는 OpenBLAS가 필요하다 (libopenblas-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테마 순환 (설정에 저장됨)
TabView ⇄ Diff
EscPick으로 돌아가기
^D디버그 오버레이
q / ^C종료 (어느 모드에서든)

Pick

Enter / l선택 커밋을 View로
Tab선택 커밋을 Diff로
/커밋 메시지·작성자·해시 prefix 검색
^F / ^B페이지 이동

View

Enter파일 열기 / 폴더 접기·펼치기
h / l폴더 접기(→부모) / 펼치기(→첫 자식)
H / L이전 / 다음 변경 파일(*)로 점프
..gitignore 필터 토글
u / d내용 스크롤 (기본 3줄)
J / K내용 페이지 스크롤
wword wrap 토글 (기본 on)
Esc / h파일 위에서: Pick으로

Diff

j k h l 변경 파일 이동
vside-by-side ⇄ unified
u / d, J / Kdiff 스크롤 / 페이지
wword 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       # 모달에 보여줄 결과 수

영감