git alias?

  • git을 매일 사용한다면 자연스럽게 git의 온갖 명령어와 옵션에 익숙해지게 된다.
  • 하지만 익숙해지는 것과 별개로, 여전히 입력하기 귀찮은 길고 복잡한 명령들이 있다.
  • alias를 사용하면 귀찮은 명령들을 쉽고 재미있게 축약하여 사용할 수 있다.

일반적으로 널리 쓰이는 git alias

다음은 보통 널리 쓰이는 git alias를 설정한 .gitconfig 파일의 [alias] 섹션이다.

[alias]
    s = status -s
    co = checkout
    ci = commit
    br = branch

위와 같이 설정하면 다음과 같이 사용할 수 있다.

  • git sgit status -s 와 똑같다.
  • git co ...git checkout와 똑같다.

status -scheckout는 매번 입력하기에는 너무 긴 문자열이라 손가락이 피곤하다.

이러한 alias라면 확실히 편리하게 사용할 수 있을 것이다.

그리고 다음 alias는 필수 alias라고 생각하는 것이다. 몇 년 전 예전 회사 동료 H 님에게 받은 이후로 잘 사용하고 있다. git의 log 그래프를 보기 좋게 해주니 이런 종류의 alias를 사용하지 않고 있었다면 반드시 등록해서 사용하도록 하자.

[alias]
l = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%C(bold blue)<%an>%Creset' --abbrev-commit

git alias에서 셸 명령어 사용하기

바로 위에서 사용한 git alias는 편리하고 심플하다.

하지만 셸 명령어를 사용할 수 있다면 좀 더 재미있고 강력하게 사용할 수 있다.

  • 느낌표로 시작하는 alias는 셸 스크립트로 작동하게 된다.

Hello World 부터 찍어보며 시작해보자.

[alias]
    test = "!echo Hello World"

실행하면 Hello World가 출력된다.

$ git test
Hello World

이것으로 다양한 기능을 git 명령어에 덕지덕지 갖다 붙일 수 있다.

현재 브랜치 이름 출력하기

이 alias는 branch 목록에서 *, 즉 현재 브랜치 이름을 가져온다.

[alias]
    b0 = "!git branch | awk '/^\\*/{print $2}'"

구조는 심플하다.

  • 브랜치 목록을 가져온다.
  • awk로 파이핑한다.
    • *로 시작하는 행을 찾는다.
    • 2번째 단어를 출력한다.

현재 브랜치를 백업하는 브랜치 만들기

이번에는 현재 브랜치를 백업하는 브랜치를 만드는 alias를 만들어 보자.

[alias]
    b0 = "!git branch | awk '/^\\*/{print $2}'"
    back = "!git branch backup-`git b0`"

git back을 실행하면, 현재 브랜치 이름 앞에 backup-을 붙인 백업 브랜치를 만들어 준다.

만약 현재 브랜치가 master라면, backup-master 브랜치가 만들어진다.

머지된 브랜치 청소하기

다음 alias를 사용하면 이미 머지를 마쳤지만 귀찮아서 지우지 않고 있었던 브랜치들을 한꺼번에 삭제할 수 있다.

[alias]
    cleanbranch = "!git branch -d $(git branch --merged | grep -v '^\\*\\|\\<master$')"

구조는 다음과 같다.

  • 서브셸
    • 머지된 브랜치 목록을 가져온다.
    • grep으로 파이핑한다.
      • *로 시작하는 라인을 제외한다.
      • 단어경계master로 끝나는 라인을 제외한다.
  • 서브셸에서 얻은 브랜치 목록을 git branch -d로 삭제한다.

fzf로 선택 ui 추가하기

fzf를 사용하면 선택 ui를 추가할 수 있어 더 편리한 alias를 만들 수 있다.

브랜치 선택기

다음 alias를 사용하면 브랜치 목록을 보고 검색어/위-아래 화살표 키를 사용해 브랜치를 선택, checkout 할 수 있다.

