Part of 10306343 การจัดการโครงสร้างพื้นฐานและการส่งมอบซอฟต์แวร์อย่างต่อเนื่อง (Development Operations: DevOps)

2026-10-18 · 24 min read

บทที่ 2 Git พื้นฐาน: commit, branch และการแก้ merge conflict

ภาพรวมของบท

บทนี้เปลี่ยนโฟลเดอร์ devops-lab01 จากบทที่ 1 ให้เป็น Git repository ที่มีประวัติจริง: commit แรก, branch, merge แบบ Branch-First, merge conflict ที่คุณแก้เองตามเอกสารทางการ, .gitignore กันไฟล์ลับ และปิดท้ายด้วย tag v0.1.0 ซึ่งเป็น milestone แรก (M1) ของโครงงานสะสมที่ใช้ต่อถึงวันนำเสนอ

ทำไมเรื่องนี้สำคัญ: ทุกบทหลังจากนี้ทำงานบน repository บทที่ 3 ผลักขึ้น GitHub ทำงานเป็นทีมผ่าน pull request บทที่ 5 ให้ CI ตรวจทุก commit ที่ push ขึ้นมา และบทที่ 8 ติด tag กับ image ตามเวอร์ชันของแอป ถ้าคุณไม่แน่ใจว่า commit ของคุณมีไฟล์อะไรข้างใน หรือแก้ conflict ไม่เป็น งานทุกบทถัดไปจะสะดุดที่จุดเดียวกันนี้

ความเชื่อมโยงกับบทอื่น: โฟลเดอร์ทำงานคือ ~/devops-lab01 จากบทที่ 1 ซึ่งมี app.log, app.log.bak, clean.log, hello.sh, .env และ check-errors.sh จาก Lab E สคริปต์นี้จะถูกย้ายไปอยู่ที่ scripts/check-errors.sh ใน repository และจะกลับมาเป็นขั้นตรวจใน CI ของบทที่ 5 สิ่งที่ได้จากบทนี้ถูกใช้ต่อดังนี้

สิ่งที่ได้จากบทนี้ใช้ต่อในบท
repository ที่มีประวัติ commit, branch และ merge (M1)3 (ผลักขึ้น GitHub เป็นทีม) และทุกบทหลังจากนั้น
scripts/check-errors.sh ใน repository5 (ขั้นตรวจใน CI)
.gitignore และนิสัยกันไฟล์ลับออกจาก repository9 (ตรวจว่าไม่พบรหัสผ่าน) และ 10 (GitHub Secrets)
tag v0.1.04 (ปรับเวอร์ชันเป็น 0.2.0) และ 8 (tag ของ image)
การอ่านผล git log --oneline --graph3 (อ่านประวัติ pull request) และ 12 (อ่านประวัติทั้งโครงงาน)

ผลลัพธ์การเรียนรู้

เมื่อจบบท คุณต้องทำสิ่งต่อไปนี้ได้ และแต่ละข้อมีงานที่ใช้วัดผล

รหัสผลลัพธ์การเรียนรู้วัดจากCLO ของรายวิชา
2.1สร้าง commit, branch และ merge ตามลำดับที่กำหนดใน repository ของตนเองงานข้อ 1CLO3
2.2แก้ merge conflict จนไฟล์ได้ผลตามที่กำหนด โดยใช้ข้อมูลจากเอกสาร git ทางการงานข้อ 2CLO3
เตรียมพื้นแยกไฟล์ที่ควรเก็บออกจากไฟล์ที่ไม่ควรเก็บด้วย .gitignore และปักหมุดเวอร์ชันด้วย tagงานข้อ 3ยังไม่นับ CLO ใช้ต่อในบทที่ 3 และ 5

CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):

  • CLO3 ใช้เครื่องมือดิจิทัลอย่างเหมาะสมเพื่อพัฒนาระบบ และสามารถเรียนรู้และปรับตัวด้วยตนเองจากแหล่งข้อมูลที่เชื่อถือได้

แนวคิด Single Source of Truth และ Branch-First ในส่วนที่ 1 เป็นพื้นความรู้ของวิชา ไม่ได้วัดเป็น CLO ในบทนี้ เพราะการเลือกและอธิบาย Git workflow ตามบริบทงานจะเริ่มวัดในบทที่ 3 (CLO1) บทนี้จึงจับคู่เฉพาะ CLO3 ซึ่งวัดการใช้เครื่องมือตามขั้นตอนที่กำหนดและการหาคำตอบจากเอกสารทางการ Learning Log ครั้งที่ 2 เก็บสะสมเป็น portfolio ที่ประเมินรวมในบทที่ 13

ก่อนเริ่ม: ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์

ความรู้พื้นฐาน: บทที่ 1 (ใช้ Terminal, แก้ไฟล์ข้อความ, คำสั่ง grep และโครงสร้างโฟลเดอร์ devops-lab01) ไม่ต้องเคยใช้ Git มาก่อน

สิ่งที่ต้องมี: เครื่อง Ubuntu ที่ติดตั้ง Git แล้ว (ตรวจด้วย git --version ถ้ายังไม่มี ติดตั้งด้วย sudo apt update แล้ว sudo apt install git) และโฟลเดอร์ ~/devops-lab01 จากบทที่ 1 ที่มีไฟล์ app.log, app.log.bak, clean.log, hello.sh, .env และ check-errors.sh (เวอร์ชันที่แก้ตาม Lab E.3) ถ้าโฟลเดอร์หาย ให้ทำ Lab C และ Lab E ของบทที่ 1 ใหม่ก่อน

รุ่นซอฟต์แวร์ที่ใช้ทดสอบ: ทุกคำสั่งและผลลัพธ์ในบทนี้ทดสอบแล้วบนรุ่นต่อไปนี้

ซอฟต์แวร์รุ่นที่ทดสอบ
Git2.51.0.windows.1 (ทดสอบบน Windows 10 กับ Git Bash วันที่ 18 ต.ค. 2569)
Git ของผู้เรียน (Ubuntu 24.04)2.43.x — ยังไม่ได้ทดสอบซ้ำบน Ubuntu 24.04 และเครื่องห้องแลบจริง

คำสั่งทั้งหมดเป็นคำสั่งพื้นฐานที่ใช้เหมือนกันทุกแพลตฟอร์ม (git switch มีตั้งแต่ Git 2.23) ส่วนที่ต่างกันได้คือข้อความเตือน เช่น เรื่อง CRLF ที่พบเฉพาะ Windows (ดู Lab A) และข้อความเกี่ยวกับชื่อ branch เริ่มต้นที่ปรากฏเมื่อยังไม่ได้ตั้งค่า

เวลาโดยประมาณ: ผู้เรียนที่ผ่านบทที่ 1 มาแล้วใช้เวลาส่วนปฏิบัติประมาณ 2–3 ชั่วโมง บวกเวลาศึกษาด้วยตนเอง

ค่าใช้จ่ายและข้อมูลลับ: บทนี้ทำบนเครื่องของคุณทั้งหมด ไม่มี Lab บนคลาวด์ จึงไม่มีค่าใช้จ่าย แต่มีสองเรื่องที่ต้องระวังตั้งแต่ต้น ข้อแรก user.name และ user.email ที่คุณตั้งใน Lab A จะติดไปกับทุก commit เมื่อผลัก repository ขึ้น GitHub ในบทที่ 3 ใครก็เห็นข้อมูลนี้ได้ จึงควรใช้ชื่อและอีเมลที่คุณสบายใจจะเปิดเผย ข้อที่สอง ไฟล์ลับอย่าง .env (มีรหัสผ่านสมมติ change-me จากบทที่ 1) ต้องไม่ติดเข้าไปใน repository เลย .gitignore ใน Lab D คือเครื่องมือแรกของวิชานี้ที่กันความลับออกจาก repository และแนวคิดนี้จะกลับมาอีกครั้งในบทที่ 9 (ตรวจว่าไม่พบรหัสผ่านใน repository) และบทที่ 10 (GitHub Secrets)

ส่วนที่ 1 บรรยาย

แต่ละหัวข้อมีตัวอย่างที่ยากขึ้นทีละระดับ และจบด้วยคำถามตรวจความเข้าใจ ให้ตอบในใจหรือคุยกับเพื่อนข้าง ๆ ก่อนไปหัวข้อถัดไป

2.1 ทำไมต้องมี version control และ Single Source of Truth

ถ้าคุณเคยตั้งชื่อไฟล์ว่า report.doc, report-v2.doc, report-final.doc และ report-final-จริงๆ.doc คุณก็เคยเจอปัญหาที่ version control มีไว้แก้แล้ว version control system (VCS) คือเครื่องมือที่บันทึกประวัติการเปลี่ยนแปลงของไฟล์ทั้งโครงการ ทำให้ย้อนดูว่าใครแก้อะไรเมื่อไร กลับไปใช้เวอร์ชันก่อนหน้าได้ และทำงานหลายทางพร้อมกันแล้วรวมกันได้ (Pro Git)

