প্রি কমিট হুক — পরিচ্ছন্ন কোডবেজ

ভূমিকা

ধরুন আপনি একটি প্রজেক্টে কাজ করছেন এবং মাত্রই একটি ফিচার বা বাগ ফিক্স এর কাজ শেষ করেছেন। এখন আপনার করা কোডগুলো কমিট করে অন্য ডেভেলপারদের জন্য পুশ করবেন। কিন্তু আপনি কি কিছু ভুলে যাচ্ছেন? হয়তো ডিবাগের জন্য কয়েক জায়গায় প্রিন্ট ফাংশন লিখেছিলেন, কিংবা আপনার লিন্টিং রুলসে কিছু ওয়ার্নিং দিচ্ছে, অথবা আপনার হয়তো ইউনিট টেস্ট স্যুট রেডি আছে, কমিট এর আগে দেখে নিতে চান সব টেস্ট আগের মতো ঠিকঠাক কাজ করছে কিনা।

আপনার কি কখনো প্রশ্ন জেগেছে প্রতি কমিট এর আগে এইসব কাজ অটোমেট করার জন্য কোনো পদ্ধতি বা স্ক্রিপ্ট আছে কিনা যাতে করে বার বার এসব খুঁটিনাটি বিষয় ভুলে যেতে না হয়? সেই স্ক্রিপ্টই হয়তো আমাদের হয়ে এসব চেক করে দিবে।

আমারও মনে হয়েছে। অনেকবার।

এবং আন্দাজ করুন তো? ঠিক, আমাদের নতুন করে চাকা আবিষ্কারের দরকার নেই। গিট নিজেই এতো দারুণ আর শক্তিশালী যে, এমন কিছু ইতোমধ্যে নেই শুনলেই বরং অবাক লাগতো।

এই আর্টিকেলের ইংরেজি ভার্সন পড়তে এখানে ক্লিক করুন।

গিট হুকস

গিট হুকস কী চলুন Atlassian এর এই সংজ্ঞায় চোখ বুলিয়ে নেই

Git hooks are scripts that run automatically every time a particular event occurs in a Git repository. They let you customize Git’s internal behavior and trigger customizable actions at key points in the development life cycle.

সুন্দর বর্ণনা। হুক হচ্ছে এমন কিছু স্ক্রিপ্ট যা ডেভেলপমেন্ট এর বিভিন্ন ধাপ যেমন কমিট এর আগে-পরে, রিবেজ কিংবা মার্জ এর আগে পরে রান হবে। সেটা ক্লায়েন্ট বা সার্ভার দুই খানেই হতে পারে। এই স্ক্রিপ্টে কী কী হবে সেটা আমরা নিজেদের মতো ঠিক করে দিতে পারি। বিস্তারিত জানতে এখানে আর এখানে চোখ বুলিয়ে নিতে পারেন।

এই আর্টিকেলে আমাদের উদ্দেশ্য ক্লায়েন্ট সাইডের একটি হুক, সুনির্দিষ্ট করে প্রি কমিট হুক কে আত্মস্থ করা।

প্রি কমিট হুক

প্রি কমিট হুক হচ্ছে এমন একটা স্ক্রিপ্ট যা প্রতি কমিট এর আগে রান হবে। স্ক্রিপ্ট এর ভেতরের সব ঠিকঠাক থাকলে কমিট হবে, কোনো সমস্যা থাকলে কমিট না হয়ে সমস্যাটা দেখিয়ে আগে সমাধান করতে বলবে।

আপনি কি কখনো প্রজেক্টের এর hooks/ফোল্ডারে উঁকি দিয়েছিলেন? এটা .git ফোল্ডারে থাকে। (.git/কিন্ত হিডেন থাকে। প্রথমে একে আনহাইড করে নিতে হবে)

ওখানে চোখ বুলালে দেখতে পারবেন অনেকগুলো হুক রয়েছে। যদিও এদের সবার শেষে .sample এক্সটেনশন দেয়া, কাজেই এরা ঘুমন্ত অবস্থায় আছে বলা যায়।

ঘুম ভাঙাতে pre-commit.sample ফাইলটিকে রিনেম করে .sample সরিয়ে দিয়ে কোনো টেক্সট এডিটরে ওপেন করুন-

আপনি চাইলে লেখাগুলোয় চোখ বুলিয়ে নিতে পারেন অথবা সব মুছে দিতে পারেন, যেহেতু আমরা আমাদের নিজেদের মতো করে স্ক্রিপ্ট লিখবো

স্ক্রিপ্টটি আমরা ব্যাশ, পাইথন রুবি ইত্যাদি অনেক ভাষায় লিখতে পারি। তবে এখানে আমরা ব্যাশ ব্যবহার করবো।

