Chào tất cả các bạn, mình là Siêu Lười đây. Có khi nào trong quá trình làm việc, bạn đã phải làm những công việc lặp đi lặp lại như tạo commit, push message lên mà phải trải qua N step như nghĩ tên message như thế nào, rồi viết câu lệnh thật dài. Chắc chắn là có rồi đúng không, lập trình mà không commit & push như bánh mì không có bate vậy, (^.<)

Đùa vui vậy thôi, chứ hôm nay Siêu Lười sẽ chia sẻ cho mọi người cách mình "Tự động hóa quy trình tạo commit message, cho đến việc push code lên github" chỉ với 1 câu thần chú duy nhất. Let's Go.

I. Đặt vấn đề

Trong các dự án Siêu Lười làm đều gặp phải vấn đề tương tự lặp đi lặp lại là: mỗi một task mới cần implement thì sẽ tạo branch dựa vào ID của issue (task) được quản lý trên Github (hoặc jira, backlog,...) theo 1 format cụ thể. Xong rồi lúc tạo commit sẽ phải commit dựa vào title của issue đó, xong rồi push code lên. Để thực hiện điều đó thì cần 1 loạt thao tác như:
1, Tìm lại issue. (công việc lãng phí thời gian nhất)
2, Copy title.
3, Gõ lệnh tạo commit. (để suy nghĩ tên commit đầu tiên cũng rất mệt mỏi)
4, Push commit lên repo.

Điều đó thật là tốn thời gian đối với Siêu Lười như mình (^_^)".

Chính vì phương châm thà lười thêm 1 tí để còn làm việc khác chứ không để tốn thời gian thêm 1 tí cho việc nhàm chán lặp đi lặp lại, nên mình đã quyết định tạo ra 1 script thần thánh, có thể giúp mình tiết rút ngắn thời gian, không phải thao tác nhiều lần lặp đi lặp lại như vậy nữa.

II. Chuẩn bị

Trước khi đi vào chi tiết thì chúng ta chủng bị trước 1 số thứ:

  1. Tại vì có sử dụng API của Github để lấy thông tin issue, nên chúng ta hãy tạo 1 token để truy cập đến repository của mình, nếu bạn chưa biết thì có thể tham khảo tại đây để tạo token github. (Đừng quên giới hạn quyền cho token nhé)
  2. Cài đặt Oh My Zsh (optional), nếu bạn nào không thích thì có thể dùng mặc định của ubuntu. Tuy nhiên, mình suggest dùng cái này vì nó có nhiều lệnh giúp quá trình sử dụng git của bạn được nhanh và tiện lợi hơn.
  3. Tạo file shell script để chứa nội dung code:
$ cd ~
$ touch .config_git_push.sh

4. Mở file .config_git_push.sh  và code thôi nào !!!

III. Script thần thánh

1. Define biến

# Set biến môi trường
export GITHUB_TOKEN="YOUR_GITHUB_TOKEN" # thay thế token bạn tạo vào đây
export BRANCH_PREFIX="i" # tùy theo dự án sẽ có 1 format cho tên branch khác nhau, thay đổi cho phù hợp với bạn nhé, ở đây mình để `i` có nghĩa là issue.

# Alias để tạo branch và commit : git push with commit
alias gpwc='git_push_with_commit_issue'

# define biến
error_code=0
remote_url="" 
commit_message=""
current_branch=""
origin_name=""
issue_title=""

Ở trên mình tạo 1 alias gpwc để mỗi lần tạo commit với push code, chúng ta sẽ gọi câu thần chú này.

2. Hàm  git_push_with_commit_issue

# Hàm thực hiện tạo branch và commit
git_push_with_commit_issue() {
    get_current_branch
    commit_message="$1"
    
    # Kiểm tra nếu không có tham số được truyền vào
    if [ -z "$commit_message" ]; then
        create_commit_message_from_issue_github

        if [[ $error_code -eq -1 ]]; then
            return
        fi
    fi

    echo "commit message: $commit_message"

    git commit -m "$commit_message"

    # Push branch lên origin
    git push origin $current_branch
}