Git ต่างจาก VCS รุ่นเก่าตรงที่เวลาคุณ commit Git เก็บ "ภาพถ่าย" (snapshot) ของไฟล์ทั้งชุดในขณะนั้น ไม่ใช่เก็บเฉพาะส่วนต่างของแต่ละไฟล์ ตำรา Pro Git สรุปว่า Git คิดเรื่องข้อมูลของตัวเองเป็น "stream of snapshots" คือลำดับของภาพถ่ายทั้งชุด (Pro Git §1.3)

Single Source of Truth (SSOT) คือหลักที่ว่า ความจริงร่วมของทีมต้องมีที่เดียว สำหรับโค้ด ที่นั้นคือ repository ไม่ใช่เครื่องของใครคนใดคนหนึ่ง หรือไฟล์ zip ในกล่องจดหมาย เมื่อทุกคนดึงงานจากที่เดียว จะไม่มีคำถามว่า "เวอร์ชันล่าสุดอยู่ที่ไหน" ในบทนี้ repository ยังอยู่บนเครื่องของคุณคนเดียว แต่ทุกอย่างที่ฝึกวันนี้จะถูกยกขึ้น GitHub ให้เป็น SSOT ของทีมในบทที่ 3

ประวัติของ repository ยังเป็นหลักฐานที่ตรวจสอบย้อนได้ (audit trail) ซึ่งสำคัญกับ DevOps ตรงที่ pipeline ในบทที่ 5 จะทำงานกับ "commit" เป็นหน่วย ไม่ใช่กับ "ไฟล์"

ตัวอย่างระดับความยากที่เพิ่มขึ้น:

  • ระดับ 1 (ไฟล์เดียว): นักเขียนคนเดียวเก็บทุกฉบับเอง ยังไม่ถึงกับพัง แต่ไล่เรียงว่าฉบับไหนเกิดก่อนหลังไม่ได้
  • ระดับ 2 (หลายคน): สองคนแก้ไฟล์เดียวกันจากสำเนาคนละชุด เอามารวมกันด้วยมือ ต้องเทียบทีละบรรทัด
  • ระดับ 3 (ส่งมอบต่อเนื่อง): ทีมส่งรุ่นใหม่ทุกสัปดาห์ ต้องรู้ทุกเวลาว่าโค้ดตรงไหนอยู่ในรุ่นไหน และกลับไปรุ่นก่อนได้เร็วแค่ไหน นี่คือบริบทของวิชานี้และของ DORA metrics จากบทที่ 1

คำถามตรวจความเข้าใจ 2.1: ทีมเก็บโค้ดล่าสุดไว้ในเครื่องของหัวหน้าและแจกสำเนาผ่านไดรฟ์แชร์ วันจันทร์ลูกทีมคนหนึ่งก็อปไปแก้ วันพุธหัวหน้าก็แก้ไฟล์เดิมบนเครื่องตัวเอง วันศุกร์เอาสองฉบับมารวมกันไม่ได้ บอกอย่างน้อย 2 อย่างที่ version control ช่วยแก้สถานการณ์นี้

2.2 working tree, staging และ commit

ไฟล์ในโครงการ Git อยู่ได้สามสถานะ ตามสามพื้นที่หลักของโครงการ (Pro Git §1.3 The Three States)

พื้นที่คืออะไรไฟล์ในสถานะ
working treeโฟลเดอร์จริงที่คุณแก้ไฟล์อยู่modified (แก้แล้ว ยังไม่บอก Git)
staging area (index)ร่างที่คัดไว้ว่าจะเก็บอะไรในภาพถ่ายถัดไปstaged (คัดแล้วด้วย git add)
repository (.git)ประวัติถาวรของภาพถ่ายทั้งหมดcommitted (เก็บแล้วด้วย git commit)

คำสั่งหลักสามตัวที่เดินไปตามสามพื้นที่นี้: git status ดูว่าไฟล์แต่ละตัวอยู่สถานะใด, git add ชื่อไฟล์ ยกไฟล์จาก working tree ขึ้น staging, และ git commit -m "ข้อความ" ถ่ายรูปสิ่งที่อยู่บน staging ลงประวัติ

ระดับ 1 อ่าน git status ออก: ผลแบ่งเป็นกลุ่น "Changes to be committed" คือ staged จะติดไปกับ commit ถัดไป, "Changes not staged for commit" คือ modified ยังไม่ได้ add, และ "Untracked files" คือไฟล์ใหม่ที่ Git ยังไม่รู้จัก

ระดับ 2 ยืนยันด้วยตา: คุณแก้ a.txt กับ b.txt แล้วสั่ง git add a.txt เวลา commit เฉพาะ a.txt ติดไป ในขณะที่ b.txt ยังเป็น modified รออยู่ (จะเห็นจริงใน Lab A) ใช้ git diff ดูการแก้ที่ยังไม่ได้ stage และ git diff --staged ดูสิ่งที่กำลังจะติดไปกับ commit

ระดับ 3 ทำไมต้องมี staging ทั้งที่ดูเหมือนขั้นตอนเกิน: เพราะมันให้คุณคัดว่าอะไรจะเข้าภาพถ่าย แทนที่จะ "เก็บทุกอย่างที่แก้วันนี้" การ commit เล็กและตรงประเด็นทำให้ทบทวนง่าย เมื่อถึงบทที่ 3 เพื่อนรีวิว pull request ของคุณทีละ commit คุณจะขอบคุณ staging นี้

คำถามตรวจความเข้าใจ 2.2: คุณแก้ A.txt กับ B.txt สั่ง git add A.txt ตามด้วย git commit -m "แก้ไข" เสร็จแล้วสั่ง git commit -m "อีกครั้ง" ทันทีโดยไม่ add อะไรเพิ่ม — B.txt อยู่สถานะใด และ commit รอบที่สองเกิดอะไรขึ้น

2.3 branch: แยกทางทำงานโดยไม่แตะงานของกัน

branch ใน Git ไม่ใช่สำเนาโฟลเดอร์ทั้งก้อน แต่เป็น "ตัวชี้" ที่ชี้ไปที่ commit หนึ่ง ตำรา Pro Git นิยามตรง ๆ ว่า "A branch in Git is simply a lightweight movable pointer to one of these commits" (Pro Git §3.1) การสร้าง branch จึงเร็วและถูกมาก สร้างได้ไม่จำกัด ไม่ต้องประหยัด

อีกตัวชี้ที่ต้องรู้คือ HEAD ซึ่งชี้ไปที่ branch ที่คุณ "กำลังอยู่" เมื่อคุณ commit ใหม่ ตัวชี้ของ branch ปัจจุบัน (รวมทั้ง HEAD) จะเลื่อนไปชี้ commit ใหม่นั้น ส่วน branch อื่นยังชี้ที่เดิม

คำสั่งหลัก: git branch ดูรายการ branch (ตัวที่มี * คือตัวที่อยู่), git switch -c ชื่อbranch สร้างใหม่แล้วสลับไปอยู่, git switch ชื่อbranch สลับกลับ ถ้าเจอคำสั่ง git checkout -b ในเอกสารเก่า นั่นคือวิธีเดิมที่ทำสิ่งเดียวกัน git switch เป็นคำสั่งแยกที่มาทีหลัง (Git 2.23) เพื่อให้อ่านออกว่า "สลับ" อย่างเดียว (git-switch)

  • ระดับ 1: ดูรายการ branch และบอกได้ว่าตอนนี้อยู่ตัวไหน
  • ระดับ 2: สร้าง feature/usage, commit หนึ่งครั้งบนนั้น, สลับกลับ main — ไฟล์ในโฟลเดอร์กลับไปเป็นเหมือนก่อนแก้ เพราะ working tree ถูกอัปเดตตาม branch ที่สลับไป (git-switch)
  • ระดับ 3: จาก commit เดียวกัน สอง branch แยกทางไปคนละทาง กราฟของประวัติแยกเป็นสองเส้นแล้วมาบรรจบอีกครั้งตอน merge (หัวข้อถัดไป) นี่คือรูปที่คุณต้องอ่านออกในแบบฝึกหัดของบทนี้

คำถามตรวจความเข้าใจ 2.3: คุณอยู่บน branch feature/usage แล้ว commit 2 ครั้ง เพื่อนเปิดดู branch main ในเครื่องเดียวกัน — เพื่อนเห็น commit ทั้งสองอันนั้นไหม เพราะอะไร

2.4 merge และ Branch-First

merge คือการนำ commit ของ branch หนึ่งมารวมกับ branch ที่คุณกำลังอยู่ ด้วยคำสั่ง git merge ชื่อbranch (สั่งจาก branch ปลายทางเสมอ) ผลมีสองรูปแบบ (Pro Git §3.2)

รูปแบบเกิดเมื่อประวัติเป็นอย่างไร
fast-forwardbranch ปลายทางไม่มี commit ใหม่เลยหลังแตกกิ่งGit แค่เลื่อนตัวชี้ไปหน้า ไม่เกิด merge commit กราฟเป็นเส้นตรง
three-way mergeทั้งสองฝั่งมี commit ใหม่ของตัวเองGit เทียบสามจุด (ปลายสอง branch + บรรพบุรุษร่วม) แล้วสร้าง merge commit ที่มีพ่อแม่สองตัว กราฟเป็นสามเหลี่ยม