কাজেই ফাইলের একেবারে শুরুতে #!/bin/bash লিখি যাতে বোঝা যায় এটা একটা ব্যাশ স্ক্রিপ্ট।

এখন, স্ক্রিপ্টে আমরা আসলে কী করতে চাই?

প্রতিটি কমিটের আগে আমরা নিচের কাজগুলো করতে চাই-

১. কোডগুলো ফরম্যাট করবো

২. dart analyze রান করে কোনো ওয়ার্নিং বা ইস্যু আছে কিনা চেক করতে চাই

৩. টেস্ট কেসগুলো রান করতে চাই

কিন্তু শুরুতে আমাদের একটা ব্যাপার বুঝে নিতে হবে। আমরা কি প্রজেক্ট এর ভেতরে থাকা সব ফাইলে এই চেকগুলো করতে চাই? এটা ভালো বুদ্ধি হবে না, কারণ প্রায় সব প্রজেক্টেই এমন অনেক ফাইল থাকে যেগুলোতে আসলে ডেভেলপমেন্ট রিলেটেড কোড থাকে না, যেমন এসেটস, ডিপেন্ডেন্সি ইত্যাদির ফাইল। আমাদের মূল কোডগুলো সব হয়তো একটা ফোল্ডারের ভেতরে থাকে। ফ্লাটারের ক্ষেত্রে সেটা lib/

আবার, lib/ এর ভেতরের সব ফাইলে কি আমরা চেক করতে চাই?

নাহ! আমাদের হয়তো ১০০ ফাইল আছে, কিন্তু আমরা চেঞ্জ করেছি হয়তো ১০ টি ফাইলে, আর তার মধ্যে হয়তো ৫ টি ফাইল আমাদের কমিট এর জন্য স্টেজিং এরিয়া তে এনেছি ।

মূলকথা হলো, আমরা শুধু স্টেজিং এরিয়াতে আনা ফাইলগুলোর উপর এই স্ক্রিপ্ট রান করতে চাই।

এখন আমরা format, analyze আর test কমান্ডগুলো স্টেজিং এরিয়ার সব ফাইলে রান করবো। কোডের পুনরাবৃত্তি কমানোর জন্য আমরা একটা ফাংশন লিখে ফেলি

# Function to check staged files
check_staged_files() {
local command="$1"

# Get a list of staged files within the lib/ or test/ directory
local staged_files
staged_files=$(git diff --name-only --cached | grep -E '^(lib/|test/)')
# Run the specified command on each staged file
for file in $staged_files; do
if ! $command "$file"; then
exit 1 # Exit the script if there's an issue
fi
done
}

ওয়াও! কী হচ্ছে এখানে?

ধীরে, ব্যাপারগুলো আসলে সহজই। চলুন ধাপে ধাপে একটু দেখে নেই।

প্রথমে, check_staged_files() নামে একটা ফাংশন তৈরি করেছি যা আর্গুমেন্ট হিসেবে একটা কমান্ড নিবে। যেহেতু একাধিক কমান্ড রান করবো তাই কমান্ডটি আলাদা ভাবে আর্গুমেন্ট হিসেবে পাঠাবো।

দ্বিতীয়ত, lib/ আর test/ ডিরেক্টরির সব ফাইলগুলো একটা ভেরিয়েবল এ জমা করে রাখছি।

তৃতীয়ত, ভেরিয়েবলে থাকা এই সবগুলো ফাইলে আর্গুমেন্টে পাওয়া কমান্ডটি রান করবো। সব ঠিকঠাক থাকলে তো হলো-ই, যদি কোনো কমান্ড কোনো ফাইলে সমস্যা পায়, তাহলে আমরা exit 1 দিয়ে স্ক্রিপ্ট থেকে বের হয়ে যাবো যাতে সমস্যাটা বের করে ঠিক করতে পারি।

তেমন কঠিন না, তাই না? বলেছিলাম তো। চলুন এখন কমান্ডগুলো একে একে রান করি।

# Run code formatting using dart format on staged files
check_staged_files "dart format --set-exit-if-changed"

# Run static code analysis using flutter analyze on staged files
check_staged_files "flutter analyze"
# Run unit tests using flutter test
if ! flutter test; then
exit 1
fi

১. আমরা check_staged_files ফাংশন ব্যবহার করে সব স্টেজড ফাইলে ফরম্যাটিং করছি।

২. একইভাবে ফাইলগুলোতে কোনো লিন্টিং ইস্যু আছে কিনা জানার জন্য flutter analyze কমান্ড দিচ্ছি।

৩. সবশেষে আমাদের টেস্টকেস(যদি থাকে) রান করছি। যেহেতু এগুলো কোনো নির্দিষ্ট ফাইলের উপর চালাবো না, তাই check_staged_files ব্যবহার করছি না।

