WAS Engineer - Linux 9번째

이정빈·2022년 9월 28일
0

리눅스 복습

목록 보기
9/11
post-thumbnail

버전 관리 시스템

리눅스를 사용하다 보면 다양한 파일을 만들고 변경하게 된다. 예를 들어 메모를 기록한 텍스트 파일, 배시의 설정 파일, 셸 스크립트 등은 한 번 작성한 것으로 끝나는 것이 아니라 이후 계속해서 추가나 변경하는 것이 일반적이다.

파일을 편집할 때 가끔씩 실수로 잘못된 내용을 저장할 수도 있고, 기존에 지운 내용이 다시 필요해지는 경우도 생긴다. 하지만 파일을 변경한 뒤 특정 과거 시점으로 복원하는 것은 무척 어려운 일이다.

이를 위해 작업하기 전에 늘 백업을 하는 것도 한 가지 방법이다. 보통 편집 당시의 날짜를 파일 이름에 붙여서 백업한다.

백업 파일이 많아지면 특정 시점으로 돌아가기 위해 확인해야 할 파일이 많아지게 되고 관리하는 데도 어려움이 따르게 된다.

이러한 문제를 해결하기 위해 존재하는 도구가 버전 관리 시스템이다. 버전 관리 시스템을 사용하면 수동으로 백업하지 않아도 변경 이력을 관리할 수 있다. 버전 관리 시스템은 크게 두 가지 기능을 제공한다.

  • 파일을 '언제,누가, 무슨 목적으로, 어떤 변경을 가했는가'를 기록할 수 있고 확인할 수도 있다.
  • 필요에 따라 과거의 특정 시점으로 파일을 복원할 수 있다.

여러 종류의 버전 관리 시스템이 있는데 이 책에서는 깃을 다룬다. 리눅스 커널을 비롯하여 많은 프로젝트가 깃을 사용하고 있으니 이번 기회에 깃의 사용법을 익혀 두면 크게 도움이 된다.

깃 설치와 초기 설정

먼저 깃의 설치 여부를 확인하는 방법부터 알아보겠다. 다음과 같이 version 옵션을 붙여서 git 명령어를 실행한다.

  • 설치된 깃의 버전 확인
$ git --version
git version 2.25.1

명령어가 발견되지 않았다는 에러 메시지가 출력됐다면 git-core라는 패키지를 설치해야 한다. CentOS에서는 다음과 같이 yum 명령어로 깃을 설치한다.

  • yum으로 깃 설치(CentOS)
$ yum install git-core
...생략...
Is this ok [y/d/N]:y

우분투에서는 apt-get을 사용하여 설치한다.

  • apt-get으로 깃 설치(우분투)
$ sudo apt-get install git-core
  1. 깃 초기 설정

깃을 설치한 후에는 먼저 이름과 메일 주소를 설정해야 한다. 다음과 같이 config 명령어를 실행한다.

  • 깃 초기 설정
$ git config --global user.name 'ldk'
$ git config --global user.email 'ldk@example.com'

기본적인 사용법

  1. 리포지터리 작성하기

여기서는 셸 스크립트 findgrep-19.sh를 만들고 수정하는 이력을 깃으로 관리한다.

  • 새로운 디렉터리 생성
$ mkdir -p ~/git/findgrep

해당 디렉터리로 이동하여 git init 명령어를 실행한다.

  • 디렉터리에서 깃 초기화
$ cd ~/git/findgrep
$ git init
/home/ldk/git/findgrep/.git/ 안의 빈 깃 저장소를 다시 초기화했습니다.

git init을 실행하면 초기화되어 .git이라는 디렉터리가 만들어진다.

  • .git 디렉터리가 만들어짐
$ ls -a
...git

.git 디렉터리가 깃 리포지터리의 실체이다.

리포지터리(repository)란 깃이 관리하는 파일의 이력을 보존하는 공간을 의미한다. 파일의 수정 이력은 전부 이 리포지터리에 기록된다. 또한, 과거 상태로 돌아갈 때도 리포지터리의 내용을 참고하게 된다. 따라서 리포지터리는 무척 중요한 디렉터리이므로 실수로 지우지 않도록 주의해야 한다. 리포지터리에 저장되는 파일을 사용자가 직접 보거나 편집할 일은 없다. 깃이 관리하는 파일이라고 보면 된다.