เอกสารทางการของ git merge ระบุเรื่อง fast-forward ไว้ว่าในกรณีนั้น "a new commit is not needed ... instead, the HEAD (along with the index) is updated to point at the named commit, without creating an extra merge commit" และพฤติกรรมนี้ "can be suppressed with the --no-ff option" (git-merge) วิชานี้ใช้ git merge --no-ff เสมอ เพราะ merge commit ที่ปรากฏชัดทำให้เห็นในกราฟว่างานชิ้นไหนเคยมี branch ของตัวเอง — สะดวงทั้งตอนเรียน ตอนตรวจงาน และตรงกับ pull request ของบทที่ 3 ซึ่ง merge ผ่าน GitHub ก็สร้าง merge commit ให้เหมือนกัน

Branch-First คือกติกาการทำงานของวิชานี้: งานทุกชิ้นเริ่มที่การแตก branch ใหม่ ไม่แก้และ commit ตรง ๆ บน branch หลัก เหตุผลคือ branch หลัก (main) ต้องเชื่อถือได้เสมอ ทุกการเปลี่ยนแปลงเกิดบน branch ที่มีชื่อบอกว่าทำอะไร แล้วค่อยรวมกลับอย่างเป็นระบบ นี่คือแก่นของ GitHub flow ที่ทั้งทีมจะใช้จริงในบทที่ 3 ชื่อ branch ที่ใช้ในวิชานี้เป็น feature/ชื่องาน เช่น feature/usage

คำถามตรวจความเข้าใจ 2.4: คุณแตก branch feature/x จาก main แล้ว commit บน feature/x หนึ่งครั้ง โดย main ไม่มี commit ใหม่เลย — ถ้า merge แบบปกติจะได้รูปแบบใด และใครบางคนที่ดูกราฟจะรู้ไหมว่าเคยมี branch นี้

2.5 conflict เกิดเมื่อไร

Git รวมงานอัตโนมัติได้ดีเมื่อสองฝั่งแก้คนละบริเวณ ตำรา Pro Git ระบุเงื่อนไขของ conflict ว่า "If you changed the same part of the same file differently in the two branches you're merging, Git won't be able to merge them cleanly" (Pro Git §3.2) เมื่อเกิด conflict การ merge จะหยุดกลางทาง และ Git ทิ้งเครื่องหมายไว้ในไฟล์ให้คนตัดสินใจ

TEXT
<<<<<<< HEAD
ข้อความฝั่งของคุณ (branch ที่คุณอยู่ตอนสั่ง merge)
=======
ข้อความฝั่งที่ merge เข้ามา
>>>>>>> ชื่อ branch ที่ merge

เอกสารทางการของ git merge อธิบายว่าเครื่องหมายสามตัวนี้ครอบ "พื้นที่ที่การเปลี่ยนแปลงที่ขัดกันเกิดขึ้น" โดยฝั่งก่อน ======= คือฝั่งของคุณ และหลังจากนั้นคืออีกฝั่ง (git-merge, HOW CONFLICTS ARE PRESENTED) เมื่อเจอ conflict คุณมีสองทางเท่านั้น (git-merge, HOW TO RESOLVE CONFLICTS)

  1. ยกเลิก: git merge --abort ทำให้กลับไปสถานะก่อนสั่ง merge ทุกอย่างเหมือนเดิม
  2. แก้ให้จบ: แก้ไฟล์ให้เป็นผลที่ต้องการ (เลือกฝั่งหนึ่ง หรือรวมทั้งสองฝั่ง) ลบเครื่องหมายทั้งสามชนิด แล้ว git add ไฟล์ ตามด้วย git commit เพื่อปิดการ merge

สิ่งสำคัญที่ต้องเข้าใจ: conflict ไม่ใช่ความผิดพลาดของเครื่อง และไม่ทำให้ไฟล์พัง Git แค่ไม่กล้าตัดสินใจแทนคุณว่าข้อความไหนถูกต้อง มันจึงหยุดรอคุณ ใน Lab C คุณจะสร้าง conflict จริงด้วยตัวเองและแก้ด้วยสองวิธีข้างบน

คำถามตรวจความเข้าใจ 2.5: จาก block conflict ด้านบน ฝั่งบน (เหนือ =======) มาจาก branch ใด และถ้าคุณตัดสินใจว่ายังไม่อยาก merge ตอนนี้ จะใช้คำสั่งใด

2.6 สาธิตก่อนเข้า Lab: เดินครบเส้นทางใน 10 นาที

ก่อนลงมือทำ Lab ผู้สอนจะสาธิตสดครบวงจรหนึ่งรอบบนโครงงานจำลอง ลำดับคือ: git init → ตั้งค่า → git add + git commit แรก → git switch -c แตก branch → แก้ไฟล์แล้ว commit → git switch main → git merge --no-ff → ทำ conflict ขึ้นมา → git merge --abort → merge ใหม่ → แก้ conflict → git add + git commit → git log --oneline --graph ดูประวัติ → git tag จบด้วยภาพเดียวกับที่คุณต้องส่งเป็นงานข้อ 1

ขณะดูสาธิต ให้จดสองอย่าง: ผลของ git status เปลี่ยนอย่างไรทีละขั้น และกราฟของ git log --oneline --graph งอกเป็นสามเหลี่ยมตอนไหน Lab A ถึง D จะพาคุณทำซ้ำทุกขั้นบนโครงงานจริงของคุณเอง

คำถามตรวจความเข้าใจ 2.6: ในสาธิตมีคำสั่งที่คุณใช้มาแล้วจากบทที่ 1 อยู่ด้วย คำสั่งใด และช่วยอะไรในบทนี้

ส่วนที่ 2 ปฏิบัติ

ทุก Lab มีสองช่วง ฝึกตามตัวอย่าง ให้พิมพ์คำสั่งตามและเทียบผล ส่วน ตัดสินใจเอง (B.5, C.6, D.4) ไม่มีคำสั่งให้ คุณต้องเลือกคำสั่งหรือตัดสินใจเอง

ℹ️ผลของคุณจะต่างเล็กน้อย

เลข hash ของ commit (เช่น 77b5783) เกิดใหม่ไม่ซ้ำกันทุกเครื่องและทุกครั้ง ชื่อผู้เขียนมาจาก user.name ที่คุณตั้ง และเวลาในผลลัพธ์ตามเครื่องคุณ ตัวอย่างในบทนี้ใช้ผู้ใช้ชื่อ student ให้ดูที่รูปแบบของผล ไม่ต้องตรงทุกตัวอักษร

⚠️อย่าเพิ่มทุกไฟล์ด้วย git add .

ใน devops-lab01 มี .env ซึ่งเป็นไฟล์รหัสผ่าน (ค่าสมมติจากบทที่ 1) คำสั่ง git add . เพิ่มทุกไฟล์รวมทั้งไฟล์ลับเข้า staging ทันที จนกว่าจะมี .gitignore ใน Lab D ให้ระบุชื่อไฟล์ที่ต้องการ add เสมอ และตรวจด้วย git status ก่อน commit ทุกครั้ง

Lab A: ตั้งค่า Git และ commit แรกบนแอปตั้งต้น

สถานการณ์: หัวหน้าทีมบอกว่า ก่อนจะเข้าทีมในบทที่ 3 ทุกคนต้องมีโครงงานของตัวเองเป็น repository ที่มีประวัติ คุณจึงเปลี่ยนโฟลเดอร์ devops-lab01 จากบทที่ 1 ให้เป็น repository ของคุณเอง

เป้าหมาย: ตั้งค่า Git ครั้งแรกของเครื่อง, เตรียมโครงสร้างโฟลเดอร์, สร้าง commit แรก (ส่วนต้นของ LLO 2.1)

ฝึกตามตัวอย่าง

A.1 เตรียมโครงสร้างโครงงาน

ย้ายสคริปต์จากบทที่ 1 ไปอยู่ในโฟลเดอร์ scripts/ แล้วสร้าง README.md อธิบายโครงงาน

BASH
cd ~/devops-lab01
mkdir -p scripts
mv check-errors.sh scripts/check-errors.sh
cat > README.md <<'EOF'
# DevOps Lab 01
 
แบบฝึกหัดของรายวิชา 10306343 (DevOps) เริ่มจากบทที่ 1
 
## ไฟล์ในโครงงาน
 
- app.log ตัวอย่าง log จากบทที่ 1
- scripts/check-errors.sh สคริปต์ตรวจ ERROR จากบทที่ 1
- hello.sh สคริปต์ทักทายจาก Lab B ของบทที่ 1
EOF

A.2 ตั้งค่า Git ครั้งแรกของเครื่อง

BASH
git --version
git config --global user.name "student"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main
git config --global core.autocrlf false

แต่ละค่าทำงานดังนี้

