누가 변경했는가은(는) 무엇을 하나요?
"누가, 어떤 티켓으로, 언제 이것을 만들었는지" 터미널을 떠나지 않고 답합니다. 필요한 모든 것은 이미 로컬 git 기록에 있습니다. 코드 호스트가 제공하는 것은 PR 설명뿐이며, 출력 결과는 그 PR에 대한 링크를 제공합니다.
이것은 수동 루프를 대체합니다: 파일을 열고, 기능을 찾고, 블레임 뷰를 열고, 해시를 복사하고, 코드 호스트에 붙여넣고, PR로 클릭하여 이동하고, 티켓과 작성자를 읽는 과정을 한 번에 해결합니다.
사용자가 얻는 것
작성자 이름, 커밋 해시, 티켓 번호, 브랜치 이름, 날짜, PR 번호, 클릭 가능한 커밋 및 PR 링크. 범위에 대한 주요 판결 하나, 그다음 해당 범위를 건드린 모든 커밋의 테이블입니다.
스크립트 실행
bash ~/.claude/skills/who-changed/scripts/who-changed.sh <파일> [시작 끝] [raw]
스킬이 프로젝트별로 설치된 경우 경로는 .claude/skills/who-changed/scripts/who-changed.sh입니다.
<파일>단독: 전체 파일 모드. 상위 기여자와 마지막 12개 커밋.<파일> <시작> <끝>(또는 한 줄 번호): 라인 범위 모드. 커밋별 라인 소유권.raw추가:-w -C -M없이 블레임. 사용자가 바이트 수준의 진실을 원할 때 사용합니다.
환경 변수 재정의: WHO_CHANGED_BASE는 PR 조회가 향하는 ref를 설정합니다 (기본값: origin/HEAD, 그다음 origin/main, origin/master, main, master); WHO_CHANGED_TICKET_REGEX는 티켓 패턴을 변경합니다 (기본값: [A-Z][A-Z0-9]+-[0-9]+).
스크립트는 탭으로 구분된 보고서를 출력하며, 저장소나 네트워크에 쓰지 않습니다. 사용자를 위해 재구성하세요 - 원시 TSV를 그대로 붙여넣지 마세요.
1단계: 대상을 라인 범위로 확인
스크립트는 숫자를 받으므로 함수 이름을 먼저 범위로 변환해야 합니다. Git의 -L :함수이름: 단축키는 언어별 함수 패턴에 의존하며, 많은 언어(특히 TypeScript 클래스 멤버)에서 no match로 실패합니다. 직접 범위를 확인하세요:
- 선언부를 grep으로 찾으세요. 예를 들어
^\s*(public|private|protected)?\s*(async\s+)?<이름>\s*[(<], 그리고 화살표 함수 속성의 경우<이름>\s*[:=]\s*(function|\(). HTML이나 CSS의 경우 리터럴 선택자, 키 또는 블록을 grep하세요. - 해당 줄부터 시작하여 첫 번째
{에서 괄호 짝을 맞춰 닫는 줄을 찾습니다. - 이름이 두 군데 이상 일치하면 일치 항목을 나열하고 어느 것인지 물어보세요. 절대 첫 번째를 조용히 선택하지 마세요 - 흔한 이름은 여러 파일에 존재하는 경우가 많습니다.
줄 번호는 작업 트리에서 가져오므로 사용자가 편집기에서 보는 것과 일치합니다.
명시적 범위(285-300, 285, L285-L300)가 주어지면 그대로 사용합니다. 파일 경로만 주어지면 전체 파일 모드를 사용합니다.
2단계: 출력 읽기
## meta 블록은 base(PR 조회가 향하는 ref), base_tip_date, web, host(링크 베이스, git remote에서 파생), 블레임 모드, 파일에 커밋되지 않은 변경 사항이 있는지 여부를 전달합니다.
## commits의 행은 소유한 줄 수로 정렬됩니다. 판결은 첫 번째 행입니다 - 해당 범위의 대부분을 소유한 커밋입니다. 범위가 진정으로 공유된 경우(3년에 걸쳐 4명이 만든 함수는 한 사람의 소유로 보고되어서는 안 됨) 차점자를 언급하세요.
스크립트는 Bitbucket, GitHub 및 GitLab 리모트에 대해 commit_url과 pr_url을 이미 빌드합니다. -인 경우 인식 가능한 리모트가 없음을 의미합니다 - 그렇게 말하고 링크는 생략하고 호스트를 임의로 만들지 마세요.
다음과 같이 제시하세요 (아래 이름은 가상입니다):
### calculateTotal - src/cart/cart.service.ts:42-61
**Alice Moreno** · `a1b2c3d` · 2023-02-08 · 20줄 중 13줄 소유
티켓 SHOP-88 · 브랜치 `SHOP-88-cart-totals` · PR #41
| 줄 | 작성자 | 커밋 | 날짜 | 티켓 | 브랜치 | PR |
|---|---|---|---|---|---|---|
| 13 | Alice Moreno | a1b2c3d | 2023-02-08 | SHOP-88 | SHOP-88-cart-totals | #41 |
| 6 | Ben Carter | 9f8e7d6 | 2026-03-11 | SHOP-310 | SHOP-310-rounding-fix | 병합되지 않음 |
| 1 | Chen Wei | 4c5d6e7 | 2024-06-20 | — | release-2024-06 | #112 |
그런 다음 링크, 신뢰도 플래그가 발생한 경우 주의 사항 줄을 추가합니다.
신뢰도 레이블 및 각각에 대해 말할 내용
| 레이블 | 의미 | 사용자에게 알리기 |
|---|---|---|
ok | 티켓 브랜치가 PR을 통해 병합됨 | 추가 필요 없음 |
local-only | 커밋이 아직 base에 없음 | "아직 기본 브랜치에 병합되지 않음" - PR이 없음 |
no-pr-merge | 메인라인에 있지만, 이를 전달하는 PR 병합이 없음 | 메인라인에 직접 커밋되었거나, 병합 제목이 규칙을 위반함 |
squash | (#123) 접미사에서 PR 번호를 읽음 | 스쿼시 병합됨, 따라서 해당 PR 내의 줄별 기록이 평탄화됨 |
low-no-ticket | 메시지나 브랜치에 티켓 없음 | 티켓이 기록되지 않았다고 명시하세요. 추측하지 마세요 |
low-integration-branch | 브랜치 이름에 티켓이 없는 브랜치를 통해 병합됨 | PR은 통합 브랜치가 도착한 곳이며, 코드가 작성된 곳이 아님. 신뢰도 낮음 |
uncommitted | 아직 커밋되지 않음 | 로컬 미커밋 편집; 판결에서 제외됨 |
오래된 코드는 종종 장기 통합 브랜치(develop, release-*, ui-dev)를 통해 도착합니다. 이러한 경우 정직한 답변은 "PR #112를 통해 메인라인에 병합되었지만, 해당 PR은 통합 브랜치입니다 - 이것이 코드가 작성된 PR이 아닙니다."
-w -C -M이 기본값인 이유
-w는 공백을 무시하고, -M은 파일 내에서 이동된 줄을 추적하며, -C는 다른 파일에서 복사된 줄을 추적합니다. 이는 단순한 장식이 아닙니다. 2023년에 작성되고 2026년에 다시 들여쓰기된 블록을 예로 들어보겠습니다 (예시 숫자):
- 스마트 (
-w -C -M): 9줄 중 9줄이 원저자(2023)에게 감 - 정확함 - 원시: 9줄 중 8줄이 다시 들여쓰기한 사람(2026)에게 감 - 오래된 코드를 인정함
따라서 명명된 작성자는 바이트를 마지막으로 건드린 사람이 아니라 동작을 마지막으로 변경한 사람입니다. 중요할 때 그렇게 말하고 raw를 후속 조치로 제안하세요.
정직성 규칙
- 절대 티켓 ID를 만들지 마세요. 커밋 메시지나 브랜치에 티켓이 없으면
—를 보고하고 티켓이 기록되지 않았다고 말하세요. - 절대 작성자를 만들지 마세요. 블레임이 결정적이지 않으면 어떤 커밋이 후보인지 말하세요.
- "~을 통해 병합됨"과 "~에서 작성됨"을 구별하세요. 낮은 신뢰도 플래그가 발생할 때마다.
base_tip_date가 오래된 것으로 보이면 기본 ref가 최근에 가져오지 않았으며 최근 병합이 누락될 수 있다고 언급하세요.git fetch origin을 제안하세요 - 요청 없이 실행하지 마세요.
관련될 때 언급할 가치가 있는 한계
- "이유"는 git에 없습니다. PR 설명, 리뷰어 및 티켓 제목은 코드 호스트 또는 트래커의 API와 자격 증명이 필요합니다. PR 링크가 연결 지점입니다.
- 스쿼시 병합은 기록을 평탄화합니다. PR이 스쿼시된 경우 하나의 커밋이 전체 기능을 소유하며 그 안의 줄별 속성은 사라집니다.
--ancestry-path는 커밋을 메인라인으로 가져온 첫 번째 병합을 선택합니다. 중첩된 기능 브랜치의 경우 중간 병합일 수 있으며 티켓 자체의 PR이 아닐 수 있습니다.- 전체 파일 기여자는 이름 변경을 추적하지 않습니다.
git shortlog에는--follow가 없습니다. 커밋 목록은 이름 변경을 추적하지만 기여자 집계는 추적하지 않습니다. -C는 때때로 복사-붙여넣기된 코드를 원본 파일에 귀속시켜 놀라게 할 수 있습니다.- PR 감지는 세 가지 병합 메시지 스타일을 인식합니다: Bitbucket (
Merged in <브랜치> (pull request #N)), GitHub (Merge pull request #N from ...), 스쿼시 (... (#N)). 다른 스타일은no-pr-merge로 처리됩니다.
스크립트를 사용할 수 없는 경우의 대체 방법
git blame -w -C -M -L <시작>,<끝> --date=short -- <파일>
git log -1 --date=short --pretty=format:"%H%n%an%n%ad%n%s%n%b" <sha>
git merge-base --is-ancestor <sha> origin/main # 조상이 아님 -> 병합되지 않음
git log --merges --ancestry-path --reverse --pretty=format:"%h|%s" <sha>..origin/main | grep -m1 "pull request"
git branch --contains <sha> # 병합되지 않은 경우
병합 제목을 파싱하여 브랜치와 PR 번호를 얻습니다. 두 가지 주의사항: git shortlog는 명시적인 HEAD가 필요하며, 그렇지 않으면 조용히 stdin을 읽고 아무것도 반환하지 않으며, 브랜치 이름에 접두사가 붙을 수 있으므로(feature/SHOP-88-...) 티켓 정규식을 문자열 시작에 고정하지 마세요.