[alias]
    ch = "!git checkout $(git branch | fzf)"

gitch

브랜치 이름만 나오게 하면 좀 심심하니까 -vv로 각 브랜치의 가장 마지막 commit의 hash 값 일부와 커밋 메시지 첫번째 라인도 보이도록 하자.

[alias]
    ch = "!git checkout $(git branch -vv | grep -v '^\\*' | fzf | awk '{print $1}')"

구조는 다음과 같다.

  • 서브셸
    • git branch -vv 목록을 가져온다.
    • grep으로 *로 시작하는 라인 제외한다.
    • fzf로 사용자가 선택하게 한다.
    • awk로 사용자가 선택한 라인에서 branch 이름만 가져온다.
  • 서브셸에서 얻은 브랜치 이름으로 git checkout 한다.

add 선택기

git add를 할 때 스테이징할 파일과 아닌 파일이 마구 섞여 있다면 손가락이 바빠진다.

이번에는 fzf를 써서 add 선택기를 만들어 보자.

  • 여러 아이템을 선택하고 싶을 땐 fzf에 -m 옵션을 주면 된다.
    • tab키로 체크/해제가 가능하다.
[alias]
    s = status -s
    a = "!git add $(git status -s | fzf -m | awk '{print $2}')"

git a를 입력하고 tab 키로 추가하고 싶은 파일만 체크한 다음, 엔터 키를 치면 된다.

git-a

일회용 함수 선언으로 복잡한 셸 스크립트 실행하기

git alias 안에서 복잡한 셸 명령어나 스크립트를 실행하는 데에는 한계가 있다.

하지만 함수를 선언해 쓰는 꼼수를 사용하면 이런저런 재미있고 유용한 alias를 만들 수 있다.

sync 도우미 만들기

다음 alias는 $1을 인식하므로, 사용자가 입력한 옵션을 사용할 수 있다.

[alias]
    b0 = "!git branch | awk '/^\\*/{print $2}'"
    sync = "!f() { git fetch $1 && git reset --hard $1/$(git b0); }; f"

예를 들어 다음과 같이 사용하면…

$ git sync upstream

다음 명령어를 사용한 것과 똑같다.

$ git fetch upstream
$ git reset --hard upstream/현재브랜치이름

물론 upstream외에도 다른 remote를 지정해 사용할 수 있다.

branch 도우미 만들기

다음은 내가 즐겨 쓰고 있는 alias이다.

[alias]
    bb = "! # Branch tools. Type 'git bb help' ; \n\
        f() { \n\
            if [ $# = 0 ]; then \
                git checkout $(git branch -vv | grep -v '^\\*' | fzf | awk '{print $1}'); \
            elif [ $1 = 'help' ]; then \
                echo 'git bb           : Select and checkout branch'; \
                echo 'git bb c, clean  : Clean all merged branches'; \
                echo 'git bb d, D      : Delete seleted branches(D: force)'; \
                echo 'git bb l, list   : List branches excluding the current branch'; \
                echo 'git bb m, merged : List merged branches excluding the current and master branches'; \
            elif [ $1 = 'd' -o $1 = 'D' ]; then \
                git branch -$1 $(git bb list | fzf -m); \
            elif [ $1 = 'clean' -o $1 = 'c' ]; then \
                git branch -d $(git bb merged); \
            elif [ $1 = 'list' -o $1 = 'l' ]; then \
                git branch | grep -v '^\\*'; \
            elif [ $1 = 'merged' -o $1 = 'm' ]; then \
                git branch --merged | grep -v '^\\*\\|\\<master$'; \
            else \
                git bb help; \
            fi; \
        }; f"

복잡해 보이지만 작은 기능들을 여럿 모아 놓았을 뿐이다.

  • git bb : 브랜치 선택기
  • git bb help : 도움말