ค่าทำไมต้องตั้ง
user.name / user.emailประทับตัวตนลงทุก commit ถ้าไม่ตั้ง Git ปฏิเสธการ commit (ทดสอบแล้ว ขึ้น "Author identity unknown") ใช้ชื่อที่คุณสบายใจเปิดเผย เพราะจะปรากฏบน GitHub ตั้งแต่บทที่ 3
init.defaultBranch mainให้ branch แรกของ repository ใหม่ชื่อ main ตรงกับที่วิชานี้และ GitHub ใช้
core.autocrlf falseไม่เปลี่ยนปลายบรรทัดระหว่าง LF กับ CRLF ผู้ใช้ Windows ที่ไม่ตั้งค่านี้จะเจอข้อความเตือนตอน git add เช่น warning: in the working copy of 'README.md', LF will be replaced by CRLF ... (พบจริงตอนทดสอบบน Git for Windows) การปิดการแปลงทำให้ไฟล์ใน repository เหมือนกันทุกแพลตฟอร์ม บน Ubuntu ไม่จำเป็นแต่ตั้งไว้ไม่เสียหาย

A.3 สร้าง repository และดูสถานะ

BASH
git init
git status
TEXT
Initialized empty Git repository in /home/student/devops-lab01/.git/
On branch main
 
No commits yet
 
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.env
	README.md
	app.log
	app.log.bak
	clean.log
	hello.sh
	scripts/
 
nothing added to commit but untracked files present (use "git add" to track)

คำสั่ง git init สร้างโฟลเดอร์ลับ .git/ ซึ่งเก็บทั้งประวัติ (repository ในความหมายของหัวข้อ 2.2) สังเกตว่า git status เห็น .env ด้วย — ไฟล์ลับยังไม่ถูกเก็บเข้าไปไหน แต่มันรออยู่ใน working tree และจะติดทันทีถ้าคุณสั่ง git add .

A.4 commit แรก

BASH
git add README.md
git status
git commit -m "commit แรก: เพิ่ม README อธิบายโครงงาน"
git log --oneline
TEXT
[main (root-commit) 77b5783] commit แรก: เพิ่ม README อธิบายโครงงาน
 1 file changed, 9 insertions(+)
 create mode 100644 README.md
77b5783 (HEAD -> main) commit แรก: เพิ่ม README อธิบายโครงงาน

commit แรกมีเฉพาะ README.md ตามที่เรา add เท่านั้น ไฟล์อื่นยังเป็น untracked รอวันของตัวเองใน Lab D สัญลักษณ์ (HEAD -> main) ยืนยันว่า HEAD ชี้ branch main ซึ่งชี้ commit ใหม่นี้

จุดตรวจ A: git log --oneline แสดง commit หนึ่งแถวขึ้นต้นด้วย (root-commit) และ git status ยังแสดง untracked ที่เหลืออยู่ (ไม่มี .env ติดเข้าไปใน commit)

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
git commit ขึ้น Author identity unknown และ Please tell me who you areยังไม่ได้ตั้ง user.name / user.emailรันคำสั่ง config ใน A.2 ให้ครบแล้ว commit ใหม่
git init แล้วขึ้นข้อความยาวเกี่ยวกับ master และ init.defaultBranchใช้ Git ที่ยังไม่ได้ตั้ง defaultBranch (พบได้บน Ubuntu ที่ติดตั้งใหม่)ตั้งตาม A.2 แล้วเปลี่ยนชื่อ branch ปัจจุบันด้วย git branch -m main
warning: in the working copy of '...', LF will be replaced by CRLF ... ตอน git add บน WindowsGit for Windows ตั้ง core.autocrlf true ไว้ตั้งแต่ติดตั้ง จึงพยายามแปลงปลายบรรทัดตั้ง git config --global core.autocrlf false ตาม A.2 แล้ว add ใหม่ (ไม่เป็นอันตรายแม้ไม่แก้ แต่ผลจะต่างจากตัวอย่าง)
fatal: not a git repository (or any of the parent directories)รันคำสั่ง Git นอกโฟลเดอร์ที่ git init ไว้cd ~/devops-lab01 แล้วตรวจด้วย pwd

Lab B: branch และ merge

สถานการณ์: คุณต้องเพิ่มเอกสารสองเรื่องใน README.md คือหัวข้อ "วิธีใช้สคริปต์" กับการปรับคำอธิบายโครงงาน ตามกติกา Branch-First คุณทำงานแต่ละชิ้นบน branch ของมัน แล้ว merge กลับ main ด้วย merge commit ที่เห็นชัดในกราฟ

เป้าหมาย: LLO 2.1 สร้าง commit, branch และ merge ตามลำดับที่กำหนด

ฝึกตามตัวอย่าง

B.1 แตก branch และทำงานบนนั้น

BASH
git switch -c feature/usage
TEXT
Switched to a new branch 'feature/usage'

เปิด README.md ด้วย editor ที่คุณถนัด (เช่น nano README.md) แล้วเพิ่มหัวข้อต่อท้ายไฟล์

TEXT
## วิธีใช้สคริปต์
 
รันสคริปต์ตรวจ log ด้วยคำสั่ง
 
    ./scripts/check-errors.sh app.log

แล้ว commit บน branch นี้

BASH
git add README.md
git commit -m "เพิ่มหัวข้อวิธีใช้สคริปต์ใน README"
TEXT
[feature/usage ae00216] เพิ่มหัวข้อวิธีใช้สคริปต์ใน README
 1 file changed, 6 insertions(+)

สังเกตว่าบรรทัดผลเปลี่ยนจาก [main ...] เป็น [feature/usage ...] — commit ตกอยู่บน branch ที่คุณอยู่เท่านั้น

B.2 กลับ main และแก้คนละบริเวณกับ branch

BASH
git switch main
TEXT
Switched to branch 'main'

เปิด README.md อีกครั้ง จะพบว่าหัวข้อที่เพิ่งเขียนไปหายไปแล้ว เพราะ working tree ถูกคืนกลับมาตามสถานะของ main นี่คือพฤติกรรมปกติของ git switch (git-switch) ให้แก้บรรทัดที่สามของไฟล์ (บรรทัดคำอธิบายใต้ชื่อโครงงาน) จาก

TEXT
แบบฝึกหัดของรายวิชา 10306343 (DevOps) เริ่มจากบทที่ 1

เป็น

TEXT
แบบฝึกหัดของรายวิชา 10306343 (DevOps) ต่อยอดต่อเนื่องทุกบท

แล้ว commit

BASH
git add README.md
git commit -m "ปรับคำอธิบายโครงงานให้ตรงเป้าหมายของวิชา"
TEXT
[main 7d8f70b] ปรับคำอธิบายโครงงานให้ตรงเป้าหมายของวิชา
 1 file changed, 1 insertion(+), 1 deletion(-)

การแก้ครั้งนี้อยู่บรรทัดที่ 3 ส่วนของ branch feature/usage แตะเฉพาะท้ายไฟล์ สองบริเวณห่างกันพอที่ Git จะรวมให้เองได้ในขั้นตอนถัดไป (ถ้าแก้บริเวณเดียวกันจะเกิดอะไรขึ้น รอไว้ดูใน Lab C)

B.3 merge กลับด้วย --no-ff

BASH
git merge --no-ff feature/usage -m "รวม branch feature/usage: เพิ่มหัวข้อวิธีใช้"
TEXT
Auto-merging README.md
Merge made by the 'ort' strategy.
 README.md | 6 ++++++
 1 file changed, 6 insertions(+)

Auto-merging README.md คือ Git รวมสองฝั่งของไฟล์นี้ให้เอง เพราะแก้คนละบริเวณ ดูประวัติทั้งหมดเป็นกราฟ

BASH
git log --oneline --graph --all
TEXT
*   9690908 (HEAD -> main) รวม branch feature/usage: เพิ่มหัวข้อวิธีใช้
|\
| * ae00216 (feature/usage) เพิ่มหัวข้อวิธีใช้สคริปต์ใน README
* | 7d8f70b ปรับคำอธิบายโครงงานให้ตรงเป้าหมายของวิชา
|/
* 77b5783 commit แรก: เพิ่ม README อธิบายโครงงาน

อ่านกราฟจากบนลงล่าง: จุด * คือ commit, เส้น |\ และ |/ คือการแยกและบรรจบของ branch, commit 9690908 ที่ผ่านจากสองเส้นคือ merge commit ที่มีพ่อแม่สองตัว และ (HEAD -> main) บอกว่าคุณอยู่บน main ซึ่งชี้ commit ล่าสุดนี้

B.4 ลบ branch ที่รวมแล้ว

BASH
git branch -d feature/usage
git branch
TEXT
Deleted branch feature/usage (was ae00216).
* main

commit ของ branch ที่รวมแล้วไม่หายไปไหน มันถูกเลี้ยงด้วย merge commit บน main การลบเป็นแค่การเก็บตัวชี้ที่ไม่ใช้แล้ว ตัวเลือก -d (ตัวเล็ก) จะปฏิเสธถ้า branch ยังไม่ได้ถูกรวม ซึ่งเป็นเครื่องกันพลาดให้คุณ

จุดตรวจ B.1–B.4: git log --oneline --graph แสดงรูปสามเหลี่ยมหนึ่งอัน (merge commit หนึ่งตัว) README.md มีทั้งคำอธิบายที่แก้บน main และหัวข้อวิธีใช้จาก branch และ git branch เหลือแค่ * main