이력 관리의 대상이 되는 파일이 놓인 공간을 작업 트리라 한다. 이번 예에서는 findgrep 디렉터리가 이 프로젝트의 작업트리에 해당한다. 작업 트리에서 파일을 추가, 편집, 삭제하면서 작업을 진행한다.

  • 깃의 디렉터리 구성 예
디렉터리역할
~/git/findgrep/.git/리포지터리
~/git/findgrep/작업 트리
  1. 리포지터리에 파일 추가하기
  • 버전 관리할 파일 작성
$ touch findgrep-19.sh
$ chmod 755 findgrep-19.sh
$ vim findgrep-19.sh
...(편집 작업 수행)...

여기서는 findgrep-19.sh의 초기 버전으로 다음 4개 행만 추가한다.

  • findgrep-19.sh
#!/bin/bash

pattern=$1
find . -type f | xargs grep -nH "$pattern"

이 파일을 깃 리포지터리의 이력에 추가해 보겠다. 리포지터리에 추가할 때는 git add와 git commit이라는 두 명령어를 사용한다.

git add는 깃이 관리할 파일을 추가하는 명령어이다. 지금 시점에서 findgrep-19.sh는 아직 관리 대상에 추가되지 않은 상태이다.

  • 이력에 추가할 파일 지정
$ git add findgrep-19.sh

이어서 commit 명령어를 실행한다. git commit은 실제 리포지터리에 변경 이력을 추가하는 명령어인데, 이 때 -m 옵션을 지정하면 이번 변경 사항에 대한 메시지를 입력할 수 있다.

  • 리포지터리에 변경 이력 추가
$ git commit -m 'findgrep-19.sh 신규 등록'
[master (최상위-커밋) 137be16] findgrep-19.sh 신규 등록
1 file changed, 4 insertions(+)
create mode 100755 findgrep-19.sh

이로써 깃 리포지터리에 변경 이력이 등록된다. 변경 내용은 버전 하나로 리포지터리에 보관된다.

이처럼 어느 시점에서의 프로젝트의 상태를 리비전(revision) 혹은 커밋(commit)이라 한다. 그리고 새로운 리비전을 리포지터리에 등록하는 것을 '커밋한다'라고 한다.

  1. 차이 표시 그리고 다시 커밋하기

파일을 수정한 뒤 한 번 더 커밋해 보겠다. 먼저 findgrep-19.sh를 에디터로 열어서 다음과 같이 수정한다.

  • findgrep-19.sh
#!/bin/bash

pattern=$1
directory=$2
if [ -z "$directory" ]; then
	directory='.'
fi

find "$directory" -type f | xargs grep -nH "$pattern"

변경 사항을 커밋하기 전에 여기서 status와 diff라는 명령어를 소개하겠다.
status는 현재 작업 트리의 상태를 표시해준다.

  • 작업 트리 상태 출력
$ git status
현재 브랜치 master
커밋하도록 정하지 않은 변경 사항:
(무엇을 커밋할지 바꾸려면 "git add <파일>..."을 사용하십시오)
  (작업 디렉터리의 변경을 무시하려면 "git restore <file>..."을 사용하시오)
    수정함:	findgrep-19.sh
    
커밋할 변경 사항을 추가하지 않았다.("git add" 및/또는 "git commit -a"를 사용하십시오)

현재 커밋한 뒤 findgrep-19.sh를 다시 수정한 상태이므로 5번째 행에 수정함:이라고 표시되고 있다. 변경 사항을 확인하려면 git diff를 사용하면 된다.

git diff로 차이점을 확인하여 문제가 없다면 두 번째 커밋을 수행한다. 처음 파일을 추가했을 때와 동일하게 먼저 git add를 실행한다.

  • 파일 추가
$ git add findgrep-19.sh

이어서 git commit을 수행하는데 이번에는 -m 옵션 없이 실행해 보겠다.

  • 옵션 없이 커밋
$ git commit

이처럼 -m옵션 없이 git commit을 실행하면 로그 메시지를 입력하기 위해 에디터가 실행된다. 어떤 에디터를 사용할지 설정하지 않으면 Vim이 실행된다. 에디터를 통해 다음과 같이 커밋 로그를 작성한다.

  • 커밋 로그 메시지
