IT/VCS

[git] git add -p를 사용해 원하는 부분만 커밋하기

심량 2014. 9. 11. 16:35

소스 코드 수정 중에는 다양한 변경 사항(버그 수정, 기능 추가, 코드 정리 등)이 한 파일에 섞여 들어갈 때가 많습니다. 이럴 때 git add -p 명령을 사용하면 파일의 특정 부분만 선택적으로 스테이지에 올려 커밋할 수 있습니다.

 

git add -p 명령어 실행

git add -p 또는 git add --patch 명령을 실행하면 다음과 같은 질문이 나타납니다:

Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?

 

여기서 각 옵션이 하는 역할을 간단히 정리했습니다:

  1. y (yes): 현재 보이는 코드 덩어리(chunk)를 스테이지에 추가합니다.
  2. n (no): 현재 덩어리를 스테이지에 추가하지 않습니다.
  3. q (quit): 작업을 종료합니다. 종료하더라도 y로 추가한 내용은 스테이지에 남아 있습니다.
  4. a (all): 이후 모든 덩어리를 자동으로 추가합니다.
  5. d (delete): 이후 모든 덩어리를 자동으로 제외합니다.
  6. /: 정규식을 사용해 일치하는 덩어리를 검색합니다.
  7. j: 현재 덩어리를 결정하지 않고 다음 덩어리로 이동합니다.
  8. J: j와 비슷하지만 더 큰 단위로 다음 덩어리를 보여줍니다.
  9. g: 덩어리 목록을 출력해 특정 덩어리를 선택할 수 있습니다.
  10. e (edit): 덩어리를 수동으로 편집합니다. 더 세부적인 조정이 필요할 때 유용합니다. 생각보다 통과되기 어려움. 숙련자용
  11. ?: 명령어 도움말을 보여줍니다
  12. k/K: j/J 와 비슷하지만 다음 덩어리가 아닌 이전 덩어리를 찾습니다. 목록에는 보이지 않지만 동작합니다.

간단하게 사용하는 예제를 살펴보겠습니다.

# Manual hunk edit mode -- see bottom for a quick guide
@@ -5,8 +5,8 @@
 using namespace std;
~
 unsigned int i;