ตัดสินใจเอง B.5 (LLO 2.1): ทำงานชิ้นที่สองด้วยตัวเองทั้งชิ้น โดยไม่ดูคำสั่งใน B.1–B.4: แตก branch ใหม่ชื่อ feature/goals เพิ่มหัวข้อใหม่ท้าย README.md

TEXT
## ก้าวต่อไป
 
- บทที่ 3: ผลัก repository นี้ขึ้น GitHub แล้วทำงานเป็นทีม

commit, กลับ main, merge ด้วย --no-ff, ลบ branch แล้วดูกราฟ ข้อสังเกตที่ต้องตอบใน Learning Log: รอบนี้ main ไม่มี commit ใหม่เลยหลังแตก branch ถ้าไม่ใส่ --no-ff ผลจะต่างอย่างไร (ทดลองได้ แต่ให้จบด้วย merge commit ตามกติกาของวิชา)

ผลลัพธ์ที่คาดหวัง B.5: กราฟมีสามเหลี่ยมสองอันซ้อนกัน และ README.md มีหัวข้อก้าวต่อไป

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
สั่ง merge แล้วขึ้น Already up to date.อยู่บน branch ที่จะ merge (ทิศกลับ) หรือ merge ซ้ำอันที่รวมไปแล้วmerge ต้องสั่งจาก branch ปลายทาง ในบทนี้คืออยู่ main ก่อนสั่ง git merge feature/... ตรวจด้วย git branch ทุกครั้งก่อน merge
แก้ไฟล์และ commit ไปแล้ว ถึงคิดได้ว่าอยู่ branch ผิดไม่ดู branch ปัจจุบันก่อนเริ่มงานถ้า commit ไปแล้ว ให้สร้าง branch ที่ต้องการตอนนี้ (git switch -c ชื่อ) แล้วคืน main ด้วย git switch main — commit จะอยู่กับ branch ใหม่ที่สร้าง
สั่ง merge แล้วจอขึ้นโปรแกรมแก้ข้อความรอmerge commit ต้องการข้อความแต่ไม่ได้ใส่ -mพิมพ์ :wq แล้วกด Enter (ใน vi) หรือกำหนด -m "ข้อความ" ตั้งแต่แรกตามที่บทสอน
กราฟไม่เห็นเส้นแยกสองเส้น (|)merge เกิดแบบ fast-forward เพราะไม่ใส่ --no-ff หรืออยู่ branch ผิดตรวจคำสั่งว่าเป็น git merge --no-ff ... -m "..." และอยู่ main
merge แล้วเกิด CONFLICT ทั้งที่คิดว่าแก้คนละที่บริเวณที่สองฝั่งแก้อยู่ติดกันหรือทับกัน (เช่น ฝั่งหนึ่งแก้บรรทัดท้ายไฟล์ อีกฝั่งเพิ่งต่อท้ายไฟล์)ถ้ายังไม่ถึง Lab C ให้ git merge --abort แล้วขยับจุดแก้ให้ห่างกัน หรืออ่าน Lab C แล้วแก้ไปเลย

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab C: conflict จำลอง

สถานการณ์: คุณกับเพื่อนร่วมทีมแก้หัวข้อเดียวกันพร้อมกันบนสอง branch ในทีมจริงจะเกิดจากสองคน ที่นี่คุณเล่นทั้งสองบทบาทเอง เพื่อให้เจอ conflict จริงและได้ฝึกแก้ตามคู่มือทางการ

เป้าหมาย: LLO 2.2 แก้ merge conflict จนไฟล์ได้ผลตามที่กำหนด โดยใช้ข้อมูลจากเอกสาร git ทางการ

ฝึกตามตัวอย่าง

C.1 ฝั่งแรก: เปลี่ยนชื่อโครงการบน branch ใหม่

BASH
git switch -c feature/title

เปิด README.md แก้บรรทัดแรกจาก # DevOps Lab 01 เป็น

TEXT
# สมุดบันทึกการทดลอง DevOps

commit ด้วยข้อความ เปลี่ยนชื่อโครงการเป็นสมุดบันทึกการทดลอง

C.2 ฝั่งที่สอง: แก้บรรทัดเดิมบน main

BASH
git switch main

แก้บรรทัดแรกของ README.md (ซึ่งตอนนี้ยังเป็น # DevOps Lab 01 เพราะงานของ branch ยังไม่มาถึง) เป็น

TEXT
# DevOps Lab 01 (10306343)

commit ด้วยข้อความ ใส่รหัสรายวิชาในชื่อโครงการ

C.3 merge แล้วเจอ conflict จริง

BASH
git merge --no-ff feature/title -m "รวม branch feature/title"
TEXT
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

Git บอกตรง ๆ ว่าผสมสองฝั่งของ README.md ไม่ได้ เพราะทั้งคู่แก้บรรทัดเดียวกันด้วยข้อความต่างกัน ดูสถานะและตัวไฟล์

BASH
git status
head README.md
TEXT
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)
 
Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   README.md
TEXT
<<<<<<< HEAD
# DevOps Lab 01 (10306343)
=======
# สมุดบันทึกการทดลอง DevOps
>>>>>>> feature/title
 
แบบฝึกหัดของรายวิชา 10306343 (DevOps) ต่อยอดต่อเนื่องทุกบท

ไฟล์ไม่พัง และไม่มีอะไรถูก commit ไปไหน Git แค่คั่นสองฝั่งไว้ให้คุณตัดสิน (หัวข้อ 2.5) ฝั่งบนคือของ main ที่คุณอยู่ ฝั่งล่างคือของ feature/title

C.4 ทางหนี: abort แล้ว merge ใหม่

ก่อนจะแก้ ให้ลองทางออกแรกจากเอกสารทางการ (git-merge) สักครั้งเพื่อรู้จักไว้

BASH
git merge --abort
git status --short
git merge --no-ff feature/title -m "รวม branch feature/title"

คำสั่งแรกคืนทุกอย่างกลับไปก่อน merge (คำสั่งที่สองจึงเหลือแต่ untracked เดิม ๆ) แล้วเราสั่ง merge เข้ามาใหม่เพื่อกลับมาที่ conflict เหมือนเดิม ทางออกนี้ใช้เมื่อคุณยังไม่พร้อมแก้ เช่น อยากไปคุยกับเพื่อนร่วม branch ก่อน

C.5 แก้ conflict ให้จบและปิด merge

ตกลงกันเองหรือกับทีมว่าชื่อไหนถูก ข้อกำหนดของ Lab นี้คือใช้ฝั่ง main เปิด README.md แล้วแก้บรรทัดแรกให้เหลือเพียง

TEXT
# DevOps Lab 01 (10306343)

โดยลบเครื่องหมาย <<<<<<< HEAD, =======, >>>>>>> feature/title และข้อความฝั่ง branch ออกให้หมด จากนั้นตรวจว่าไม่มีเครื่องหมายค้างด้วย grep จากบทที่ 1

BASH
grep -n -E "^(<<<<<<<|=======|>>>>>>>)" README.md
echo "grep exit=$?"
TEXT
grep exit=1

exit 1 คือ "ไม่พบ" ซึ่งเป็นผลที่ต้องการ (ระวัง: รูปแบบนี้ตรงกับบรรทัดที่ขึ้นต้นด้วยเครื่องหมายจริง ๆ ของ marker เท่านั้น ไม่ตรงกับเนื้อหาปกติของ README) แล้วปิดการ merge ตามขั้นที่เอกสารกำหนด: git add ไฟล์ที่แก้ แล้ว git commit

BASH
git add README.md
git commit -m "แก้ conflict ใน README.md: เลือกใช้ชื่อโครงการของฝั่ง main"
git branch -d feature/title
git log --oneline --graph | head -12
TEXT
[main 51fccf4] แก้ conflict ใน README.md: เลือกใช้ชื่อโครงการของฝั่ง main
Deleted branch feature/title (was 992146d).
*   51fccf4 (HEAD -> main) แก้ conflict ใน README.md: เลือกใช้ชื่อโครงการของฝั่ง main
|\
| * 992146d เปลี่ยนชื่อโครงการเป็นสมุดบันทึกการทดลอง
* | 229fb9a ใส่รหัสรายวิชาในชื่อโครงงาน
|/
*   e47c37f รวม branch feature/goals: เพิ่มหัวข้อก้าวต่อไป

สังเกตว่า commit ที่ปิด merge conflict มีสองพ่อแม่เหมือน merge commit ปกติ และทีมตรวจงานย้อนหลังได้ว่า commit นี้เกิดจากการแก้ conflict

จุดตรวจ C.1–C.5: grep ไม่เจอ marker (exit 1), บรรทัดแรกของ README.md เป็น # DevOps Lab 01 (10306343) และ git status กลับไปสะอาด (เหลือแต่ untracked เดิม)