যদি সব ঠিকঠাকে থাকে আমাদের কমিটটি সফল হবে, আর যদি কোনো সমস্যা পাওয়া যায়, সেখানেই স্ক্রিপ্টটি থেমে গিয়ে আমাদেরকে সমস্যাটি সমাধান করতে বলবে। এভাবে সব সমস্যা সমাধান করলে কমিট সফল হয়ে যাবে।

সব টুকরো জোড়া লাগালে আমাদের পুরো স্ক্রিপ্টটি নিচের মতো হবে-

#!/bin/bash

# Function to check staged files
check_staged_files() {
local command="$1"
# Get a list of staged files within the lib/ and test/ directory
local staged_files
staged_files=$(git diff --name-only --cached | grep -E '^(lib/|test/)')
# Run the specified command on each staged file
for file in $staged_files; do
if ! $command "$file"; then
exit 1 # Exit the script if there's an issue
fi
done
}

# Run code formatting
check_staged_files "dart format --set-exit-if-changed"
# Run static code analysis
check_staged_files "flutter analyze"
# Run unit tests
if ! flutter test; then
exit 1
fi

# Reapply the stash to other files
git stash pop
# Exit with success status
exit 0

আমাদের স্ক্রিপ্ট লেখার কাজ শেষ(প্রায়)। এখন তা টেস্ট করে দেখার সময়।

মিউজিক প্লিজ 🥁🥁

আপনারা যেকোনো প্রজেক্টে টেস্ট করে দেখতে পারেন, আমি আপাতত এই এনিমেশন প্রজেক্টটায় টেস্ট করে দেখছি, এখানে ইচ্ছাকৃত কিছু সমস্যা আর একটা ফেইল করা টেস্ট ফাইল আছে।

টেস্ট ফাইলের ভেতরে একটু চোখ বুলিয়ে নেই

এটা জাস্ট একটা ডামি ফাইল যার কাজ হচ্ছে আপাতত ফেইল করা, যাতে আমরা স্ক্রিপ্টটি টেস্ট করতে পারি। সমস্যা নেই, শেষে আমরা ওকে পাশ করিয়ে দিবো।

এখন, আমরা মোট ৪ টি ফাইলে কিছু চেঞ্জ করেছি, তবে শুধু main আর dummy_test ফাইল দুটো স্টেজিং এরিয়াতে নিবো, যাতে করে দেখতে পারি চেক শুধু স্টেজিং এরিয়ার ফাইলে হচ্ছে কিনা।

দুটো ফাইলকে স্টেজ করে নিয়েছি

এখন আমরা কোনো একটা কমিট করার চেষ্টা করবো। আমাদের প্রত্যাশা হচ্ছে, শুধু সবুজ ফাইল দুটোয়ে স্ক্রিপ্ট রান হবে। যেহেতু ফাইল দুটোয়ে লিন্টিং সমস্যা রয়েছে, আবার টেস্টকেসও ফেইল করবে, কাজেই আমাদের আশা কমিটটি সফল হবে না।

আমরা দেখতে পাচ্ছি প্রথমে দুটো ফাইলে ফরম্যাটিং হয়েছে, এরপর flutter analyze শুরু হয়েছে। কিন্তু শুরুতেই main ফাইলে ইস্যু থাকায় স্ক্রিপ্ট থেমে গেছে এবং কনসোলে ইস্যুটি দেখিয়েছে ।

স্ক্রিপ্টটি Top-bottom পদ্ধতিতে, মানে উপর থেকে নিচে ধাপে ধাপে চলেছে। কোনো ধাপে সমস্যা পেলে নিচের ধাপে আর না গিয়ে কী সমস্যা হলো তা দেখিয়ে স্ক্রিপ্ট হতে বের হয়ে গিয়েছে এবং আমাদেরকে এখন সমাধান করে আবার কমিট করতে হবে। কনসোলে আমরা দেখতে পাচ্ছি কোন ফাইলে কী সমস্যা হয়েছে। আপাতত দুটো ফাইলের সমস্যাই ঠিক করে, ফাইলগুলো স্টেজে এড করে আবার কমিট করি।

এখন দেখতে পাচ্ছি আমাদের flutter analyze এ কোনো সমস্যা পায়নি, এরপর টেস্ট কমান্ড রান করেছে। যেহেতু আমাদের টেস্ট ফেইল করেছে, তাই আবার কমিট বন্ধ হয়ে আমাদের টেস্ট পাশ করতে বলছে। এটা একটা ডামি টেস্ট তাই আমরা পাশ করিয়ে দিবো, তবে আসল টেস্ট কেসে আগে দেখতে হবে কেন ফেইল করলো, এরপর টেস্ট কেস নয় বরং কোডের ইস্যু ঠিক করতে হবে যাতে টেস্ট ঠিকঠাক পাশ হয়।