파일 검색을 위한 디렉터리를 지정 가능하도록 수정

디렉터리를 이동하지 않아도 임의의 디렉터리에서 파일을 검색할 수 있도록 수정
# 변경 사항에 대한 커밋 메시지를 입력하십시오. '#' 문자로 시작하는
# 줄은 무시되고, 메시지를 입력하지 않으면 커밋이 중지됩니다.
#
# 현재 브랜치 master
# 커밋할 변경 사항:
#		수정함:	findgrep-19.sh
#

커밋을 위한 로그 메시지는 다음과 같은 규칙을 따른다.

  • 첫 번째 행: 변경에 대한 요약
  • 두 번째 행: 빈 행
  • 세 번째 행: 상세 메시지

로그 메시지는 나중에 보더라도 어떤 이유로 변경했는지 쉽게 알 수 있도록 작성해야 한다. 무엇을 변경했는지는 파일을 비교하여 확인할 수 있지만 왜 변경했는지는 쉽게 파악하기 어렵기 때문이다.

그리고 #으로 시작하는 행은 주석이니 그대로 놔두어도 무방하다.
로그 메시지를 작성했으면 에디터를 저장하면서 종료한다. 그러면 리비전 하나가 커밋된다.

  • 두 번째 커밋
$ git commit
[master 68aee44] 파일 검색을 위한 디렉터리를 지정가능하도록 수정
 1 file changed, 6 insertions(+), 1 deletion(-)
  1. 변경 이력 확인하기

지금까지 두 번 커밋하였다. 이 이력은 깃의 리포지터리에 보관되어 언제든지 확인할 수 있다. git log 명령어로 변경 이력을 확인한다.

git log를 실행할 때 -p 옵션을 지정하면 커밋별로 차이점을 함께 표시한다.

이처럼 깃을 사용하면 '언제, 누가, 무슨 이유로, 어떤 변경을 했는지'가 이력으로 보관되어 확인할 수 있다. git add와 git commit로 변경을 가하고 git diff나 git log로 차이점을 확인하는 것이 가자아 기본적인 사용법에 해당한다.

  • git의 기본 명령어
명령어내용
git init리포지터리 생성
git addcommit 대상으로 등록
git commitcommit 수행
git status작업 트리의 상태 출력
git diff차이 표시
git log이력 표시

작업 트리와 인덱스

깃의 index라는 영역을 알아야 한다. 깃은 작업 트리에 있는 파일의 변경 사항을 리포지터리에 반영하기 전에 일단 인덱스라고 불리는 영역에 배치한다.

인덱스란 커밋하기 전에 리포지터리와 작업 트리 사이에 존재하는 공간이다. 그리고 작업 트리에서 인덱스에 파일을 등록하는 명령어가 바로 git add이다.

작업트리와 인덱스는 별도 공간이므로 내용이 일치하지 않을 수 있다. 예를 들어 작업 트리에서 파일 두 개를 수정한 뒤 그 중 파일 하나만 인덱스에 등록할 수 도 있다. 그리고 git add로 인덱스에 등록한 뒤에 다시 수정하면 자우에 수정한 부분은 인덱스에 포함되지 않는다.

git add는 여러 번 실행해도 된다. 즉, 한 파일에 대한 작업이 끝날 때마다 인덱스에 등록해도 무방하다. 그리고 최종적으로 git commit 명령어를 실행하면 인덱스에 등록된 내용이 리포지터리에 등록된다.

git diff 명령어로 작업 트리, 인덱스, 리포지터리 간의 차이를 확인할 수 있다.

먼저 git diff 명령어를 실행할 때 아무것도 지정하지 않으면 작업 트리와 인덱스의 차이를 표시한다.

이후 git add를 실행하여 변경 사항을 인덱스에 등록하면 작업 트리와 인덱스가 동일한 상태가 된다. 따라서 git diff를 실행해도 차이가 표시되지 않는다.

  • git add 직후에는 차이가 없음
$ git add findgrep-19.sh
$ git diff

하지만 아직 커밋은 하지 않았기 때문에 인덱스와 리포지터리에는 차이가 있다. 인덱스와 리포지터리의 차이를 출력하려면 git diff 명령어에 --cached 옵션을 지정한다.

커밋 단위와 인덱스