Trong hàm chính này chúng ta sẽ thực hiện công việc chính là commitpush code lên branch.

Để tạo commit thì chúng ta phải có commit message. Ở đây có 2 trường hợp:

2.1 Commit message do người dùng tự nhập:  sẽ được lấy thông qua câu lệnh commit_message="$1" . $1 là tham số đầu tiên khi gõ lệnh gpwc.

#Ví dụ:
$ gpwc "new message"

#=> tương đương với câu lệnh:
$ git commit -m "new message"
$ git push origin $current_branch

2.2 Commit message lần đầu tiên, thì sẽ lấy từ tên issue, sẽ được lấy qua hàm create_commit_message_from_issue_github. Đối với trường hợp này chúng ta chỉ dùng gpwc là mọi chuyện xong xuôi, không cần tham số gì.

Và để push được thì chúng ta phải có branch đúng không nào, việc này vô cùng đơn giản thông qua hàm get_current_branch

# Hàm lấy tên branch hiện tại
get_current_branch() {
    current_branch=$(git symbolic-ref --short HEAD)
    echo "branch_name: $current_branch"
}

git symbolic-ref: Đây là một trong những câu lệnh trong Git được sử dụng để thao tác với các tham chiếu tượng trưng (symbolic references) trong Git.

  1. --short: Đây là một tùy chọn của câu lệnh git symbolic-ref. Khi sử dụng tùy chọn này, câu lệnh chỉ hiển thị phần tên ngắn gọn của tham chiếu mà nó trỏ tới. Trong trường hợp này, tham chiếu mà chúng ta quan tâm là HEAD.
  2. HEAD: Đây là một tham chiếu tượng trưng trong Git. Thông thường, HEAD trỏ tới nhánh hiện tại, nhưng cũng có thể trỏ tới một commit cụ thể hoặc một tham chiếu khác.

3. Hàm  create_commit_message_from_issue_github

Đây là hàm cốt lõi để chúng ta có thể tiết kiệm thời gian của mình.

create_commit_message_from_issue_github() {
    get_remote_url
    get_issue_id
    get_origin_name
    get_repo_name

    pattern1="^${BRANCH_PREFIX}[0-9]+$"
    pattern2="^${BRANCH_PREFIX}[0-9]+[_-]+[a-z0-9_-]*$"
    pattern3="[-_]$"
    # Kiểm tra xem branch có đúng định dạng không
    if [[ ! ($current_branch =~ $pattern1 || ($current_branch =~ $pattern2 && ! $current_branch =~ $pattern3)) ]]; then
        echo "Tên branch không đúng định dạng (ví dụ: i10 hoặc i10_any_character hoặc i10-any-character)"
        error_code=-1

        return
    fi



    # Sử dụng GitHub API để lấy title của issue
    api_url="https://api.github.com/repos/$origin_name/$repo_name/issues/$issue_id"

    # yêu cầu cài đặt jq: sudo apt-get install jq
    issue_title=$(curl -s -H "Authorization: token $GITHUB_TOKEN" $api_url | jq -r '.title')

    # Kiểm tra xem đã có title hay chưa
    if [ -z "$issue_title" ]; then
        echo "Không tìm thấy title cho issue $issue_id. Hãy cập nhật thông tin issue."
        error_code=-1

        return
    fi

    # Tạo commit với định dạng mong muốn
    commit_message="${BRANCH_PREFIX}$issue_id: $issue_title"
}

Trước tiên, hàm sẽ load tất cả biến cần dùng thông qua các hàm sau:

3.1 Hàm get_issue_id


# Hàm lấy id của issue từ tên branch
get_issue_id() {
    issue_id=$(echo $current_branch | sed -n -E 's/i([0-9]+).*/\1/p')
    echo "issue_id: $issue_id"
}