-unsigned char data[255] = {0xf4,0xf3,0x0a,0x0b,0x0c,0x0d};
-int printl_data(unsigned int length, void* addr, const char* name){
+unsigned char text[] = {0xf4,0xf3,0xf2,0xf1,0xf0,0x01,0x02,0x03,0x04,0x05,0x06,0x0a,0x0b,0x0c,0x0d};
+int print_text (unsigned int length, void* addr, const char* name){
     unsigned char* charp = (unsigned char*)addr;
     int i;
     printf("##### ALL PACKET ALERT INFO %s#####",name);
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging. If it does not apply cleanly, you will be given
# an opportunity to edit again. If all lines of the hunk are removed,
# then the edit is aborted and the hunk is left unchanged.

 

더보기

제 git은 vim이 기본 편집기(git config --list 명령으로 확인 가능, core.editor 값)로 지정되어 있어서 vim이 실행된 결과입니다.

'#'으로 시작하는 줄이 주석인데 맨 첫줄에 수동으로 덩어리를 편집하는 중이라는 메시지가 있습니다(Manual hunk edit mode). 이 화면에서 언급된 소스를 보면 '-'로 시작하는 줄이 있고 '+'로 시작하는 줄이 있습니다. diff -u 명령을 자주 써보신 분들에겐 익숙한 화면일 겁니다. '-'는 고치면서 없어진 내용이고 '+'는 고치면서 추가된 내용입니다. data란 변수 이름과 내용이 바뀌고, print_data란 함수의 이름이 바뀐 것을 확인할 수 있습니다.

여기에서 print_data => print_text라고 바뀐 부분만 적용하고 다른 부분은 아직 그대로 두고 싶으면  '-'로 시작하는 부분이 바뀌기 전 내용이니까 하단 주석의 친절한 설명대로 '-'를 빈 칸(' ')으로 바꿔줍니다.

이제 위 내용 중 코드 덩어리(chunk)만 살펴볼까요?

-unsigned char data[255] = {0xf4,0xf3,0x0a,0x0b,0x0c,0x0d};
-int printl_data(unsigned int length, void* addr, const char* name){
+unsigned char text[] = {0xf4,0xf3,0xf2,0xf1,0xf0,0x01,0x02,0x03,0x04,0x05,0x06,0x0a,0x0b,0x0c,0x0d};
+int print_text (unsigned int length, void* addr, const char* name){

 

변경된 내용을 부분적으로만 스테이지에 올리려면 다음과 같이 수동으로 덩어리를 수정합니다.

  1. git add -p 실행 후 e 명령을 입력하면 편집 모드로 전환됩니다.
  2. 화면에 나타난 변경사항에서 원하는 부분만 남기거나 수정합니다:
    • -로 시작하는 줄은 빈 칸(' ')으로 바꿔 삭제 처리.
    • +로 시작하는 줄은 필요 없다면 삭제.

결과:

 unsigned char data[255] = {0xf4,0xf3,0x0a,0x0b,0x0c,0x0d};
-int printl_data(unsigned int length, void* addr, const char* name){
+int print_text (unsigned int length, void* addr, const char* name){

  3. 편집을 완료하면 스테이지에 적용됩니다.

 

이렇게 바꾸니 printl_packet 함수가 print_text 로 바뀐 부분만 남았고, data 자료형 변경 부분은 스테이지 영역에서 제외되었습니다.

이제 더 바꿀 부분이 없다면 알아서 종료되었을 것이고 뒷 내용들이 더 있다면 q 명령으로 빠져나와 제대로 적용되었는지 'git diff --cached' 명령으로 확인해봅니다.

$ git diff --cached
diff --git a/test.cpp b/test.cpp
index 64f43b2..1d82322 100644
--- a/test.cpp
+++ b/test.cpp
@@ -6,7 +6,7 @@ using namespace std;
 
 unsigned int i;
 unsigned char data[255] = {0xf4,0xf3,0x0a,0x0b,0x0c,0x0d};
-int printl_data(unsigned int length, void* addr, const char* name){
+int print_text (unsigned int length, void* addr, const char* name){
     unsigned char* charp = (unsigned char*)addr;
     int i;
     printf("##### ALL PACKET ALERT INFO %s#####",name);
(END)

 

커밋 대상으로 잘 지정된 것(staged)을 확인할 수 있습니다.

 

아까 제외한 부분은 git diff 명령으로 확인할 수 있습니다. 즉, 이것은 스테이징 영역에 포함안되어 있습니다. commit 명령 내리면 git diff --cached 할 때 보이는 내용만 commit 이 됩니다.

 $ git diff
diff --git a/test.cpp b/test.cpp
index 1d82322..2a65265 100644
--- a/test.cpp
+++ b/test.cpp
@@ -5,7 +5,7 @@
 using namespace std;
 
 unsigned int i;
-unsigned char data[255] = {0xf4,0xf3,0x0a,0x0b,0x0c,0x0d};
+unsigned char text[] = {0xf4,0xf3,0xf2,0xf1,0xf0,0x01,0x02,0x03,0x04,0x05,0x06,0x0a,0x0b,0x0c,0x0d};
 int print_text (unsigned int length, void* addr, const char* name){
     unsigned char* charp = (unsigned char*)addr;
     int i;
(END)

 

git status 명령을 내리면 일부만 추가되고 일부는 추가되지 않았음을 확인할 수 있습니다.

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   test.cpp
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   test.cpp
#

 

이제 커밋하려면 git commit 명령을, 맘에 안들어서 스테이징을 취소하려면 git reset 명령을 실행하면 됩니다.

 

추가 팁:

1. git add -p 대신에 git add -e 명령을 내리면 바뀐 내용 전체에 대해서 e 명령을 내린 것처럼 수동으로 편집할 수 있습니다. 일일이 덩어리 단위로 편집하기 귀찮고 난 바뀔 내용 다 알고 있어 하는 경우에는 git add -e 명령을 사용하는게 편할 것 같습니다.

2. 임시로 보관함에 내용을 저장할 수 있는 git stash 명령에도 -p 옵션을 사용해서 이처럼 원하는 부분만 추가할 수 있습니다.

git stash -p

하지만 이 명령을 사용하면 다시 복귀할 때 귀찮아지므로(stash pop 또는 git stash apply 명령으로 보관함의 내용을 다시 적용할 때는 편집중인 내용이 없이 깨끗해야 합니다.) 사용하지 않는 것을 추천합니다. 이 명령을 사용했다가 보관함의 내용을 적용할 때 다음 오류 메시지를 만나는 경우가 있습니다.

error: Your local changes to the following files would be overwritten by merge:
	test.cpp
Please, commit your changes or stash them before you can merge.
Aborting

 

이럴 땐 잠시 언급된 파일(여기서는 test.cpp)을 git add test.cpp 명령으로 추가시키고 git stash pop 또는 git stash apply 명령으로 보관함의 내용을 적용한 다음 git reset 명령으로 git add 명령을 취소하면 잃는 내용 없이 적용할 수 있습니다.

git add <파일>
git stash pop
git reset

 

자세한 부분은 참고 글을 확인하시기 바랍니다. 

 

참고: http://stackoverflow.com/questions/5506339/how-can-i-git-stash-a-specific-file

http://nuclearsquid.com/writings/git-add/