ตัดสินใจเอง C.6 (LLO 2.2): จำลอง conflict อีกครั้งด้วยตัวเอง แต่รอบนี้ให้แก้ด้วยวิธี "รวมความหมายทั้งสองฝั่ง" แทนการเลือกฝั่งเดียว

  1. แตก branch ใหม่ (ตั้งชื่อเอง เช่น feature/filelist) แล้วแก้หัวข้อ ## ไฟล์ในโครงงาน ของ README.md ให้สั้นลงเป็น ## รายการไฟล์ แล้ว commit
  2. กลับ main แก้หัวข้อเดิมเป็น ## ไฟล์ในโครงงาน (จากบทที่ 1) แล้ว commit
  3. merge ให้เกิด conflict แล้วแก้โดยให้เหลือหัวข้อเดียวคือ ## รายการไฟล์จากบทที่ 1 (รวมทั้งสองฝั่ง) ตรวจด้วย grep ว่าไม่เหลือ marker แล้ว commit ปิด merge
  4. เปิดเอกสารทางการ git-merge หัวข้อ HOW TO RESOLVE CONFLICTS (และ/หรือ Pro Git §3.2) แล้วเขียนบันทึก 2–3 บรรทัดว่าอ่านหัวข้อใด และเข้าใจอะไรเพิ่มจากตอนทำ hands-on — บันทึกนี้ใช้ส่งงานข้อ 2

ผลลัพธ์ที่คาดหวัง C.6: README.md มีหัวข้อ ## รายการไฟล์จากบทที่ 1 หนึ่งเดียว ไม่มี marker เหลือ มี commit ปิด merge ตัวที่สองในประวัติ และมีบันทึกแหล่งทางการที่อ่าน

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
merge แล้วขึ้น Already up to date. ไม่มี conflictสองฝั่งแก้คนละบริเวณ Git รวมให้แล้ว หรือขืน commit ฝั่งหนึ่งไม่ครบตรวจว่าแก้บรรทัดเดียวกันจริง และ commit ครบทั้งสองฝั่งก่อน merge
git commit ตอน conflict ขึ้น error: Committing is not possible because you have unmerged filesยังไม่ได้ git add ไฟล์ที่แก้เสร็จแก้ไฟล์ให้จบก่อน แล้ว git add README.md แล้วจึง commit
commit แล้วเปิดไฟล์อีกครั้งยังเห็น <<<<<<<ลบเครื่องหมายหรือข้อความฝั่งหนึ่งไม่ครบใช้ grep -n -E "^(<<<<<<<|=======|>>>>>>>)" README.md หาตำแหน่ง แล้วแก้และ commit การแก้ไขซ้ำเป็น commit ใหม่
อยู่ๆ อยากย้อนกลับตอนกำลังแก้ conflictยังไม่พร้อม หรือแก้พังgit merge --abort ใช้ได้เฉพาะก่อน commit ปิด merge เท่านั้น
เผลอเรียก git add . ตอนแก้ conflictความชินมือยังไม่พังถ้าทำตามบท (ไฟล์ลับจะถูก stage แต่ยังไม่ถูก commit) — git restore --staged .env คืนสถานะได้ และตรวจ git status ก่อน commit เสมอ

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab D: .gitignore, สคริปต์เข้า repository และ tag v0.1.0

สถานการณ์: ก่อนส่งมอบ repository ให้ทีมในบทที่ 3 คุณต้องแยกให้ออกว่าไฟล์ไหนควรเก็บเข้า repository ไฟล์ไหนต้องไม่เข้าเด็ดขาด แล้วปักหมุดสถานะสมบูรณ์ของบทนี้ด้วย tag เป็น milestone แรกของโครงงานสะสม

เป้าหมาย: แยกไฟล์ที่ควรเก็บ/ไม่ควรเก็บด้วย .gitignore และปักหมุดเวอร์ชันด้วย tag (เตรียมพื้นสำหรับบทที่ 3 และ 5 ไม่นับ CLO)

ฝึกตามตัวอย่าง

D.1 สำรวจสิ่งที่เหลืออยู่

BASH
git status
TEXT
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.env
	app.log
	app.log.bak
	clean.log
	hello.sh
	scripts/
 
nothing added to commit but untracked files present (use "git add" to track)

จัดกลุ่มไฟล์ที่เหลือตามที่มาจากบทที่ 1

ไฟล์ที่มาควรเข้า repository ไหม
scripts/check-errors.shLab E ของบท 1 (แก้ตาม E.3 แล้ว)ใช่ จะเป็นขั้นตรวจใน CI บทที่ 5
hello.shLab B ของบท 1ใช่ เป็นสคริปต์ใช้งานได้จริง
app.logLab C ของบท 1ไม่ เป็นข้อมูลตัวอย่างที่สร้างใหม่ได้
app.log.bakไฟล์สำรองจาก sed -i.bak ในบท 1ไม่ ไฟล์สำรองชั่วคราว
clean.logผลจาก Lab E ของบท 1ไม่ เป็นผลรันที่สร้างใหม่ได้
.envไฟล์รหัสผ่านสมมติจาก Lab B ของบท 1ห้าม ไฟล์ลับต้องไม่ติดเข้า repository เด็ดขาด

หลักคิด: เก็บสิ่งที่ต้องใช้ทำงานต่อ (โค้ด สคริปต์ เอกสาร) ไม่เก็บสิ่งที่เครื่องสร้างใหม่ได้เอง (ผลรัน, log, ไฟล์สำรอง) และไม่เก็บความลับเด็ดขาด

D.2 เก็บสคริปต์ที่ใช้งานจริงเข้า repository

BASH
git add scripts/check-errors.sh hello.sh
git commit -m "เก็บสคริปต์ของบทที่ 1 เข้า repository"
git ls-files
TEXT
[main bfa5a36] เก็บสคริปต์ของบทที่ 1 เข้า repository
 2 files changed, 14 insertions(+)
 create mode 100644 hello.sh
 create mode 100644 scripts/check-errors.sh
README.md
hello.sh
scripts/check-errors.sh

git ls-files แสดงไฟล์ทั้งหมดที่อยู่ใน repository — ตอนนี้มีสามไฟล์ และไม่มี .env

D.3 รู้จักและเริ่ม .gitignore

ไฟล์ .gitignore เป็นรายการชื่อไฟล์ที่ Git จะทิ้งไว้ ไม่ตามเล่น หนึ่งรายการต่อบรรทัด ใช้ * แทนอะไรก็ได้ เช่น *.bak คือไฟล์ใด ๆ ที่ลงท้าย .bak เริ่มจากสองรายการแรก

BASH
printf 'app.log\n*.bak\n' > .gitignore
git status --short
TEXT
?? .env
?? .gitignore
?? clean.log

app.log กับ app.log.bak หายไปจากสายตาของ Git แล้ว เหลือ .env กับ clean.log ที่ยังไม่มีในรายการ (ตัว .gitignore เองจะถูกเก็บเข้า repository เหมือนไฟล์ปกติ เพื่อให้ทีมได้กติกาเดียวกัน)

ตัดสินใจเอง D.4 (เตรียมพื้น): เติม .gitignore ให้ครบด้วยตัวเอง แล้วปิดบทด้วย tag

  1. คิดว่า clean.log ควรใส่เป็นชื่อตรงหรือใช้แพเทิร์น *.log ดี (ข้อดี-ข้อเสียต่างกันอย่างไร) และ .env ต้องอยู่ในรายการเสมอ เพราะเหตุใด
  2. ตรวจว่าครบด้วย git check-ignore .env app.log app.log.bak clean.log — คำสั่งนี้พิมพ์เฉพาะไฟล์ที่ถูกกัน
  3. commit .gitignore ด้วยข้อความ เพิ่ม .gitignore กันไฟล์ log และไฟล์ลับ
  4. ปิดบทด้วย annotated tag และดูภาพรวมสุดท้าย:
BASH
git tag -a v0.1.0 -m "รุ่นสมบูรณ์ของบทที่ 2 (M1): repository, .gitignore, สคริปต์ตรวจ log"
git tag
git show v0.1.0 --stat | head -8
git log --oneline --graph --all
git status

tag คือชื่อติดกับ commit หนึ่งเพื่อใช้อ้างอิงเวอร์ชัน แบบ annotated (สร้างด้วย -a) เก็บชื่อผู้ติด tag วันที่และข้อความเป็นวัตถุของตัวเอง เอกสารทางการแนะนำให้ใช้แบบนี้กับ release (git-tag) เมื่อ commit ใหม่เกิดขึ้นภายหลัง tag ยังชี้ commit เดิม — นี่คือสิ่งที่ทำให้ tag ใช้ย้อนกลับมา "รุ่น v0.1.0" ได้เสมอ และเป็นพื้นของการติด tag image ในบทที่ 8

สำหรับคุณ v0.1.0 คือ M1 (milestone 1) ของโครงงานสะสมของวิชา: repository เดี่ยวที่เกิดจากแอปตั้งต้น มีประวัติ commit, branch และ merge ครบ มี .gitignore กันไฟล์ลับ และมี scripts/check-errors.sh พร้อมใช้ แบบเดียวกับที่บทที่ 1 บอกคุณไว้ว่า check-errors.sh จะถูกเก็บเข้า repository และใช้ต่อในบทที่ 5 — ตอนนี้มันเป็นความจริงแล้ว บทที่ 3 ทั้งทีมจะเริ่มจาก repository แบบนี้ของทุกคน