명령어 기능 다른 입력 방법
git bb 브랜치 선택기  
git bb c 머지된 브랜치들 한꺼번에 삭제 git bb clean
git bb d 브랜치 선택, 삭제  
git bb D 브랜치 선택, 강제로 삭제  
git bb l 브랜치 목록 보기(현재 브랜치 제외) git bb list
git bb m 머지된 브랜치 목록 보기(현재/master 브랜치 제외) git bb merged
git bb help 도움말 보기 git bb 아무거나

옵션이 많아서 헷갈리지 않도록 도움말도 추가해 놓았다.

$ git bb help
git bb           : Select and checkout branch
git bb c, clean  : Clean all merged branches
git bb d, D      : Delete seleted branches(D: force)
git bb l, list   : List branches excluding the current branch
git bb m, merged : List merged branches excluding the current and master branches

각 옵션 앞에 -, --를 붙일까 말까 고민했지만 그냥 이 정도가 편한 것 같아서 더 건드리지는 않았다.

merged branch clean 도우미 만들기

위의 git bb clean은 다음 명령을 실행한다.

git branch -d $(git branch --merged | grep -v '^\*\|\<master$')

이 기능은 잘 작동한다.

그러나 여러 대의 컴퓨터로 작업을 하다 보면 같은 이름을 가진 브랜치라도 헤드의 값이 달라서 내가 보기에는 머지된 것 같은데 --merged로는 나오지 않는 브랜치가 발생할 수 있다.

사실 이건 내가 착각한 것일 뿐 --merged의 오작동이 아니다.

그러나 어쨌든 불편한 건 불편한 것이므로 다음과 같은 alias를 만들어 보았다.

blist = "!git branch | grep -v '^\\*'"
blist-merged = "!git branch --merged | grep -v '^\\*\\|\\<master$'"
bclean = "! # Search and delete merged branches;\n\
    git branch -d $(git blist-merged); \
    for branch in $(git blist) ; do \
        echo \"\nSearch :\\033[32m $branch \\033[0m\"; \
        if [ $(git l | grep $branch -c) -gt 0 ]; then \
            git l | egrep \"Merge.*$branch\" -C 2; \
            read -p \"Delete $branch? [y|n] \" -r; \
            REPLY=${REPLY:-"n"}; \
            if [ $REPLY = \"y\" ]; then \
                git branch -D $branch; \
                echo \"\\033[32m$branch \\033[0mhas been\\033[31m deleted\\033[0m.\n\"; \
            fi; \
        fi; \
    done \n\
"

이 alias는 밤에 잠을 자다가 떠오른 아이디어였는데, 낮에도 이런 괜찮은 생각이 떠오르면 좋겠다.

아무튼 이 bcleangit bb clean으로 등록해 두고 실행하면 다음과 같이 돌아간다.

gitbbc

  • 이 alias를 실행하면 일단 다음 명령어를 실행해 merged branch를 일괄적으로 삭제한다.
git branch -d $(git branch --merged | grep -v '^\*\|\<master$')
  • 그리고 브랜치 목록을 조회한 다음, for 루프를 돈다.
    • git log graph에서 Merge.*브랜치이름의 문자열을 찾아 보여준다.
    • 위에서 보여준 해당 브랜치 이름을 삭제할 것인지를 물어본다.

어느 정도는 수작업으로 하던 것이었기에 이렇게 만들어 놓으니 편리하고 재미있다.

git bb c, git bb clean, git bclean 모두 잘 동작한다.

검색 결과의 윗줄/아랫줄을 보여주는 라인 수를 조절하고 싶다면 다음 구문의 -C 2에서 2를 변경하면 된다.

git l | egrep \"Merge.*$branch\" -C 2; \

강력한 branch 삭제 도구 만들기

사실 git의 브랜치는 .git/refs/ 에 들어 있는 파일 이름일 뿐이다.

