
# 서브모듈

Git 서브모듈은 하나의 저장소 안에 다른 저장소를 포함합니다. 부모 트리에서 서브모듈은 서브모듈 저장소의 특정 커밋을 가리키는 gitlink 항목(mode `160000`)으로 표시됩니다. 서브모듈의 URL와 경로는 `.gitmodules` 파일에 선언됩니다.

## 개념

부모 저장소의 트리에는 다음 항목이 있습니다:

```
100644 blob abc123...  README.md
040000 tree def456...  src/
160000 commit 789012... lib/external    <-- gitlink entry (submodule)
```

gitlink 항목은 체크아웃해야 하는 서브모듈 커밋을 기록합니다. 실제 서브모듈 저장소는 `.git/modules/<name>/`에 있습니다.

## SubmoduleManager

```php
use Pitmaster\Submodule\SubmoduleManager;

$manager = new SubmoduleManager(
    $repo->objectDatabase(),
    $repo->workDir(),
    $repo->gitDir(),
);
```

### 서브모듈 나열

```php
$submodules = $manager->list();

foreach ($submodules as $sub) {
    echo "{$sub->name}: {$sub->url} at {$sub->path}\n";
    if ($sub->branch !== null) {
        echo "  tracks branch: {$sub->branch}\n";
    }
}
```

### 서브모듈 값 객체

```php
use Pitmaster\Submodule\Submodule;

$sub->name;     // Submodule name (from .gitmodules section header)
$sub->path;     // Path in the working tree
$sub->url;      // Remote URL
$sub->branch;   // Tracked branch (nullable)
```

## .gitmodules 파싱

`.gitmodules` 파일은 서브모듈 설정을 선언합니다:

```ini
[submodule "lib/external"]
    path = lib/external
    url = https://github.com/vendor/lib.git
    branch = main

[submodule "tools/helper"]
    path = tools/helper
    url = https://github.com/vendor/helper.git
```

```php
$content = file_get_contents('.gitmodules');
$submodules = SubmoduleManager::parseGitmodules($content);

foreach ($submodules as $sub) {
    echo "{$sub->name} -> {$sub->url}\n";
}
```

파서는 다음 항목을 처리합니다:
- 섹션 헤더: `[submodule "<name>"]`
- 키-값 쌍: `path = ...`, `url = ...`, `branch = ...`
- 주석(`#`로 시작하는 줄)
- 공백 및 들여쓰기

`path`와 `url`이 모두 없는 항목은 건너뜁니다. 값에서 따옴표는 제거하지 않습니다.

## Gitlink 항목

Gitlink 항목은 mode가 `160000`인 트리 항목입니다. 서브모듈 저장소의 커밋을 가리키며 트리나 블롭은 가리키지 않습니다.

```php
$tree = $repo->readObject($treeHash);

foreach ($tree->entries as $entry) {
    if ($entry->isGitlink()) {
        echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
    }
}
```

### TreeEntry 감지

```php
$entry->isGitlink();  // true if mode is '160000'
$entry->mode;         // '160000'
```

## 서브모듈 초기화

```php
$manager->init();
```

`.gitmodules`에 선언된 각 서브모듈에 대해 다음을 수행합니다:
1. 빈 `.git/modules/<name>/` 디렉터리를 만듭니다
2. 없으면 서브모듈 경로 디렉터리를 만듭니다
3. `submodule.<name>.active = true` 및 `submodule.<name>.url`을 `.git/config`에 기록합니다

가져오거나 체크아웃하지 않으며 서브모듈의 `.git` 파일도 기록하지 않습니다.

## 서브모듈 업데이트

```php
$manager->update($repo->head()->tree);
```

지정된 트리에서 고정된 각 서브모듈에 대해 `update()`는 소스 저장소의 git 디렉터리를 `.git/modules/<name>/`에 복사하고, 서브모듈의 작업 파일을 소스의 파일로 교체합니다. `.git` 파일을 모듈 디렉터리를 가리키도록 기록하고, `core.worktree`을 설정한 다음 고정된 커밋에서 HEAD를 분리합니다.

서브모듈 URL은 로컬 경로여야 합니다. 절대 경로를 사용하거나 부모의 작업 트리에 대한 상대 경로를 사용하세요(git은 상대 URL을 대신 부모의 원격 URL을 기준으로 해석합니다). 스킴이 있는 URL(`https://...`)은 `RuntimeException('Submodule update currently supports only local paths: ...')`를 발생시킵니다.

업데이트 후에는 각 서브모듈을 별도의 저장소로 열 수 있습니다:

```php
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
```

## 고정된 커밋 확인

```php
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);

if ($pinnedId !== null) {
    echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}
```

`pinnedCommit()` 메서드는 트리 계층을 순회해 지정된 경로의 gitlink 항목을 찾고 대상 커밋 해시를 반환합니다.

## 서브모듈 상태

```php
$headCommit = $repo->head();
$statuses = $manager->status($headCommit->tree);

foreach ($statuses as $status) {
    $expected = $status['expected'] ?? 'not pinned';
    $actual = $status['actual'] ?? 'not initialized';
    $dirty = $status['dirty'] ? ' (dirty)' : '';

    echo "{$status['name']}: expected={$expected}, actual={$actual}{$dirty}\n";
}
```

상태 필드:
- `name`: 서브모듈 이름
- `path`: 작업 트리 내 경로
- `expected`: 부모 트리에서 고정된 커밋 해시(없으면 null)
- `actual`: 서브모듈의 현재 HEAD(초기화되지 않았으면 null)
- `dirty`: 두 커밋을 모두 알고 있고 서로 다르면 true. 서브모듈 작업 트리 내부의 변경 사항은 확인하지 않습니다.

## 디렉터리 구조

```
project/
  .git/
    modules/
      lib/
        external/          # Submodule's actual git directory (.git/modules/<name>)
        HEAD
        objects/
        refs/
        ...
  .gitmodules              # Submodule declarations
  lib/
    external/
      .git                 # File: "gitdir: ../../.git/modules/lib/external"
      src/
      README.md
  src/
  README.md
```

## 서브모듈 워크플로

```php
// 1. Open the parent repo
$repo = Pitmaster::open('/path/to/project');

// 2. List submodules
$manager = new SubmoduleManager(
    $repo->objectDatabase(),
    $repo->workDir(),
    $repo->gitDir(),
);

$submodules = $manager->list();

// 3. Populate the submodules pinned at HEAD (local source paths only)
$manager->update($repo->head()->tree);

// 4. Check status
$statuses = $manager->status($repo->head()->tree);

// 5. Open a submodule as its own repo
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
$subHead = $subRepo->head();
echo "Submodule HEAD: {$subHead->id->hex}\n";
```