ผลลัพธ์ที่คาดหวัง D.4: git check-ignore พิมพ์ครบสี่ไฟล์, git status จบด้วย nothing to commit, working tree clean, git tag แสดง v0.1.0 และกราฟสุดท้ายมีหน้าตาแบบนี้ (hash ของคุณต่างกัน)

TEXT
* 90db269 (HEAD -> main, tag: v0.1.0) เพิ่ม .gitignore กันไฟล์ log และไฟล์ลับ
* bfa5a36 เก็บสคริปต์ของบทที่ 1 เข้า repository
*   aa4337e แก้ conflict หัวข้อรายการไฟล์: รวมความหมายทั้งสองฝั่ง
|\
| * 57237c4 เปลี่ยนหัวข้อรายการไฟล์ให้สั้นลง
* | 86585a0 ระบุที่มาของรายการไฟล์
|/
*   51fccf4 แก้ conflict ใน README.md: เลือกใช้ชื่อโครงการของฝั่ง main
|\
| * 992146d เปลี่ยนชื่อโครงการเป็นสมุดบันทึกการทดลอง
* | 229fb9a ใส่รหัสรายวิชาในชื่อโครงงาน
|/
*   e47c37f รวม branch feature/goals: เพิ่มหัวข้อก้าวต่อไป
|\
| * 8fbcd09 เพิ่มหัวข้อก้าวต่อไปใน README
|/
*   9690908 รวม branch feature/usage: เพิ่มหัวข้อวิธีใช้
|\
| * ae00216 เพิ่มหัวข้อวิธีใช้สคริปต์ใน README
* | 7d8f70b ปรับคำอธิบายโครงงานให้ตรงเป้าหมายของวิชา
|/
* 77b5783 commit แรก: เพิ่ม README อธิบายโครงงาน

ภาพนี้คือหลักฐานหลักของงานข้อ 1 (M1) ให้ถ่ายรูปเก็บไว้ตอนนี้เลย

จุดตรวจ D: git ls-files ไม่มี .env และไฟล์ log, git status สะอาด, git tag มี v0.1.0

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
เพิ่มชื่อไฟล์ใน .gitignore แล้ว git status ยังแสดงไฟล์นั้นไฟล์ถูก git add และ commit ไปก่อนแล้ว .gitignore มีผลเฉพาะไฟล์ที่ยังไม่ถูก trackgit rm --cached ชื่อไฟล์ เอาออกจาก staging ของ Git (ไฟล์จริงยังอยู่) แล้ว commit การเอาออก — ถ้าเป็นไฟล์ลับที่ติดไปแล้ว ให้แจ้งผู้สอนทันที
git check-ignore .env ไม่พิมพ์อะไรรายการใน .gitignore พิมพ์ผิด หรือมีช่องว่างนำหน้าตรวจไฟล์ด้วย cat .gitignore แต่ละบรรทัดต้องชิดซ้าย
tag ข้อความพิมพ์ผิด อยากลบทำใหม่พิมพ์เร็วไปgit tag -d v0.1.0 ลบ แล้วติดใหม่ (ทำได้เพราะยังไม่ได้ผลัก tag ขึ้นไหน)
ใส่ *.log แล้วกังวลว่าจะกันไฟล์ที่ทีมต้องการแพเทิร์นกว้างไปนี่คือการ trade-off ของข้อ 1 ใน D.4 ถ้าทีมต้องการเก็บ log บางชนิดไว้ ให้ระบุชื่อเฉพาะแทน หรือใช้ exception ใน .gitignore (นอกขอบเขตบทนี้ รีวิวกับทีมในบทที่ 3)

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

เก็บกวาดเมื่อจบ Lab

Lab ของบทนี้ไม่มีอะไรต้องลบ — สิ่งที่ได้คือ repository ของคุณเอง ให้ยืนยันสถานะส่งมอบก่อนจบวัน

BASH
git status
git log --oneline --graph --all | head -5
git tag

สิ่งที่ต้องเห็น: nothing to commit, working tree clean, กราฟมี merge commit อย่างน้อยสองตัว (จาก Lab B และ B.5) และ commit แก้ conflict (จาก Lab C และ C.6) และมี tag v0.1.0 เก็บโฟลเดอร์ ~/devops-lab01 ทั้งโฟลเดอร์ไว้ บทที่ 3 จะผลักมันขึ้น GitHub และใช้ต่อทั้งทีมจนจบภาคเรียน

แบบฝึกหัด

ทำข้อ ก1–ก2 และ ข1 ในชั้นเรียนถ้ามีเวลาเหลือ ข้อที่เหลือทำในเวลาศึกษาด้วยตนเอง ทุกข้อระบุผลลัพธ์การเรียนรู้ที่ฝึก

ก. ความเข้าใจ

  • ก1 (LLO 2.1) จากกราฟต่อไปนี้ (เก็บมาจาก Lab B ก่อนลบ branch) ตอบ 3 ข้อ
TEXT
*   9690908 (HEAD -> main) รวม branch feature/usage: เพิ่มหัวข้อวิธีใช้
|\
| * ae00216 (feature/usage) เพิ่มหัวข้อวิธีใช้สคริปต์ใน README
* | 7d8f70b ปรับคำอธิบายโครงงานให้ตรงเป้าหมายของวิชา
|/
* 77b5783 commit แรก: เพิ่ม README อธิบายโครงงาน

(1) commit ใดคือ merge commit และรู้ได้อย่างไร (2) commit ae00216 อยู่บน branch ใด (3) ผู้ใช้นี้กำลังอยู่บน branch ไหน และรู้ได้จากอะไร

  • ก2 (LLO 2.1) ไฟล์หนึ่งถูกแก้แล้วแต่ยังไม่ได้ git add — ไฟล์นั้นอยู่พื้นที่ใดของสามพื้นที่ และมีสถานะว่าอะไร
  • ก3 (พื้นความรู้ ไม่นับ CLO) ทีมเก็บโค้ดไว้ 3 ที่ (เครื่องละคนและโฟลเดอร์แชร์หนึ่ง) อธิบาย 2–3 บรรทัดว่าปัญหาอะไรจะเกิดขึ้น และ SSOT ช่วยอย่างไร
  • ก4 (เตรียมพื้น) ไฟล์ต่อไปนี้ไฟล์ใดบ้างที่ควรอยู่ใน .gitignore ของโครงงานคล้ายของเรา: README.md, .env, clean.log, scripts/check-errors.sh, app.log.bak

ข. การประยุกต์

  • ข1 (LLO 2.1) เขียนลำดับคำสั่งทั้งชุด (อย่างน้อย 7 คำสั่ง) สำหรับงานหนึ่งชิ้นตามกติกา Branch-First โดยไม่เปิดดูบท: เริ่มจากการอยู่บน main ที่สะอาด จนได้ merge commit และ branch ถูกลบ
  • ข2 (LLO 2.2) ไฟล์ที่กำลัง conflict มีบริเวณหนึ่งเป็น
TEXT
<<<<<<< HEAD
เวอร์ชัน: 1.5.0
=======
เวอร์ชัน: 1.6.0
>>>>>>> release

ทีมตกลงให้ใช้ 1.6.0 เขียนว่าบรรทัดใดบ้างที่ต้องลบ และผลลัพธ์สุดท้ายของบริเวณนี้เป็นอย่างไร

  • ข3 (LLO 2.2) เพื่อนสั่ง merge แล้วเจอ conflict แต่กลัวว่าจะทำพังเลยไม่กล้าแตะอะไร แนะนำเพื่อน 2 ทางเลือกพร้อมเงื่อนไขว่าแต่ละทางใช้ได้ถึงเมื่อไร (อ้างจากเอกสารทางการ)

ค. การวิเคราะห์ปัญหา

  • ค1 (LLO 2.1) เพื่อนอีกคนทำ Lab B เสร็จแล้วบอกว่า "merge สำเร็จ แต่กราฟเป็นเส้นตรง ไม่มีสามเหลี่ยมเหมือนของคุณ" อธิบายสาเหตุ และบอกว่าต้องทำอย่างไรกราฟจะเห็น merge commit
  • ค2 (LLO 2.2) เพื่อนแก้ conflict จน commit ผ่านไปแล้ว แต่พอเปิดไฟล์ดูพบข้อความ ======= ปนอยู่กลางไฟล์ และแอปทำงานผิดปกติ บอกสาเหตุ คำสั่งที่ใช้ตรวจหาจุดผิดทั้งไฟล์ และวิธีแก้ให้เรียบร้อย
  • ค3 (เตรียมพื้น) เพื่อนใจร้อนสั่ง git add . ตอนทำ Lab A จน .env ติด commit แรกไปแล้ว จึงมาเพิ่ม .env ใน .gitignore ทีหลัง (Lab D) — .env จะหายไปจากการถูก track หรือไม่ เพราะเหตุใด และควรแก้อย่างไร (คำใบ้: git ls-files และ git rm --cached)

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