좀 위험하지만 강력하게 삭제하는 작업을 편하게 하고 싶어 다음과 같이 작업하였다.

branch-clean = "!# Search and delete merged branches.;\n\
    curr_hash=`git show -s | head -1 | cut -d ' ' -f2`; \
    for branch in `find .git/refs/heads -type f | egrep -v '/(master|develop)$'` ; do \
        hash=`cat $branch`; \
        if [ $hash = $curr_hash ]; then \
            continue; \
        fi; \
        git ll | egrep $hash -C 1; \
        read -p \"Delete $branch? [y|n] \" -r; \
        REPLY=${REPLY:-"n"}; \
        if [ $REPLY = \"y\" ]; then \
            rm $branch; \
            echo \"\\033[32m$branch \\033[0mhas been\\033[31m deleted\\033[0m.\n\"; \
        fi; \
    done"

이 알리아스는 바로 윗절의 merged branch clean 도구를 개선한 것이다. 겉보기엔 똑같이 작동하지만 .git의 파일을 삭제하므로 주의해서 사용해야 한다.

이미 만들어져 있는 tag가 현재 HEAD를 지시하도록 하기

tag-refresh = "!# Re reference tag.;\n \
    f() { \
        _height=$(stty size | awk '{print $1}');\
        tag_name=`git tag | fzf --preview=\"git l {1} | head -n $_height\" `; \
        echo $tag_name; \
        git tag -d $tag_name; \
        git tag $tag_name; \
    }; f"

wip처럼 자주 옮기는 태그를 관리할 때 쓴다. 꽤 자주 쓰는 알리아스.

fzf preview를 사용해 미리보기 기능 추가하기

fzf의 preview 옵션을 사용하면 뭔가 일일이 확인하고 선택하는 과정을 단축할 수 있다.

브랜치 선택기에 로그 그래프 미리보기 기능 추가하기

ch를 다음과 같이 수정해보자.

[alias]
    ch = "!git checkout $(git bselect)"
    bselect = "! # select branch with preview; \n\
        f() { \
            _height=$(stty size | awk '{print $1}');\
            git branch | egrep -v '^\\*' | fzf --preview \"git l {1} | head -n $_height\"; \
        }; f"
  • 하나의 알리아스로 만들어도 되겠지만 bselect는 여러모로 쓸모가 있을 것 같아 따로 만들었다.
  • 터미널 높이 길이 값으로 $LINES를 쓰면 더 깔끔했겠지만, git alias 설정 문자열 내에서는 $LINES값이 제대로 출력되지 않아 stty size를 사용했다.

gitch

add 파일 선택기에 미리보기 기능 추가하기

이번엔 a를 다음과 같이 수정하자.

[alias]
    a = "! # add files with fzf preview diffs; \n\
        f() { \
            _height=$(stty size | awk '{print $1}');\
            git s | fzf -m --preview \"git diff {2} | head -n $_height | pygmentize\" | awk '{print $2}' | xargs git add; \
        }; f"
  • 코드 미리보기에 색깔을 칠해주기 위해 Pygment를 사용했다.
    • 설치는 심플하게 pip3 install Pygments

gita_

파일 이름 옆에 변경 라인 숫자 보여주기

이 글을 작성하고 며칠이 지났다.

각 파일별 변경된 라인 숫자를 보여주면 좀 더 편할 것 같아서 다음과 같이 수정해 보았다.

a = "!git diff-select | xargs git add"
diff-select = "! # add files with fzf preview diffs; \n\
    f() { \
        _height=$(stty size | awk '{print $1}');\
        git diff-info \
        | fzf -m --header \"$(git diff --shortstat)\" --preview \
            \"if [[ {1} == '??' ]]; then cat {3}; else git diff {3}; fi \
            | head -n $_height \
            | pygmentize\" \
        | awk '{print $3}'; \
    }; f"