issue_id=$(echo $current_branch | sed -n -E 's/i([0-9]+).*/\1/p'): Trong câu lệnh này:

  • echo $current_branch: In ra tên của branch hiện tại.
  • sed -n -E 's/i([0-9]+).*/\1/p': Đây là một lệnh sed dùng để thực hiện biến đổi trên chuỗi văn bản đầu vào (tên của branch hiện tại). Giải thích của lệnh sed như sau:
  • -n: Không in ra dòng nào mặc định.
  • -E: Sử dụng cú pháp biểu thức chính quy mở rộng (ERE).
  • 's/i([0-9]+).*/\1/p': Lệnh s này thực hiện tìm kiếm một chuỗi bắt đầu bằng "i" theo sau là một chuỗi các chữ số từ 0 đến 9 và bất kỳ ký tự nào khác (([0-9]+)), và sau đó bỏ qua bất kỳ ký tự nào khác (.*). Phần được nằm trong cặp ngoặc tròn ([0-9]+) sẽ được nhóm lại và đánh số ở vị trí đầu tiên, do đó \1 sẽ là kết quả là ID của vấn đề. Cuối cùng, tùy chọn /p đảm bảo rằng kết quả sẽ chỉ được in ra nếu có sự khớp.
  • issue_id=$(...): Kết quả từ lệnh sed sẽ được gán vào biến issue_id.
  • echo "issue_id: $issue_id": In ra màn hình thông tin về ID của vấn đề mà hàm đã lấy được.

3.2 Hàm get_remote_url


# Hàm lấy remote url
get_remote_url() {
    remote_url=$(git config --get remote.origin.url)
    echo "remote_url: $remote_url"
}

Hàm này đơn giản chỉ là in ra config remote origin url để gán vào biến remote_url

3.3 Hàm get_repo_name

# Hàm lấy repositoty name
get_repo_name() {
    repo_name=$(echo $remote_url | awk -F/ '{print $NF}' | sed 's/\.git$//')
    echo "repo_name: $repo_name"
}

repo_name=$(echo $remote_url | awk -F/ '{print $NF}' | sed 's/\.git$//'): Trong câu lệnh này:

  • echo $remote_url: In ra URL của remote repository.
  • awk -F/ '{print $NF}': Sử dụng awk để tách URL thành các phần dựa trên dấu /, và {print $NF} sẽ in ra phần cuối cùng, tức là tên của repository.
  • sed 's/\.git$//': Lệnh sed này sẽ loại bỏ phần mở rộng .git từ tên của repository nếu có.
  • repo_name=$(...): Kết quả từ các lệnh trên sẽ được gán vào biến repo_name.
  • echo "repo_name: $repo_name": In ra màn hình thông tin về tên của repository mà hàm đã lấy được.

3.4 Hàm get_origin_name

# Hàm lấy origin name
get_origin_name() {
    # Kiểm tra xem URL có chứa "://" không
    if [[ $remote_url =~ "://" ]]; then
        # Nếu có, sử dụng awk để lấy phần giữa "://" và "/"
        origin_name=$(echo "$remote_url" | awk -F'://' '{print $2}' | cut -d'/' -f2)
    else
        # Nếu không, sử dụng awk để lấy phần sau ":", sau đó cắt bỏ phần sau "/"
        origin_name=$(echo $remote_url | awk -F':' '{print $2}' | cut -d'/' -f1)
    fi
    echo "origin_name: $origin_name"
}

if [[ $remote_url =~ "://" ]]; then: Đây là một câu lệnh điều kiện, kiểm tra xem URL của remote repository có chứa chuỗi "://" không. Nói đúng hơn là kiểm tra xem có đang dùng https hay là ssh.

