스태시
Git 스태시는 작업 디렉터리와 인덱스의 변경 사항을 저장해 다른 작업으로 전환한 뒤 나중에 복원할 수 있게 합니다. Pitmaster는 git의 저장 구조에 따라 refs/stash와 reflog를 사용해 스태시를 구현합니다. Repository에는 스태시 메서드가 없습니다. 아래의 Stash 클래스를 사용하세요.
알려진 문제는 두 가지입니다. 항목이 여러 개일 때 drop()(따라서 pop()도)는 목록에서 항목을 제거하지 않습니다(#10). 또한 apply()는 스태시가 건드리지 않는 파일의 미커밋 변경 사항을 덮어씁니다(#11). 작업 트리가 깨끗한 상태에서 스태시를 적용하세요.
스태시 작동 방식
각 스태시 항목은 부모가 두 개인 커밋입니다:
- 부모 0: 스태시 시점의 HEAD 커밋(기준 상태)
- 부모 1: 인덱스 상태(스테이징된 변경 사항)를 담은 커밋
스태시 커밋 자체의 트리에는 작업 트리 상태(스테이징되지 않은 변경 사항 포함)가 담깁니다.
스태시 "스택"은 refs/stash의 reflog입니다. 가장 최근의 스태시는 refs/stash 자체입니다. 이전 항목은 reflog 항목으로 기록됩니다: stash@{0}, stash@{1} 등.
refs/stash -> stash commit (worktree state)
parent[0] -> HEAD at time of stash
parent[1] -> index state commit
parent[0] -> HEAD at time of stash
Stash 객체 생성
use Pitmaster\Stash\Stash;
$stash = new Stash(
$repo->objectDatabase(),
$repo->refDatabase(),
$repo->gitDir(),
$repo->workDir(),
);
Push(변경 사항 저장)
// public function push(string $message = '', bool $includeUntracked = false): ObjectId
$stashId = $stash->push('Work in progress on login');
echo "Saved: {$stashId->hex}\n";
// Also stash untracked files
$stashId = $stash->push('With new files', includeUntracked: true);
Push는 다음 작업을 수행합니다:
- 현재 HEAD 커밋을 읽습니다
- 현재 인덱스(스테이징 상태)에서 트리를 만듭니다
- HEAD를 부모로 하는 "인덱스 커밋"을 만듭니다
- 현재 작업 디렉터리(스테이징되지 않은 변경 사항 포함)에서 트리를 만듭니다
- 스태시 커밋을 만듭니다(작업 트리 트리, 부모 = HEAD + 인덱스 커밋)
refs/stash을 스태시 커밋을 가리키도록 업데이트합니다- 스택에 reflog 항목을 추가합니다
- 작업 트리와 인덱스를 HEAD로 초기화합니다
Push 후 추적 중인 파일은 HEAD와 일치합니다. 추적되지 않는 파일은 includeUntracked이 설정되어 있지 않으면 그대로 남습니다. 설정되어 있으면 해당 파일은 제거되어 스태시 커밋 자체의 트리에 저장됩니다(git은 대신 세 번째 부모에 저장하므로 git에서는 추적 중인 변경 사항으로 표시됩니다). push()은 변경 사항이 없어도 항목을 만듭니다. 이때 git은 "저장할 로컬 변경 사항이 없습니다"라고 표시합니다.
기본 메시지 사용
$stashId = $stash->push();
// Message: "WIP on <branch>: <short hash> <HEAD subject>"
// With a message: "On <branch>: <message>"
분리된 HEAD 상태에서는 <branch>이 HEAD입니다.
Pop(적용 후 제거)
$stash->pop();
최상위 스태시 항목을 작업 디렉터리에 적용하고 스택에서 제거합니다. apply() 다음에 drop()을 실행하는 것과 같습니다.
특정 항목 Pop
$stash->pop(1); // Pop stash@{1}
Apply(제거하지 않고 복원)
$stash->apply(); // Apply stash@{0}
$stash->apply(2); // Apply stash@{2}
Apply는 파일별로 스태시를 현재 HEAD와 병합합니다(스태시 기준, 현재 HEAD, 스태시 트리). 스태시 항목은 스택에 남고 인덱스는 건드리지 않습니다. 충돌이 발생하면 파일에 Updated upstream 및 Stashed changes로 표시된 마커를 남기고 Pitmaster\Exceptions\MergeConflictException를 발생시킵니다. pop()은 항목을 계속 유지합니다. Apply는 먼저 로컬 변경 사항이 있는지 확인하지 않습니다(위 참고 사항 참조).
항목 나열
$entries = $stash->listEntries();
foreach ($entries as $entry) {
echo "stash@{{$entry['index']}}: {$entry['message']}\n";
// stash@{0}: On main: Work in progress on login
// stash@{1}: WIP on main: f707601 Add login form
}
각 항목에는 다음 정보가 포함됩니다:
index: 스태시 위치(0 = 가장 최근 항목)message: 스태시 메시지hash: 스태시 커밋 해시
Drop(적용하지 않고 제거)
$stash->drop(); // Drop stash@{0}
$stash->drop(1); // Drop stash@{1}
항목이 하나뿐일 때 삭제하면 refs/stash와 해당 reflog가 제거됩니다. 항목이 여러 개면 drop()은 현재 refs/stash만 이동시키고 reflog는 변경하지 않으므로 항목이 listEntries()에 남습니다(#10).
저장소 세부 정보
스태시 커밋 구조
commit <hash>
tree <worktree-tree-hash>
parent <HEAD-hash>
parent <index-commit-hash>
author <current identity>
committer <current identity>
<stash message>
인덱스 커밋 구조
commit <hash>
tree <index-tree-hash>
parent <HEAD-hash>
author <current identity>
committer <current identity>
index on <branch>: <short hash> <HEAD subject>
이름은 GIT_COMMITTER_NAME, GIT_AUTHOR_NAME, 저장소 설정의 user.name, Pitmaster 순으로 확인해 결정합니다(이메일도 같은 순서이며 마지막 기본값은 pitmaster@example.invalid입니다). 날짜는 GIT_COMMITTER_DATE, GIT_AUTHOR_DATE, 현재 시간 순으로 결정됩니다. 커밋과 달리 스태시는 PITMASTER_* 상수를 읽지 않습니다.
Reflog 저장소
스태시 항목은 .git/logs/refs/stash에 저장됩니다:
0000000000000000000000000000000000000000 abc123... Name <email> 1700000000 +0000 WIP on main: f707601 Add login form
abc123... def456... Name <email> 1700000001 +0000 On main: Work in progress on login
reflog는 시간순으로 읽습니다. 스태시 목록은 이를 역순으로 표시합니다(최신 항목이 먼저). 스태시 reflog는 core.logallrefupdates와 관계없이 기록됩니다.
CLI 사용
# Save current changes
./vendor/bin/pitmaster stash
./vendor/bin/pitmaster stash push
./vendor/bin/pitmaster stash push "Work in progress on login"
# Apply and remove top entry
./vendor/bin/pitmaster stash pop
# Apply without removing
./vendor/bin/pitmaster stash apply
# List all entries
./vendor/bin/pitmaster stash list
# Remove top entry without applying
./vendor/bin/pitmaster stash drop
메시지는 push(또는 save) 뒤에 오는 인수입니다. -m는 지원되지 않습니다. pop, apply 및 drop는 항상 stash@{0}에서 작동합니다. CLI에는 --include-untracked가 없습니다.
제한 사항
- 전체 작업 디렉터리만 스태시할 수 있습니다(
--keep-index없음). 추적되지 않는 파일은push($message, includeUntracked: true)을 통해서만 포함됩니다 --patch모드(인터랙티브 청크 선택)는 지원되지 않습니다- Pop/Apply는 작업 트리 상태를 복원하지만 인덱스는 복원하지 않습니다. 스테이징된 수정 사항은 스테이징되지 않은 수정 사항으로, 새로 스테이징한 파일은 추적되지 않는 파일로 돌아옵니다
Repository::resolve()는stash@{n}에서 허용되지 않습니다- 연결된 워크트리에서 스태시를 사용하면 잘못된 설정과 reflog 위치를 사용합니다(#14)