ส่วนที่ 3 ศึกษาด้วยตนเอง

  1. อ่าน Pro Git บทที่ 2 Git Basics และ บทที่ 3 Git Branching แล้วสรุปแบบของคุณเองอย่างละ 5 บรรทัดว่าแต่ละบทสอนอะไรที่เกินจากที่ทำใน Lab
  2. ทำแบบฝึกหัดที่เหลือให้ครบ และทำ Lab B ซ้ำหนึ่งรอบโดยไม่เปิดดูคำสั่ง (ใช้ branch ชื่อใหม่ แล้วลบทิ้งเมื่อเสร็จ)
  3. เขียนงานที่ต้องส่งให้ครบทั้ง 4 ข้อ
  4. เขียน Learning Log ครั้งที่ 2 ตามหัวข้อในงานข้อ 4
  5. (ไม่บังคับ) เล่นเกม Oh My Git! ซึ่งให้ฝึกอ่านกราฟ commit และเคลื่อน branch ด้วยภาพ ผ่านด่านแรก ๆ ช่วยให้อ่าน git log --graph คล่องขึ้น
  6. (ไม่บังคับ) อ่าน Pro Git §3.6 Rebasing — วิชานี้ใช้ merge อย่างเดียวในแกนหลัก rebase เป็นภาคผนวกสำหรับผู้ที่อยากรู้ เพราะยากสำหรับผู้เริ่มต้น ถ้าอ่านแล้วลองสรุปว่า rebase กับ merge ต่างกันตรงไหน

งานที่ต้องส่ง

กำหนดส่ง: ก่อนเรียนบทที่ 4 เพื่อเว้นบัฟเฟอร์ 1 บท เช่นเดียวกับบทที่ 1 ที่ให้ส่งก่อนบทที่ 3

ข้องานหลักฐานผลลัพธ์CLO
1M1 ประวัติ repository ของคุณภาพหรือข้อความผล git log --oneline --graph --all จากจุดสิ้นสุด Lab D พร้อมผล git tag และ hash ของ commit ที่ tag v0.1.0 ชี้2.1CLO3
2การแก้ merge conflictผล merge ที่เกิด conflict (มีบรรทัด CONFLICT) และไฟล์ตอนมี marker, บรรทัดแรก/หัวข้อของไฟล์หลังแก้, hash ของ commit ที่แก้ conflict (C.5 และ C.6) และบันทึกแหล่งข้อมูลทางการที่อ่าน (URL + หัวข้อ + สิ่งที่เข้าใจเพิ่ม)2.2CLO3
3.gitignore และ tagเนื้อหา .gitignore พร้อมเหตุผลโดยละเอียดว่ากันอะไรบ้างเพราะอะไร, ผล git check-ignore และ git status ที่สะอาด, ผล git show v0.1.0 --statเตรียมพื้นยังไม่นับ CLO ใช้ต่อในบทที่ 3 และ 5
4Learning Log ครั้งที่ 2ตอบ 4 หัวข้อ: วันนี้เรียนอะไร · คำสั่งหรือสถานะของ Git ใดยังไม่เข้าใจ · หาคำตอบจากแหล่งใด (ใส่ลิงก์เอกสารทางการอย่างน้อย 1 แหล่ง) · ถ้าต้องสอนเพื่อน จะอธิบาย conflict markers สามตัวอย่างไร—สะสม portfolio ประเมินรวมบทที่ 13

เกณฑ์ให้คะแนน

ข้อผ่านยังไม่ผ่าน
1กราฟมีโครงสร้างครบตามที่กำหนด: commit แรก, merge commit อย่างน้อย 2 ตัวจาก branch จริง (Lab B และ B.5), commit แก้ conflict อย่างน้อย 2 ตัว (Lab C และ C.6) และ tag v0.1.0 ชี้ commit สุดท้าย (แก้แล้วส่งใหม่ได้ 1 ครั้ง)ขาด merge commit หรือ commit แก้ conflict หรือไม่มี tag หลังส่งใหม่
2มีผลก่อนแก้และหลังแก้ครบ, ไม่มี marker เหลือ, ไฟล์ตรงข้อความที่กำหนด และมีบันทึกแหล่งทางการพร้อมหัวข้อเหลือ marker, ไฟล์ไม่ตรง หรือไม่มีแหล่งอ้างอิงทางการ
3.env ถูกกัน, มีเหตุผลประกอบรายการ, status สะอาด และมี tag v0.1.0ขาดข้อใดข้อหนึ่งจากสี่อย่าง
4ตอบครบ 4 หัวข้อ และมีลิงก์เอกสารทางการขาดหัวข้อ หรือไม่มีแหล่งอ้างอิง

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

สรุปบทและสิ่งที่ส่งต่อไปยัง Capstone

  • ประวัติของ Git เก็บเป็น snapshot ของไฟล์ทั้งชุด ไฟล์เดินผ่านสามพื้นที่: working tree (แก้) → staging (คัดด้วย git add) → repository (เก็บด้วย git commit)
  • branch คือตัวชี้ ๆ เดียวไปที่ commit สร้างถูกมาก ไม่ต้องกลัว งานทุกชิ้นของวิชานี้เริ่มบน branch ใหม่ (Branch-First) แล้ว merge กลับ main ด้วย --no-ff เพื่อให้เห็น merge commit ในกราฟ
  • merge เป็น fast-forward เมื่อปลายทางไม่มี commit ใหม่ และเป็น three-way merge เมื่อสองฝั่งแยกทางกัน
  • conflict เกิดเมื่อสองฝั่งแก้บริเวณเดียวกันต่างกัน Git หยุดรอคนตัดสิน แก้ด้วย git merge --abort หรือแก้ไฟล์ → git add → git commit และตรวจว่าไม่เหลือ marker ด้วย grep
  • .gitignore กันไฟล์ที่เครื่องสร้างใหม่ได้และไฟล์ลับออกจาก repository ใช้ได้เฉพาะไฟล์ที่ยังไม่ถูก track
  • tag annotated (git tag -a) ปักหมุดชื่อไว้กับ commit เพื่ออ้างอิงเวอร์ชัน v0.1.0 ของคุณคือ M1

สิ่งที่ส่งต่อไปยังโครงงานและ Capstone

สิ่งที่ได้ไปอยู่ที่
repository ของคุณที่มีประวัติ commit/branch/merge, .gitignore และ tag v0.1.0 (M1)บทที่ 3 ผลักขึ้น GitHub แล้ว branch หลักของคุณจะกลายเป็น main บน GitHub ของทีม · ใช้ต่อทุกบทถึง Capstone
scripts/check-errors.sh ใน repositoryบทที่ 5 ใช้เป็นขั้นตรวจใน CI (M1 ถูกใช้ต่อในบทที่ 5)
นิสัยกันไฟล์ลับด้วย .gitignore และการระบุชื่อไฟล์ตอน git add แทน git add .บทที่ 9 ตรวจว่าไม่พบรหัสผ่านใน repository · บทที่ 10 ย้ายความลับไป GitHub Secrets
tag กับเวอร์ชันบทที่ 4 ปรับเลขเวอร์ชันเป็น 0.2.0 · บทที่ 8 ติด tag ให้ image ตามเวอร์ชัน
Learning Log ครั้งที่ 2portfolio ที่ส่งในบทที่ 13

สรุปคำสั่งในบทนี้

คำสั่งใช้ทำอะไร
git initสร้าง repository ในโฟลเดอร์ปัจจุบัน
git config --global user.name / user.email / init.defaultBranch / core.autocrlfตั้งค่าระดับผู้ใช้ครั้งเดียว (ตัวตน, ชื่อ branch แรก, การแปลงปลายบรรทัด)
git status, git status --shortดูสถานะไฟล์ว่าอยู่พื้นที่ใด
git add ชื่อไฟล์ยกไฟล์ขึ้น staging (ระบุชื่อเสมอ จนกว่าจะมี .gitignore)
git commit -m "ข้อความ"ถ่ายรูปสิ่งที่อยู่บน staging ลงประวัติ
git log --oneline --graph --allดูประวัติเป็นกราฟทั้งหมด (ใช้ตรวจงานและส่ง M1)
git diff, git diff --stagedดูการแก้ที่ยังไม่ stage และสิ่งที่กำลังจะติด commit
git switch -c ชื่อbranch, git switch ชื่อbranchสร้าง branch ใหม่พร้อมสลับไป และสลับ branch
git branch, git branch -d ชื่อbranch, git branch -m mainดูรายการ, ลบ branch ที่รวมแล้ว, เปลี่ยนชื่อ branch ปัจจุบัน
git merge --no-ff ชื่อbranch -m "..."รวม branch เข้า branch ปัจจุบันโดยบังคับ merge commit
git merge --abortยกเลิก merge ที่ค้าง conflict (ใช้ก่อน commit ปิดการ merge)
git ls-filesดูรายชื่อไฟล์ที่ถูก track ทั้งหมด (ตรวจว่าไฟล์ลับไม่ติดเข้ามา)
git check-ignore ไฟล์...ตรวจว่าไฟล์ใดถูก .gitignore กันอยู่
git tag -a v0.1.0 -m "...", git tag, git show v0.1.0 --statติด annotated tag, ดูรายการ tag, ดูรายละเอียดของ tag
grep -n -E "^(<<<<<<<|=======|>>>>>>>)" ไฟล์ตรวจว่าไม่เหลือ conflict marker (จากบทที่ 1)

By Watcharin Sarachai