origin_name=$(echo "$remote_url" | awk -F'://' '{print $2}' | cut -d'/' -f2): Trong trường hợp URL chứa "://", câu lệnh này thực hiện các bước sau:

  • echo "$remote_url": In ra URL của remote repository.
  • awk -F'://' '{print $2}': Sử dụng awk để tách URL thành các phần dựa trên chuỗi "://", và {print $2} sẽ in ra phần thứ hai của kết quả sau khi tách, tức là phần sau chuỗi "://".
  • cut -d'/' -f2: Sử dụng cut để tách phần đã lấy được từ awk thành các phần dựa trên dấu /, và -f2 chỉ định lấy phần thứ hai sau khi tách.
  • origin_name=$(...): Kết quả từ các lệnh trên sẽ được gán vào biến origin_name.

else: Trường hợp không chứa "://".

origin_name=$(echo $remote_url | awk -F':' '{print $2}' | cut -d'/' -f1): Trong trường hợp này, câu lệnh sẽ tìm kiếm phần sau dấu :, và sử dụng dấu / để cắt bỏ phần sau. Cụ thể:

  • echo $remote_url: In ra URL của remote repository.
  • awk -F':' '{print $2}': Sử dụng awk để tách URL thành các phần dựa trên dấu :, và {print $2} sẽ in ra phần thứ hai của kết quả sau khi tách, tức là phần sau dấu :.
  • cut -d'/' -f1: Sử dụng cut để tách phần đã lấy được từ awk thành các phần dựa trên dấu /, và -f1 chỉ định lấy phần đầu tiên sau khi tách.
  • origin_name=$(...): Kết quả từ các lệnh trên sẽ được gán vào biến origin_name.
  • echo "origin_name: $origin_name": In ra màn hình thông tin origin name lấy được

3.5 Kiểm tra branch name có hợp lệ hay không (Tùy theo dự án của bạn mà hãy custom lại cho đúng nhé)

    pattern1="^${BRANCH_PREFIX}[0-9]+$"
    pattern2="^${BRANCH_PREFIX}[0-9]+[_-]+[a-z0-9_-]*$"
    pattern3="[-_]$"
    # Kiểm tra xem branch có đúng định dạng không
    if [[ ! ($current_branch =~ $pattern1 || ($current_branch =~ $pattern2 && ! $current_branch =~ $pattern3)) ]]; then
        echo "Tên branch không đúng định dạng (ví dụ: i10 hoặc i10_any_character hoặc i10-any-character)"
        error_code=-1

        return
    fi
  • pattern1="^${BRANCH_PREFIX}[0-9]+$": Định nghĩa biến pattern1 là một biểu thức chính quy (regular expression) dùng để kiểm tra xem tên branch có đúng định dạng hay không. Biểu thức này yêu cầu tên branch phải bắt đầu bằng giá trị của biến BRANCH_PREFIX (biến này được giả định đã được định nghĩa ở đâu đó trong script) và theo sau là một chuỗi các chữ số từ 0 đến 9.
  • pattern2="^${BRANCH_PREFIX}[0-9]+[_-]+[a-z0-9_-]*$": Định nghĩa biến pattern2 là một biểu thức chính quy khác, cũng dùng để kiểm tra tên branch. Biểu thức này yêu cầu tên branch bắt đầu bằng giá trị của biến BRANCH_PREFIX, theo sau là một chuỗi các chữ số từ 0 đến 9, sau đó là một hoặc nhiều dấu gạch dưới (_) hoặc dấu gạch ngang (-), và cuối cùng là một chuỗi bất kỳ gồm các ký tự chữ thường, chữ số, dấu gạch dưới hoặc dấu gạch ngang.
  • pattern3="[-_]$": Định nghĩa biến pattern3 là một biểu thức chính quy khác, được sử dụng để kiểm tra nếu tên branch kết thúc bằng dấu gạch dưới hoặc dấu gạch ngang.

3.6 Dùng GitHub API để lấy title issue tạo commit message