이처럼 깃에서 작업 트리와 인덱스 공간이 구분되어 있는 이유는 커밋 하나에 포함할 변경 사항을 선택할 수 있도록 하기 위해서이다.

커밋 하나에는 한 가지 이슈와 관련된 수정 사항만 포함하는 것이 좋다.

그렇지만 git add로 파일을 하나씩 등록하는 것은 번거로울 수 있다. 이때는 git add에 이어 -u 옵션을 지정하면 깃에 등록된 모든 파일이 인덱스에 등록된다.

  • 변경한 파일을 전부 인덱스에 등록
$ git add -u

변경한 파일이 만혹 전부 인덱스에 등록할 때는 -u 옵션이 편리하다.
하지만 -u 옵션으로는 새로 작성한 파일이 등록되지 않는다. 새로 작성한 파일을 포함해 모든 파일을 인덱스에 등록하려면 -A 옵션을 사용한다.

  • 모든 파일을 인덱스에 등록
$ git add -A

그러면 변경한 파일과 새로 작성한 파일들이 전부 인덱스에 등록된다.

실수했을 때 복구하기

버전 관리 시스템을 사용하는 가장 큰 이점은 예전 상태로 쉽게 복원할 수 있다는 점이다. 즉, 실수를 저질러도 예전 상태로 쉽게 복원할 수 있다.

  1. 커밋하지 않은 변경 사항 복구

먼저 작업 트리에서 발생한 실수에서 복구하는 방법을 알아보자. 작업하다 보면 에디터를 잘못 조작해서 엉뚱한 내용을 저장하거나, 다른 파일에 덮어쓰거나, 파일을 지우는 경우가 생긴다.

이 때 아직 커밋하지 않았다면 작업 트리의 최상위 디렉터리로 이동하여 다음 명령을 실행한다.

$ git checkout HEAD.

그러면 파일의 변경 사항이 전부 복원되면 인덱스에 추가한 내용도 전부 사라지게 된다. 즉, 이전 커밋의 상태로 복원되어 작업 트리와 인덱스가 리포지터리와 동일한 상태가 된다.

하지만 아직 커밋하지 않은 작업 내용이 모두 사라지므로 반드시 git diff로 확인한 뒤 수행해야 한다.

  1. 잘못된 커밋으로부터 복구
    커밋한 내용을 복원하는 방법을 알아보자.
    리포지터리의 이력을 확인하다 과거에 실수로 커밋한 사실을 발견했다면 git revert를 실행하면 된다.
  • 커밋 취소
git revert <취소하고 싶은 커밋의 오브젝트명>

git revert를 한다고 해서 깃의 커밋 이력에서 해당 커밋이 지워지는 것은 아니다. 대신에 해당 커밋의 변경 사항을 복구하는 새로운 커밋이 만들어진다. 즉, 변경 사항을 수정하여 수동으로 커밋하는 것과 동일한 작업을 수행하는 것이다.

git revert를 실행하면 일반 커밋을 할 때와 마찬가지로 커밋 로그를 입력하는 에디터가 실행된다. 여기서 커밋 로그를 입력하고 에디터를 종료하면 새로운 커밋이 생성된다. 수정 사항이 많아서 직접 고치기 힘들 때 무척 편리한 기능이다.

브랜치

소프트웨어 개발은 보통 병렬로 진행한다. 이를테면 새로운 기능을 개발하면서 기존 코드의 버그를 수정하는 일이 동시에 진행되는 것이다. 이 때 서로 관계가 없는 작업을 전혀 다른 공간에서 진행하면 좋을 것이다.

깃을 포함한 많은 버전 관리 시스템이 리비전 하나에서 복수의 커밋이 파생하는 것을 지원한다. 갈라진 이력의 흐름을 브랜치(branch)라고 한다.

브랜치에는 이름을 붙일 수 있다. 좀 더 정확하게 표현하자면 깃의 브랜치란 갈라진 이력의 선두에 있는 커밋을 가리키는 라벨(label)이다.

  • 브랜치 목록 출력
$ git branch
* master

master란 깃 리포지터리를 만들 때 자동으로 만들어지는 브랜치다. 보통 master 브랜치가 프로젝트의 기본이다. 지금까지 별도의 브랜치를 만들지 않아서 모든 커밋은 master 브랜치에 등록되었다.

  • 새로운 브랜치 생성
git branch <브랜치 이름>