diff-info = "! # get diff info;\n\
    fileA=/tmp/git-s-$(uuidgen); \
    fileB=/tmp/git-diff-$(uuidgen); \
    git s | awk '{print $2,$1}' > $fileA; \
    git diff --numstat | awk '{print $3,$1,$2}' > $fileB; \
    join -t' ' -a 1 $fileA $fileB | awk '{print $2, \"(+\"$3 \",-\"$4\")\", $1}' | sed 's/(+,-)/./' | column -t -s' ' ; \
    rm -f $fileA $fileB; \
"

실행하면 다음과 같이 아래쪽에 변경에 대한 전체 정보가 나오고, 각 파일별로 추가/삭제 정보가 나온다.

image

헷갈릴 때 사용하는 alias 추가하기

이제 꽤 많은 alias가 추가되었다.

매일 쓰는 alias라면 괜찮겠지만 자주 안 쓰는 alias는 잊을 수도 있다.

물론 .gitconfig 파일을 열어 보면 되지만, 그것도 귀찮으니 그냥 모든 git alias를 조회하는 alias를 추가하도록 하자.

[alias]
    alias = "!git config --list | egrep '^alias.+'"

이제 git alias를 입력하면 모든 git alias를 볼 수 있다.

$ git alias
alias.l=log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%C(bold blue)<%an>%Creset' --abbrev-commit
alias.lh=!git l | head -n 25
alias.s=status -s
alias.co=checkout
alias.ci=commit
alias.bb=! # Branch tools. Type 'git bb help' ; 
alias.b0=!git branch | awk '/^\*/{print $2}'
alias.back=!git branch backup-`git b0`
alias.assume=update-index --assume-unchanged
alias.unassume=update-index --no-assume-unchanged
alias.assumed=!git ls-files -v | grep ^h | cut -c 3-
alias.ch=!git checkout $(git branch -vv | grep -v '^\*' | fzf | awk '{print $1}')
alias.a=!git add $(git status -s | fzf -m | awk '{print $2}')
alias.cleanbranch=!git branch -d $(git branch --merged | grep -v '^\*\|\<master$')
alias.deletebranch=!git branch -d $(git branch | grep -v '^\*\|\<master$' | fzf -m)
alias.alias=!git config --list | egrep '^alias.+'
alias.diffs=!git diff $(git status -s | egrep '^\s*M' | awk '{print $2}' | fzf -m)
alias.sync=!f() { git fetch $1 && git reset --hard $1/$(git b0); }; f
  • bb 알리아스를 제일 위쪽에 주석을 추가하고 \n을 붙인 이유가 이것 때문이다.

그런데 이대로는 가독성이 별로 좋지 못하므로, sed를 써서 앞의 alias 명에 색을 칠해 보자.

[alias]
    alias = "!git config --list | egrep '^alias.+' | sed -e 's/^alias\\.//' | sed -e 's/^[^=]*=/\\'$'\\033[31m&\\033[(B\\033[m/'"

image

alias 설명서를 보기 좋게 다듬어 보자

  • column 명령어를 사용하면 터미널에 출력되는 문자열을 표와 같이 정렬할 수 있다.
alias = "!# Prints all aliases.;\n\
    git config --list \
    | egrep '^alias.+' \
    | sed -e 's/^alias\\.//' \
    | sed -e 's/^[^=]*=/\\'$'\\033[31m&\\033[(B\\033[m/' \
    | column -t -s'=' \
    | sed 's/!#* *//; s/;$//' \
    | cut -c1-85"
  • 위의 alias 명령어를 사용하면 !# ...;\n\... 영역에 설명을 쓸 수 있다.

다음은 나중에 사용하기 쉽도록 설명을 추가한 것이다.

구글 번역기를 열심히 돌리며 영작했다.

image

나의 git config

다음 링크에서 나의 최신 .gitconifg 파일을 볼 수 있다.

함께 읽기

  • [[/cmd/grep]]