# Sử dụng GitHub API để lấy title của issue
    api_url="https://api.github.com/repos/$origin_name/$repo_name/issues/$issue_id"

    # yêu cầu cài đặt jq: sudo apt-get install jq
    issue_title=$(curl -s -H "Authorization: token $GITHUB_TOKEN" $api_url | jq -r '.title')

    # Kiểm tra xem đã có title hay chưa
    if [ -z "$issue_title" ] || [ "$issue_title" = "null" ]; then
        echo "Không tìm thấy title cho issue $issue_id. Hãy cập nhật thông tin issue."
        error_code=-1

        return
    fi

    # Tạo commit với định dạng mong muốn
    commit_message="${BRANCH_PREFIX}$issue_id: $issue_title"
  • api_url="https://api.github.com/repos/$origin_name/$repo_name/issues/$issue_id": Xây dựng URL cho API của GitHub, sử dụng các biến như origin_name, repo_name, và issue_id để xác định repository và issue cụ thể cần truy cập.
  • issue_title=$(curl -s -H "Authorization: token $GITHUB_TOKEN" $api_url | jq -r '.title'): Thực hiện một yêu cầu HTTP GET đến API URL đã được xây dựng, với phần đầu gửi theo yêu cầu của GitHub thông qua header "Authorization" sử dụng giá trị token được cung cấp bởi biến GITHUB_TOKEN. Kết quả trả về từ API là một JSON object, và jq -r '.title' được sử dụng để lấy ra giá trị của thuộc tính "title" từ JSON đó. Kết quả này sau đó được gán vào biến issue_title.
  • if [ -z "$issue_title" ] || [ "$issue_title" = "null" ]; then: Kiểm tra xem biến issue_title có rỗng không (tức là không tìm thấy title cho issue).
  • echo "Không tìm thấy title cho issue $issue_id. Hãy cập nhật thông tin issue.": In ra thông báo lỗi khi không tìm thấy title cho issue với ID là $issue_id.
  • error_code=-1: Gán giá trị -1 cho biến error_code để đánh dấu lỗi.
  • commit_message="${BRANCH_PREFIX}$issue_id: $issue_title": Tạo nội dung cho commit với định dạng mong muốn, bao gồm BRANCH_PREFIX, issue_id, và issue_title. Đây là phần thông điệp mô tả nội dung của commit.

Wào! vậy là script thần thánh của chúng ta đã hoàn thành, bây giờ chúng ta chỉ cần 1 bước cuối cùng nữa để có thể tha hồ lười biến thêm rồ, kakaka.

IV. Thêm script vào bash và test kết quả

1. Thêm script

các bạn mở file config của zsh .zshrc và thêm đường dẫn file vừa tạo vào

$ vi ~/.zshrc

# Thêm dòng này vào trong file
source ~/.config_git_push.sh

Sau khi thêm xong, các bạn chạy lệnh source ~/.zshrc để load lại zsh nhé.

$ source ~/.zshrc

2. Kiểm tra kết quả

Mình sẽ tạo 1 issue trong git repository của mình

Giờ thì thao tác git để test kết quả nào:

# Bước 1: Tạo branch đúng format của dự án `VD: i1-welcome`
$ gco -b i1-welcome # lệnh `gco` tương đương với `git checkout` các bạn có thể tìm từ khóa `zsh git commands` để tham khảo thêm

# Bước 2: Tạo file change và add vào git
$ echo "welcome to Sieu Luoi blog" > welcome.txt
$ git add welcome.txt
$ gst # git status

#Bước 3: triệu hồi script thần thánh `gpwc`
$ gpwc

Kết quả như bạn thấy , chúng ta có thể tạo commit message và push lên git repository chỉ bằng 1 thần chú duy nhất.

Thử test thêm 1 số kết quả nào:

  • Issue không tồn tại
  • Branch không đúng định dạng
  • tạo commit với custom message nào

V. Kết Luận

Trong thời đại CNTT phát triển như vũ bão hiện nay, việc tối ưu hóa thời gian cũng là 1 thế mạnh cho các bạn trong tương lai. Hãy loại bỏ những bước dư thừa trong cuộc sống và thay vào đó hãy tận dụng khoảng thời gian đó một cách ý nghĩa hơn. Hẹn gặp lại mọi người trong các kỳ tiếp theo. From Siêu Lười with love.

Full source code: tại đây