branch 명령어로 브랜치 목록을 확인할 수 있다.

  • 브랜치 전환
git checkout <전환할 브랜치>

다른 브랜치에서 작업한 내용에 대해 테스트 한 뒤 문제가 없다면 master 브랜치에 반영할 수 있다. 한 브랜치의 내용을 다른 브랜치에 반영하는 것을 머지(merge)라고 한다.

A 브랜치의 내용을 B 브랜치로 머지하려면 먼저 B 브랜치로 전환해야 한다.
이후 git merge에 이어 반영할 브랜치 이름을 지정해 머지한다.

  • 지정한 브랜치의 내용을 현재 브랜치에 머지
git merge <머지할 브랜치 이름>
  • 브랜치 삭제
git branch -d <브랜치 이름>

한 가지 기능을 추가하기 위해 만든 브랜치를 토픽(topic) 브랜치라 한다. 작업을 진행할 때는 언제나 토픽 브랜치를 만들어 진행한 뒤에 master에 머지하는 것이 좋다.

브랜치를 삭제할 때는 해당 브랜치에서 커밋한 모든 내용이 사라지므로 주의가 필요하다. 깃에서는 d 옵션으로 아직 머지하지 않은 브랜치를 삭제할 수 없다.

강제로 삭제하려면 -D 옵션을 사용해야 한다.

리포지터리의 백업 작성

디스크가 고장나거가 실수로 리포지터리가 삭제되면 지금까지 작업한 이력을 전부 잃게 된다. 따라서 리포지터리를 백업하는 방법에 대해 알아보겠다.

깃에서는 여러 리포지터리를 만들어 이력을 공유하는 것이 가능하다.

백업용 리포지터를 만들 때에는 git init 명령어에 --bare라는 옵션을 지정한다. 그리고 이 때 관례적으로 디렉터리 이름 끝에 .git을 붙인다.

  • 변동 이력 전송
git push <이력을 전달받을 리포지터리> <이력을 보낼 브랜치>:<이력을 전달받을 브랜치>

<이력을 전달받을 리포지터리>에는 앞서 작성한 백업용 리포지터리의 경로를 지정한다. <이력을 보낼 브랜치>에는 현 리포지터리에서 백업하고 싶은 브랜치를 지정한다. <이력을 전달받을 브랜치>에는 백업용 리포지터리의 브랜치 이름을 지정한다. 보통 두 브랜치는 동일한 이름을 지정한다.

  • 리포지터리 복제
git clone <복원할 리포지터리>
  • 리포지터리의 경로에 별명 부여
git remote add <별명> <리포지터리 경로>
  • 리포지터리 경로의 별명 목록 확인
$ git remote -v

2인 이상의 작업

지금까지는 혼자서 리포지터리를 만들고 작업하는 방법을 알아봤다. 하지만 일정 규모 이상의 프로젝트는 여러 사람이 동시에 작업을 진행하는 것이 보통이다. 이 때 버전 관리 시스템을 사용하면 공동 작업을 쉽게 통합할 수 있다.

여러 사람이 작업할 때는 먼저 각 사용자가 자신의 리포지터리를 마련하여 작업을 진행한다. 자신의 리포지터리에 커밋하며 작업하다가 적절한 시점에 커밋 이력을 다른 리포지터리에 전송한다. 이 때 앞서 소개한 push가 사용된다.

이처럼 독립된 리포지터리 간에 변경 이력을 공유할 수 있는 버전 관리 시스템을 분산형 버전 관리 시스템이라 한다. 깃이 대표적인 분산형 버전 관리 시스템이다.

  1. 공유 리포지터리 작성하기

사용자 1의 리포지터리와 사용자 2의 리포지터리는 각 사용자가 작업하기 위한 리포지터리이다. 그리고 공유 리포지터리는 커밋 이력을 공유하기 위해 존재하는 리포지터리이다. 각 사용자는 본인의 리포지터리에 커밋하여 작업하다가 적절한 시점에 공유 리포지터리에 커밋 내역을 전송한다. 그리고 다른 사용자가 전송한 커밋 내역을 자기 자신의 리포지터리에 반영하기도 한다. 즉, 공유 리포지터리를 통해 사용자 간의 작업 내용을 공유하게 되는 것이다.

여러 사용자가 하나의 백업용 리포지터리를 공유한다고 볼 수 있다.