ওয়াও! অবশেষে সব চেক সফলভাবে শেষ হয়েছে। আমাদের মূল কাজ শেষ, তবে কনসোল আউটপুট দেখতে বেশ একঘেয়ে লাগছে। চলুন একটু রঙ যোগ করে নেই

#!/bin/bash

# Color formatting sequences
error="\e[31;1m%s\e[0m\n"
success="\e[32;1m%s\e[0m\n"
info="\e[33;1m%s\e[0m\n"
reset="\e[0m"

# Symbolic characters
checkmark="✅"
crossmark="❌"

# Function to check staged files within the lib/ directory
check_staged_files() {
local command="$1"

# Get a list of staged files within the lib/ directory
local staged_files
staged_files=$(git diff --name-only --cached | grep -E '^(lib/|test/)')

# Run the specified command on each staged file
for file in $staged_files; do
if ! $command "$file"; then
printf "${error}" "${crossmark} $command error ${crossmark} "
exit 1 # Exit the script if there's an issue
fi
done
}

# Inform the user that the pre-commit checks are starting
printf "${success}" " ${checkmark} Starting pre-commit checks... ${checkmark} "



# Run code formatting using dart format on staged files
printf "${info}" 'Running Flutter Formatter...'
check_staged_files "dart format --set-exit-if-changed"
printf "${success}" 'Done!'

# Run static code analysis using flutter analyze on staged files
printf "${info}" 'Running Flutter analyzer...'
check_staged_files "flutter analyze"
printf "${success}" 'Done!'

# Run unit tests using flutter test
printf "${info}" 'Running Unit Tests...'
if ! flutter test; then
printf "${error}" "${crossmark} Unit tests error ${crossmark} "
exit 1
fi
printf "${success}" 'Done!'


# Inform the user that the pre-commit checks passed
printf "${success}" "${checkmark} Pre-commit checks passed. Committing ${checkmark} "


exit 0

এখন আবার কোনো চেঞ্জ করে কমিট করলে নিচের মতো আসবে।

এটা দেখতে বেশ ভালোই লাগছে। আমি নিজে এটা এখন প্রায় সব প্রজেক্টেই ব্যবহার করি। আপনারা অবশ্যই নিজেদের মতো করে স্ক্রিপ্ট এর ধাপ আর স্টাইল করে নিতে পারেন।

কিন্তু কখনো যদি আমরা এই চেক বাইপাস করে চট করে কমিট করে ফেলতে চাই আগের মতো?

আমরা সবসময়ই পরে চেক করে দেখতে পারি। যেহেতু কোনো ইস্যুই আপাত দৃষ্টিতে খুব বড়ো কিছু নয়। তবে একটা কথা হয়তো আমরা অনেকেই জানি, এখন না মানে আর কখনো না। সফটওয়ার ইঞ্জিনিয়ারিং এ এই কথা আরো বেশি সত্য। এতো কিছু আমাদের মাথায় থাকে যে পরের জন্য কিছু রেখে দিলে আর দেখা হয়ে উঠে না প্রায়ই। তাই সাথে সাথেই করে ফেলা ভালো।

তবুও বিভিন্ন সময় তাড়াহুড়োয় যদি আমরা প্রি কমিট হুকটি স্কিপ করতে চাই, তাহলে কমিট মেসেজ এর পরে শুধু — no-verify লিখে দিলেই হবে। এতে করে হুকটি আর রান হবে না।

 git commit -m 'Update: everythign is fine' --no-verify

শুরুতে এসব সময় নষ্ট বা অনেক কিছু মনে হতে পারে। তবে এই সব চেক রান হতে শুধু কিছু সেকেন্ড বাড়তি খরচ হয়। আর আমরা যদি ছোট ছোট চেঞ্জের জন্য কমিট করতে অভ্যস্ত হই, তাহলে সময় আরো কম লাগবে। তবে দিনশেষে এটা অনেক উপকারী বলে আমার কাছে মনে হয়। অনেকটা একজন কোড রিভিউয়ার এর মতো যে চেক করে টুকটাক জিনিসগুলো মনে করিয়ে দিচ্ছে, আর এমন ছোট ছোট চেক থেকেই পুরো কোডবেজ অনেকাংশে পরিচ্ছন্ন থাকবে, আমরা কিছু ভুলে গেলেও কমিট এর সময় মনে করিয়ে দেয়ার জন্য কেউ(অথবা কিছু) রয়েছে।

আজ এই পর্যন্তই । ধৈর্য্য ধরে এতোটা পড়ে আসতে পারার জন্য ধন্যবাদ। প্রি কমিট হুকে আর কী কী করা যায় বা আর কোন কোন হুক আপনারা ব্যবহার করেন আমাকে জানাতে পারেন।

লিংকডইন কিংবা মিডিয়ামে আমাকে ফলো করে রাখতে পারেন।

ধন্যবাদ।

--

--