Skip to content

Commit 5fc4fce

Browse files
committed
object: 객체의 데이터 구조를 출력하는 시각화 함수 추가
git-scm의 내부 객체 문서의 데이터 구조 다이어그램 참고. 구현은 claude의 도움을 받음.
1 parent 04bda4d commit 5fc4fce

2 files changed

Lines changed: 219 additions & 0 deletions

File tree

include/object.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,16 @@ typedef struct {
133133
int commit_write(const Commit *c, uint8_t *hash_out);
134134
int commit_read(const char *hex, Commit *c);
135135

136+
// -- 디버그/시각화 유틸리티
137+
//
138+
// git 오브젝트가 실제로 어떤 형태로 .git/objects에 저장되고 서로 어떻게 연결되는지 시각화.
139+
140+
// 단일 오브젝트의 내부 저장 형태(경로/타입/크기/본문)를 사람이 읽기 좋게 출력.
141+
// 저장되는 raw 형식 "<type> <size>\0<content>"를 그대로 풀어서 보여준다.
142+
void object_debug_print(const char *hex_hash);
143+
144+
// hex_hash를 루트로 오브젝트 그래프(commit -> tree -> blob)를
145+
// 트리 다이어그램 형태로 재귀 출력한다.
146+
void object_print_graph(const char *hex_hash);
147+
136148
#endif

src/object.c

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,3 +505,210 @@ int commit_read(const char *hex, Commit *c)
505505
free(data);
506506
return 0;
507507
}
508+
509+
// -- 디버그/시각화 유틸리티
510+
511+
#define GRAPH_MAX_DEPTH 64
512+
#define GRAPH_BLOB_PREVIEW 48 /* blob 본문 미리보기 최대 바이트 */
513+
514+
// 본문을 출력 가능한 형태로 이스케이프해서 찍는다. 바이너리/제어문자 안전.
515+
static void print_escaped(const uint8_t *data, size_t len, size_t max)
516+
{
517+
size_t n = len < max ? len : max;
518+
519+
for (size_t i = 0; i < n; i++) {
520+
unsigned char ch = data[i];
521+
522+
if (ch == '\n') {
523+
fputs("\\n", stdout);
524+
} else if (ch == '\t') {
525+
fputs("\\t", stdout);
526+
} else if (ch == '\r') {
527+
fputs("\\r", stdout);
528+
} else if (ch >= 0x20 && ch < 0x7f) {
529+
putchar(ch);
530+
} else {
531+
printf("\\x%02x", ch);
532+
}
533+
}
534+
535+
if (len > max) {
536+
fputs("...", stdout);
537+
}
538+
}
539+
540+
// 트리 엔트리의 타입 문자열 (ls-tree 표기와 동일).
541+
static const char *entry_type(const TreeEntry *e)
542+
{
543+
return e->mode == MODE_DIR ? OBJ_TREE : OBJ_BLOB;
544+
}
545+
546+
void object_debug_print(const char *hex_hash)
547+
{
548+
char type[8];
549+
uint8_t *data = NULL;
550+
size_t len = 0;
551+
char path[512];
552+
553+
if (!hex_hash) {
554+
return;
555+
}
556+
557+
object_path(hex_hash, path, sizeof(path));
558+
559+
if (object_read(hex_hash, type, &data, &len) < 0) {
560+
printf("object %s: 읽기 실패 (%s)\n", hex_hash, path);
561+
return;
562+
}
563+
564+
printf("=== object %s ===\n", hex_hash);
565+
printf(" path : %s\n", path);
566+
printf(" type : %s\n", type);
567+
printf(" size : %zu bytes\n", len);
568+
// 압축 전 raw 형식: "<type> <size>\0<content>"
569+
printf(" stored: \"%s %zu\\0\" + <content %zu bytes> (zlib deflate)\n", type, len, len);
570+
printf(" ----- content -----\n");
571+
572+
if (strcmp(type, OBJ_TREE) == 0) {
573+
Tree t;
574+
575+
if (tree_read(hex_hash, &t) == 0) {
576+
for (int i = 0; i < t.count; i++) {
577+
TreeEntry *e = &t.entries[i];
578+
char ehex[GIT_HEX_STR_SIZE];
579+
580+
sha1_to_hex(e->sha1, ehex);
581+
printf(" %-6s %s %s\t%s\n", mode_to_str(e->mode), entry_type(e),
582+
ehex, e->name);
583+
}
584+
}
585+
} else if (strcmp(type, OBJ_COMMIT) == 0) {
586+
// 커밋 본문은 텍스트라 그대로 출력
587+
fwrite(data, 1, len, stdout);
588+
if (len == 0 || data[len - 1] != '\n') {
589+
putchar('\n');
590+
}
591+
} else {
592+
// blob 등: 제어문자를 이스케이프해서 출력
593+
printf(" \"");
594+
print_escaped(data, len, len);
595+
printf("\"\n");
596+
}
597+
598+
free(data);
599+
}
600+
601+
// hex를 루트로 오브젝트 그래프를 재귀 출력한다.
602+
// head: 이 노드 줄 앞에 붙는 접두사 (커넥터 포함)
603+
// cont: 이 노드의 자식 줄에 이어 붙는 접두사 (수직선 포함)
604+
// label: 트리 엔트리에서 넘어온 "<mode> <type> <name>" 라벨 (루트면 NULL)
605+
static void graph_rec(const char *hex, const char *label, const char *head, const char *cont,
606+
int depth)
607+
{
608+
char type[8];
609+
uint8_t *data = NULL;
610+
size_t len = 0;
611+
char path[512];
612+
613+
object_path(hex, path, sizeof(path));
614+
615+
if (object_read(hex, type, &data, &len) < 0) {
616+
printf("%s%.7s <읽기 실패: %s>\n", head, hex, path);
617+
return;
618+
}
619+
620+
// 노드 한 줄: [접두사] [라벨 또는 (타입)] [짧은 해시] [저장 경로]
621+
if (label) {
622+
printf("%s%s %.7s %s\n", head, label, hex, path);
623+
} else {
624+
printf("%s(%s) %.7s %s\n", head, type, hex, path);
625+
}
626+
627+
if (strcmp(type, OBJ_BLOB) == 0) {
628+
// blob은 본문 미리보기를 자식 줄로 한 줄 더 보여준다.
629+
printf("%s ↳ \"", cont);
630+
print_escaped(data, len, GRAPH_BLOB_PREVIEW);
631+
printf("\" (%zu bytes)\n", len);
632+
} else if (strcmp(type, OBJ_TREE) == 0) {
633+
Tree t;
634+
635+
if (depth > 0 && tree_read(hex, &t) == 0) {
636+
for (int i = 0; i < t.count; i++) {
637+
TreeEntry *e = &t.entries[i];
638+
int last = (i == t.count - 1);
639+
char child_head[1024];
640+
char child_cont[1024];
641+
char label_buf[320];
642+
char ehex[GIT_HEX_STR_SIZE];
643+
644+
snprintf(child_head, sizeof(child_head), "%s%s", cont,
645+
last ? "└── " : "├── ");
646+
snprintf(child_cont, sizeof(child_cont), "%s%s", cont,
647+
last ? " " : "│ ");
648+
649+
sha1_to_hex(e->sha1, ehex);
650+
snprintf(label_buf, sizeof(label_buf), "%-6s %s %s",
651+
mode_to_str(e->mode), entry_type(e), e->name);
652+
653+
graph_rec(ehex, label_buf, child_head, child_cont, depth - 1);
654+
}
655+
}
656+
} else if (strcmp(type, OBJ_COMMIT) == 0) {
657+
Commit c;
658+
659+
if (commit_read(hex, &c) == 0) {
660+
char msg[80];
661+
char *nl;
662+
int total = 1 + c.parent_count; // tree 1개 + parent N개
663+
int idx = 0;
664+
665+
// 커밋 메타데이터를 정보 줄로 출력
666+
printf("%s│ author : %s <%s>\n", cont, c.author.name, c.author.email);
667+
668+
snprintf(msg, sizeof(msg), "%s", c.message);
669+
nl = strchr(msg, '\n');
670+
if (nl) {
671+
*nl = '\0';
672+
}
673+
printf("%s│ message: %s\n", cont, msg);
674+
printf("%s│\n", cont);
675+
676+
// tree 자식 (재귀)
677+
{
678+
int last = (idx == total - 1);
679+
char child_head[1024];
680+
char child_cont[1024];
681+
682+
snprintf(child_head, sizeof(child_head), "%s%s", cont,
683+
last ? "└── " : "├── ");
684+
snprintf(child_cont, sizeof(child_cont), "%s%s", cont,
685+
last ? " " : "│ ");
686+
687+
graph_rec(c.tree_hex, "tree", child_head, child_cont, depth - 1);
688+
idx++;
689+
}
690+
691+
// parent 들은 히스토리 폭주를 막기 위해 재귀하지 않고 참조 줄만 출력
692+
for (int i = 0; i < c.parent_count; i++) {
693+
int last = (idx == total - 1);
694+
char ppath[512];
695+
696+
object_path(c.parent_hex[i], ppath, sizeof(ppath));
697+
printf("%s%sparent %.7s %s\n", cont, last ? "└── " : "├── ",
698+
c.parent_hex[i], ppath);
699+
idx++;
700+
}
701+
}
702+
}
703+
704+
free(data);
705+
}
706+
707+
void object_print_graph(const char *hex_hash)
708+
{
709+
if (!hex_hash) {
710+
return;
711+
}
712+
713+
graph_rec(hex_hash, NULL, "", "", GRAPH_MAX_DEPTH);
714+
}

0 commit comments

Comments
 (0)