먼저 백업용 리포지터리를 만드는 것과 동일한 방법으로 공유 리포지터리를 만든다. 그리고 사용자 2의 리포지터리에 공유 리포지터리를 clone 으로 복제하여 만든다. 그러면 해당 시점에서 세 리포지터리가 전부 동일한 커밋 이력을 가지게 된다. 그리고 두 리포지터리는 origin으로 공유 리포지터를 바라보게 된다.

  • 다른 리포지터리의 이력 받기
git fetch <리포지터리>

<리포지터리>에는 git push와 마찬가지로 리포지터리의 경로 혹은 git remote로 등록한 별명을 지정한다.

git fetch로 가져온 이력은 원격 추적 브랜치라 불리는 브랜치에 보관된다. 이는 자기 자신이 작성한 브랜치가 아니라 다른 리포지터리의 내용을 내려받기 위한 브랜치이다. 이와 반대로 처음부터 있는 master 브랜치나 git branch 명령어로 명시적으로 만든 브랜치를 로컬(local) 브랜치라고 한다. 원격 추적 브랜치는 '원격 이름/브랜치 이름'이라는 이름으로 자동으로 만들어진다. 원격 추적 브랜치 목록을 확인하고 싶을 때는 git branch 명령어에 -r 옵션을 지정하여 실행하면 된다.

git fetch 명령어를 실행해도 원격 추적 브랜치가 갱신될 뿐 아직 작업 트리나 로컬 브랜치에는 반영되지 않는다. 따라서 가져온 이력을 로컬 브랜치에 반영하려면 git merge 명령어를 실행해야 한다.

git fetch와 git merge는 함께 사용되는 경우가 많아 이를 합친 git pull이라는 명령어가 있다.

지금까지의 설명을 바탕으로 공유 리포지터리를 통해 협업할 때 각 개인이 작업하는 흐름을 정리하면 다음과 같다.

  • 일정 간격(1일 1회 정도)으로 git fetch/git merge로 공유 리포지터리의 내용을 자기 자신의 리포지터리에 반영한다.
  • 자기 자신의 리포지터리에 커밋하여 작업한다.
  • 작업 내용을 공개하고 싶은 시점에 git push 명령어로 공유 리포지터리에 자기 자신의 작업 이력을 전송한다.

이것이 여러 사용자가 공동 작업할 때의 전형적인 워크 플로우이다. 실제 깃을 운영할 때는 공유 리포지터리에 접근하는 각 사용자들의 읽기/쓰기 권한이 적절히 설정되어야 한다. 그리고 리포지터리는 보통 HTTP나 SSH와 같이 네트워크를 통해 접근 가능한 곳에 보관한다.

충돌 해결

git merge 명령어는 커밋 두개를 하나로 만드는 작업이다. 하나의 파일에 대한 두 명이 각각 따로 수정하여 공유 리포지터에 push했을 시 깃에서는 수정 내용에 충돌이 없다면 두 변경 사항을 합쳐서 새로운 커밋을 만든다. 이러한 흐름은 리포지터리 내 브랜치 간 머지에서도 마찬가지이다.

하지만 깃이 자동으로 병합할 수 없는 경우가 있다. 예를 들어 같은 파일의 같은 행을 서로 다르게 수정한 경우가 이에 해당한다. 이때는 merge 명령어가 자동 병합에 실패한 것을 통지한다. 그리고 이러한 상황을 충돌(conflict)이 발생했다라고 한다.

git diff 명령어를 통해 충돌이 발생한 이유를 파악하여 대응할 수 있다.

깃 매뉴얼

깃에는 commit이나 log처럼 다양한 서브 명령어가 존재하며 이들 서브 명령어별로 매뉴얼이 분리되어 있다. 먼저 깃에 대한 매뉴얼은 man git으로 읽을 수 있다.

  • 깃에 대한 매뉴얼 표시
$ man git

그리고 서브 명령어별 man 페이지는 man git-<서브 명령어>같이 실행하면 된다. 예를 들어 git log와 관련된 man 페이지를 읽으려면 다음과 같이 실행한다.

  • git log에 대한 매뉴얼 표시
$ man git-log
  • 깃 도움말 출력
$ git help
profile
WAS Engineer, Cloud Engineer(지망)

0개의 댓글