Git คือ โปรแกรมระบบจัดการโค้ดโปรแกรม (Source Code Management System - SCM) ซึ่งระบบ SCM ส่วนใหญ่แล้วมีหน้าทีคือ
- จัดเก็บโค้ด
- สามารถจัดเก็บโค้ดได้เป็น commit ย่อยๆ
- เมื่อมีข้อผิดพลาด สามารถถอยกลับไป commit ก่อนหน้าได้
- เมื่อทำงานร่วมกันหลายคน จะเก็บข้อมูลการแก้ไขเอาไว้ ว่าใครแก้อะไรตรงไหนไปบ้างแล้ว
- เมื่อทำงานหลายคน สามารถซิ้งค์โค้ดร่วมกันได้ โดยไม่ตรงกังวลว่าจะมีการเขียนไฟล์ทับกันแล้วข้อมูลหาย
- Git ทำงานแบบ distributed คือเก็บข้อมูลการเปลี่ยนแปลงไว้ที่เครื่องของผู้ใช้งาน ไม่จำเป็นต้องมี server กลาง
- สามารถสร้าง Git Repository server ได้เพื่อให้เป็นตัวกลางในการ sync แต่ข้อมูลบน Repositry คือ copy อีกหนึ่งชุดนั่นเอง
- ทุกวันนี้ source code ของโครงการใหญ่ๆ และบริษัทใหญ่ๆ ใช้ git เป็นจำนวนมาก และ ใช้บริการของ github ทำให้เราสามารถเข้าไปร่วมพัฒนา หรือ clone ลงมาใช้งานได้ง่ายถ้าเรามีความรู้เรื่อง Git
ตัวอย่างในการฝึก git วันนี้ เราจะทำ Web Applications ที่ชื่อว่า TodoApp กันนะครับ โดยที่เป็น App จัดการลิสต์งานนั่นเอง เราจะเขียนด้วย HTML/CSS/Javascript กัน ไม่ได้ใช้ส่วน server นะครับ เพื่อให้ง่ายต่อการทำความเข้าใจ เพราะเราเอามาใช้เป็นโจทย์ฝึกใช้ Git กันนะครับ
เริ่มแรกให้เราสร้าง folder สำหรับ project ใหม่ของเราก่อนนะครับ ตั้งชื่อว่า todoapp
mkdir todoapp
หลังจากนั้นเราจะสร้าง git repository เพื่อให้ git track และ จัดเก็บ การเปลี่ยนแปลงของ source code ของ todoapp ของเรานะครับ โดยหลังจากเราสร้าง folder แล้วให้เราเข้าไปที่ folder todoapp แล้วสั่ง git init
cd todoapp
git init
หลังจากนั้นให้เราลองสั่ง git status
ดูครับ เราจะเห็น git บอกเราแบบนี้
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
ที่เป็นแบบนี้เพราะเราเพิ่งสร้าง git repository และ folder todoapp ของเราเองก็ยังไม่ได้สร้างไฟล์อะไร ให้เราสร้างไฟล์ index.html แล้วลองเพิ่มโค้ดเข้าไปดังนี้ครับ
<!doctype html>
<html>
<head>
<title>Todo</title>
</head>
<body>
Hello World
</body>
</html>
หลังจากนั้นให้เราลองสั่ง git status
อีกครั้งเราจะเห็นแบบนี้
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.html
nothing added to commit but untracked files present (use "git add" to track)
จะเห็นว่า git status
บอกเราว่าไฟล์ index.html
ของเรานั้นเป็น Untracked files
ความหมายของ Untracked files ก็คือไฟล์ที่ยังไม่เคยจัดเก็บเข้าสู่ฐานข้อมูลของ git
นั่นเองครับ คือไฟล์ที่เราสร้างใหม่ ยังไม่เคยได้ commit มันเก็บไว้มาก่อนเลย
การสร้างการบันทึกการเปลี่ยนแปลงของ git หรือการสร้าง commit ใหม่ของ git นั้น เราต้องจัดการเพิ่มไฟล์ที่ต้องการ commit เข้าสู่ส่วนสถานะ staging ก่อน โดยใช้ git add ไฟล์ที่ถูก add เข้าไปสู่ staging จะเป็นเซตของไฟล์ที่พร้อมจะ commit ที่แยกแบบนี้เพราะ git จะใช้ให้เราเลือกได้ ว่าเราจะจัดกลุ่มไฟล์ในการ commit ยังไง สมมติเราแก้ไป 10 ไฟล์ แต่ว่าทั้ง 10 ไฟล์นี้อาจแบ่งเป็นสองเรื่องที่ไม่เกี่ยวข้องกัน โดย 5 ไฟล์ทำเรื่อง insert อีก 5 ไฟล์ทำเรื่อง update เราก็จะค่อยๆ git add เข้าไปเป็นกลุ่มทีละ 5 แล้ว commit 1 ครั้ง เสร็จแล้ว git add อีก 5 ค่อย commit อีกครั้งนั่นเอง
โอเคเรามาจัดการ commit โค้ด index.html ของเรากันโดยสั่งดังนี้ครับ
git add index.html
หลังจากนั้นลอง git status
ดูจะได้
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: index.html
git status
บอกเราว่าไฟล์ index.html
ของเรานั้นเป็นไฟล์ที่พร้อมจะถูก commit แล้ว และในวงเล็บนั้นยังไกด์เราอีกต่างหากว่าถ้าเราอยากเอาไฟล์ index.html
ออกจากส่วน staging ไปเป็น unstage ต้องทำยังไง
เราสามารถเช็คการเปลี่ยนแปลงทั้งหมด ที่เกิดกับไฟล์ที่อยู่ใน staging ได้โดยใช้คำสั่ง git diff
พร้อมกับ option --staged
ดังนี้ครับ
git diff --staged
git diff จะแสดงผลลัพธ์ดังนี้
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..76be9b8
--- /dev/null
+++ b/index.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Todo</title>
+ </head>
+ <body>
+ Hello World
+ </body>
+</html>
เนื่องจากไฟล์เรายังไม่เคย commit มาก่อน ดังนั้นจะเห็นว่า git diff จะแสดงเป็น + ข้างหน้าทั้งหมดคือ เป็น code ที่ถูกเพิ่มเข้าไปใหม่ทั้งหมดเลยนั่นเอง
ต่อไปเราจะทำการ commit โค้ดจริงๆละ โค้ดที่จะถูก commit คือโค้ดที่อยู่ใน staging อยู่แล้วคือ index.html นั่นเอง ให้เราสั่งดังนี้ครับ
git commit -m "Add index.html file"
โดยที่ option -m
เป็นการระบุข้อความสำหรับ commit ครับ (commit messages) ซึ่งถือว่าเป็นการอธิบายคร่าวๆว่า เราเปลี่ยนแปลงอะไรไปบ้างกับโค้ดที่เรา commit
จะเห็น git commit แสดงผลแบบนี้ออกมา
[master (root-commit) 68191ae] Add index.html file
1 file changed, 9 insertions(+)
create mode 100644 index.html
นั่นคือได้สร้าง commit ใหม่ให้เราแล้ว ส่วน 68191ae
คือ commit hash ครับซึ่ง git จะสร้างเพื่อเป็น uniq id ของ commit ให้อ้างอิงแต่ละ commit นั่นเองครับ
หลังจากนั้นให้เราลอง git status
ดูจะเห็นว่าได้สถานะแบบนี้แล้วหลังจาก commit
On branch master
nothing to commit, working tree clean
หลังจากได้ 1 commit แล้ว เราก็แก้ไขโค้ดของเราต่อ แก้ไข index.html
ให้เป็นแบบนี้ครับ
<!doctype html>
<html>
<head>
<title>Todo</title>
</head>
<body>
<header><h1>TODO</h1></header>
</body>
</html>
ทุกครั้งเวลาเราแก้ไขไฟล์ สิ่งที่เรามักจะทำบ่อยๆคือเช็คสถานะของ repository เราด้วย git status
ครับ ว่าเกิดการเปลี่ยนแปลงอะไรไปบ้างแล้ว ดังนั้นสั่ง git status
ดูตอนนี้จะเห็น
On branch master
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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
จะเห็นสถานะของไฟล์ index.html
ของเราตอนนี้คือ modified
ซึ่งต่างจากตอนแรกที่เรายังไม่ได้เคย commit ไฟล์นี้จะขึ้นเป็น Untracked file และนอกจากนั้น git status ยังไกด์ให้เราอีกว่า เราสามารถเพิ่มไฟล์นี้เข้า staging เพื่อทำให้พร้อมจะ commit ได้นะ และยังบอกว่าเราใช้ git checkout --
เพื่อยกเลิกการเปลี่ยนแปลงที่เกิดขึ้นกับไฟล์ของเรา ซึ่งเดียวผมอธิบายอีกทีสำหรับคำสั่งนี้นะครับ
ความต่างต่อไปของไฟล์ในสถานะ modified
กับ untracked file
คือเราใช้ git diff เช็คการเปลี่ยนแปลงได้ครับ โดยที่ยังไม่ต้อง git add ให้เราลองสั่ง git diff
ตอนนี้ดูครับจะได้ผลลัพธ์ออกมาแบบนี้
diff --git a/index.html b/index.html
index 76be9b8..b795be2 100644
--- a/index.html
+++ b/index.html
@@ -4,6 +4,6 @@
<title>Todo</title>
</head>
<body>
- Hello World
+ <header><h1>TODO</h1></header>
</body>
</html>
จะเห็นว่าส่วนที่มี - ข้างหน้าคือส่งที่เราลบออกไป และส่วนที่มี + ข้างหน้าคือส่วนที่เราเพิ่มเข้าไปนั่นเองครับ
หลังจากนี้ให้เราจัดการบันทึกการเขียนแปลงที่เราเพิ่งสร้างไปกับ index.html
ให้เป็น commit ใหม่ก่อน โดยเริ่มจาก git add
ก่อนครับ
git add index.html
ลอง git status
ดูจะได้
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: index.html
นั่นคือ index.html
ได้ย้ายไปอยู่ใน staging พร้อม commit เรียบร้อย และ git status ได้แนะนำวิธีการเอา index.html ออกไปจาก staging อีกแบบ เดี๋ยวมาอธิบายกันอีกทีครับว่ามันทำได้ยังไง
จัดการ commit ดังนี้
git commit -m "Add Todo header"
แล้ว git status ดูอีกครั้งก็จะเห็นว่า repository เรากลับมาสู่สถานะ
On branch master
nothing to commit, working tree clean
อีกครั้ง
ก่อนที่เราจะดูวิธีการลบไฟล์ออกจาก repository ให้เราเพิ่มไฟล์ใหม่เข้าไปอีกไฟล์ดูก่อนครับชื่อไฟล์ index.js แล้ว git add , git commit แบบที่เราให้เรียนรู้ไปแล้วดูครับ โดย index.js มีโค้ดแบบนี้
alert('Fail');
git add index.js
git commit -m "Add javascript"
หลังจากนั้นให้เราลบไฟล์ index.js ทิ้งได้เลยครับ แล้วเมื่อเรา git status
จะขึ้นสถานะแบบนี้
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: index.js
no changes added to commit (use "git add" and/or "git commit -a")
index.js อยู่ในสถานะ deleted
ครับ จะเห็นว่าสิ่งที่เราเคย commit ไว้แล้ว git รู้การเปลี่ยนแปลงของมัน ไม่ว่าจะเป็นแก้ไขบางส่วน หรือแม้แต่ลบมันทั้งไฟล์ออกไปแบบนี้
ต่อไปเราต้องการ commit การเปลี่ยนแปลงนี้เอาไว้ ให้เราใช้ git add index.js แล้ว git commit ได้เหมือนตอนเพิ่มเลยครับดังนี้
git add index.js
เมื่อ git status หลังจาก git add ไปแล้วจะได้แบบนี้
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: index.js
คือเราจัดการบันทึกการเปลี่ยนแปลงกับ index.js เข้า staging เรียบร้อย โดยสถานะใน staging ของ index.js คือการถูกลบออกไปนั่นเอง ต่อไปทำการ commit
git commit -m "Remove index.js"
เราได้ commit กันไป 4 commit แล้วต่อไปเราจะมาดูอีกคำสั่ง ที่ช่วยให้เราเห็นว่า เราเคย commit อะไรกันไปบ้าง นั่นคือ git log ลองสั่ง git log
ดูตอนนี้จะได้แบบนี้
commit 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97 (HEAD -> master)
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 01:08:02 2017 +0700
Remove index.js
commit f3918be770c8630e2117b194a12bedd8e5fb7417
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:55:14 2017 +0700
Add javascript
commit 588f955c902dcd5114531b0e98db46d63e3edc5c
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:54:34 2017 +0700
Add Todo header
commit 03c0aa4f20132a69bd1bb31f1616a89584aefc5f
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:23:48 2017 +0700
Add index.html file
ในแต่ละ commit git log แสดงข้อมูลว่า commit hash id คืออะไร , Author คนที่ทำการแก้ไขคือใคร, Date วันที่ทำการ commit คือวันไหน เวลาไหน และ ข้อความที่เราใส่ตอน commit
ซึ่งจริงๆแล้ว git log จะมี option ให้เราเล่นเยอะกว่านี้ในการแสดงข้อมูลของ commit เดี๋ยวเราได้ลองให้กันในส่วนต่อๆไป
จาก git log จะเห็นส่วนของ Author จะมีชื่อ และ อีเมล์ เราสามารถตั้งค่าได้ผ่านทางคำสั่ง git config โดยเราจะใส่ option --local เพื่อให้มีผลกับ repository นี้เท่านั้น ให้เราสั่งดังนี้ครับ
git config --local user.name "USER_NAME"
git config --local user.email "[email protected]"
ในบางครั้ง เวลาเราแก้ไขไฟล์เราด้วย IDE อย่างเช่น PhpStorm จะมี file หรือ folder ที่เป็น configuration ของ IDE อยู่ใน folder ของ git repository เรา แต่เราไม่อยากให้ git มาจัดการไฟล์พวกนี้ เพราะมันเป็นการตั้งค่าที่เราใช้กับ IDE บนเครื่องเรา ต่อให้เพื่อนร่วมงานเราใช้ IDE ตัวเดียวกัน ก็อาจจะตั้งค่าต่างกันได้
git จะมีวิธีการที่จะบอกให้มันรู้ว่าไฟล์แบบนี้ที่ไม่ต้องสนใจ (ignore) ไม่ต้องเทรคการเปลี่ยนแปลง โดยใช้ไฟล์ที่ชื่อว่า .gitignore
ให้เราสร้าง .gitignore
ใน folder ของ repository ของเรา แล้วของพิมพ์ลงไปแบบนี้
# Windows thumbnail db
Thumbs.db
# OSX files
.DS_Store
สองไฟล์นี้มักจะถูกสร้างโดย OS ซึ่งเราไม่ต้องการให้มันติดไปกับ git repository ของเราด้วยก็เลยเพิ่มเข้าไป
ในแต่ละบรรทัดในไฟล์ .gitignore
คือ pattern ของไฟล์ หรือ folder ที่จะถูก ignore เราสามารถใช้ wildguard characters คือ * หรือ ? ช่วยเขียน pattern ได้เช่น
/*/out
/*/*/build
/*/*/production
*.iws
*.ipr
*~
*.swp
# Android Studio
*.iml
.idea
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
.gradle
build/
.navigation
captures/
output.json
หลังจากเราสร้างไฟล์ .gitignore
แล้วเราต้อง commit .gitignore
เข้าไปด้วย ดังนี้
git add .gitignore
git commit -m "Add gitignore pattern"
ในสถาณการณ์ที่เรากำลังทดลองหาวิธีการแก้ปัญหาบางอย่างอยู่ เราอาจจะแก้โค้ดไปหลายๆไฟล์ แล้วตัดสินใจไม่เอาละ เราอาจจะให้กัน Undo ของ Editor แต่ Undo ของ Editor จะจำไว้แค่ตอนเปิดโปรแกรมอยู่ ถ้าเผลอปิด หรือโปรแกรม crash ไปก่อนจนปิดตัวเอง แล้วเราไม่ได้ใช้ version control อย่าง git ก็จะทำให้เราย้อนกลับไปจุดเดิมก่อนแก้ไขได้ยากมากๆ
สำหรับ git ถ้าของที่เคย commit ไว้แล้วทำการแก้ไข มันจะรู้ว่าไฟล์ไหนแก้อะไรไปแล้วบ้าง เราทดลองแก้ index.html
ต่อดังนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
</head>
<body>
<header><h1>TODO</h1></header>
<ul>
<li><input name="newTask"> <input type="submit" value="Add"></li>
</ul>
</body>
</html>
หลังจากนั้นสั่ง git status
ดูจะได้ดังนี้
On branch master
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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
สถานะของ index.html
กลับเป็น unstaging modified
เมื่อเราต้องการยกเลิกการเปลี่ยนแปลงของไฟล์ที่ยังไม่ได้เพิ่มไปใน staging ให้เราใช้ git checkout -- <file>
ดังนี้
git checkout -- index.html
git status ดูอีกครั้งจะได้
On branch master
nothing to commit, working tree clean
ในกรณีที่เราได้ add file เข้าไปใน staging แล้วอยากเอาออกมาเป็น unstaging อีกครั้ง โดยที่การเปลี่ยนแปลงที่เราทำไปกับไฟล์นั้นยังคงอยู่ไม่ได้หายไปไหนสามารถทำได้โดยใช้ git reset HEAD <file>
ให้เราลองแก้ไข index.html
อีกครั้ง
<!doctype html>
<html>
<head>
<title>Todo</title>
</head>
<body>
<header><h1>TODO</h1></header>
<ul>
<li><input name="newTask"> <input type="submit" value="Add"></li>
</ul>
</body>
</html>
ลอง git status ได้
On branch master
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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
หลังจากนั้น add index.html
เข้า staging
git add index.html
แล้ว git status
ดูจะได้
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: index.html
จะเห็นว่า git ก็แนะนำให้เราแล้วเช่นกัน ลองใช้ git reset HEAD <file>
ดังนี้
git reset HEAD index.html
แล้ว git status
อีกครั้งจะได้
On branch master
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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
นั่นคือ index.html กลับมาสถานะ modefied
แบบ unstaging เหมือนเดิม
ในกรณีที่เป็นไฟล์ที่อยู่ในสถาณะ untracked file คือสร้างใหม่ยังไม่เคย add หรือ commit และเราอยากลบให้หมด ถ้ามีหลายไฟล์และอยู่คนละ folder แล้วต้องไล่ลบทีละไฟล์คงจะเป็นเรื่องที่ลำบาก แต่สำหรับ git เราสามารถลบไฟล์ที่เป็น untracked file ออกได้หมดทีเดียวด้วย git clean -f เช่น ให้เราสร้างไฟล์ index.js
อีกครั้งแล้วเพิ่มโค้ดไปแบบนี้
alert('Fail')
แล้วสั่ง git status ได้
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.js
nothing added to commit but untracked files present (use "git add" to track)
แล้วลองลบด้วยวิธี git clean -f
ดังนี้
git clean -f
Removing index.js
แน่นอนว่าการใช้ version control อย่าง git ไม่ใช่เพื่อให้เรา commit แล้ว git log ดูได้เท่านั้น แต่สามารถให้เราย้อนกลับไปในกรณีที่ commit ล่าสุดมีบั๊กเกิดขึ้น อยากย้อนกลับไปจุดก่อนที่ยังไม่มีบั๊ก
ให้เราลอง git log ล่าสุดได้แบบนี้
commit 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97 (HEAD -> master)
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 01:08:02 2017 +0700
Remove index.js
commit f3918be770c8630e2117b194a12bedd8e5fb7417
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:55:14 2017 +0700
Add javascript
commit 588f955c902dcd5114531b0e98db46d63e3edc5c
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:54:34 2017 +0700
Add Todo header
commit 03c0aa4f20132a69bd1bb31f1616a89584aefc5f
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:23:48 2017 +0700
Add index.html file
เราจะ reset กลับไปที่ f3918be770c8630e2117b194a12bedd8e5fb7417 ให้เราสั่งดังนี้
git reset f3918be770c8630e2117b194a12bedd8e5fb7417
Unstaged changes after reset:
D index.js
ลองสั่ง git log อีกทีจะได้
commit f3918be770c8630e2117b194a12bedd8e5fb7417 (HEAD -> master)
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:55:14 2017 +0700
Add javascript
commit 588f955c902dcd5114531b0e98db46d63e3edc5c
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:54:34 2017 +0700
Add Todo header
commit 03c0aa4f20132a69bd1bb31f1616a89584aefc5f
Author: Weerasak Chongnguluam <[email protected]>
Date: Wed Dec 20 00:23:48 2017 +0700
Add index.html file
จะเห็นว่า git ถอยกลับมาที่ f3918be770c8630e2117b194a12bedd8e5fb7417 เป็น commit บนสุด และเมื่อเราสั่ง git status จะได้
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: index.js
no changes added to commit (use "git add" and/or "git commit -a")
ไฟล์ index.js กลับมาอยู่ในสถานะ unstaged เราสามารถเอาไฟล์กับมาจากการ delete ได้โดยใช้ git checkout -- index.js
แบบที่เราได้ลองไปแล้วนั่นเอง
นอกจากนั้น git reset ยังมี option --soft และ --hard โดยที่ --soft จะทำให้ไฟล์ที่เคย commit กลับมาอยู่ในสถานะ staged ส่วน --hard จะทำให้ไฟล์ กลับไปเป็นก่อนที่จะถูกแก้ไขเลย
ถ้าเราใช้ git reset ผิด สำหรับ git แล้ว commit ที่เราเคยทำไว้ไม่ได้หายไปไหน เพราะ git reset คือการย้ายแค่สถานะของ commit บนสุดของ working directory ของ branch ที่เราทำงานอยู่ ไม่ได้ลบ commit ที่เคยทำไว้ เราสามารถเดินหน้ากลับไป commit เดิมได้ แต่ว่าเราต้องรู้ commit hash id เราสามารถลิสต์ได้โดยใช้ คำสั่ง git reflog
เมื่อเราสั่งจะได้ออกมาประมาณนี้
git reflog
f3918be (HEAD -> master) HEAD@{0}: reset: moving to f3918be770c8630e2117b194a12bedd8e5fb7417
772f7ca HEAD@{1}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
f3918be (HEAD -> master) HEAD@{2}: reset: moving to f3918be770c8630e2117b194a12bedd8e5fb7417
772f7ca HEAD@{3}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
f3918be (HEAD -> master) HEAD@{4}: reset: moving to f3918be770c8630e2117b194a12bedd8e5fb7417
772f7ca HEAD@{5}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
588f955 HEAD@{6}: reset: moving to 588f955c902dcd5114531b0e98db46d63e3edc5c
f3918be (HEAD -> master) HEAD@{7}: reset: moving to f3918be770c8630e2117b194a12bedd8e5fb7417
772f7ca HEAD@{8}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
588f955 HEAD@{9}: reset: moving to 588f955c902dcd5114531b0e98db46d63e3edc5c
772f7ca HEAD@{10}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
588f955 HEAD@{11}: reset: moving to 588f955c902dcd5114531b0e98db46d63e3edc5c
772f7ca HEAD@{12}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
f3918be (HEAD -> master) HEAD@{13}: reset: moving to f3918be770c8630e2117b194a12bedd8e5fb7417
772f7ca HEAD@{14}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
588f955 HEAD@{15}: reset: moving to 588f955c902dcd5114531b0e98db46d63e3edc5c
772f7ca HEAD@{16}: reset: moving to 772f7ca0903c0a8e13fdfe7e66e9734b255bfc97
588f955 HEAD@{17}: reset: moving to HEAD
588f955 HEAD@{18}: reset: moving to 588f955c902dcd5114531b0e98db46d63e3edc5c
772f7ca HEAD@{19}: filter-branch: rewrite
cb310d8 (refs/original/refs/heads/master) HEAD@{20}: commit: Remove index.js
8b734af HEAD@{21}: commit: Add javascript
eacdfbd HEAD@{22}: commit: Add Todo header
68191ae HEAD@{23}: commit (initial): Add index.html file
จะเห็นว่าเป็น log การเปลี่ยนแปลงของ HEAD ซึ่งเป็นชื่อแทน commit บนสุด และเห็น hash id ของ commit ในกรณีนี้ผมย้ายไป f3918be ซึ่ง commit ที่ HEAD อ้างอิงก่อนหน้าคือ 772f7ca ผมสามารถกลับไปทีเดิมได้โดยใช้
git reset --hard 772f7ca
ระบบ branch ช่วยให้เราแยกการเปลี่ยนแปลงแก้ไขโค้ดออกเป็นกลุ่ม ในเรื่องที่เกี่ยวข้องกันได้ แล้วเมื่อการแก้ไขภายใน branch นั้นได้รับการทดสอบจนแน่ใจแล้ว จึงค่อยเรามารวมกับ branch หลักแล้วจัดการ deploy ขึ้น production ได้
ถ้าไม่มีระบบ branch จะทำให้เราพัฒนาหลายๆส่วนไปพร้อมๆกันได้ลำบากเพราะเมื่อต่างคนต่างแก้ไข และเอาโค้ดมารวมกันโดยไม่ได้ทดสอบให้แน่ใจก่อน ก็จะทำให้เกิดข้อผิดพลาดอื่นๆที่ไม่เกี่ยวข้องกับสิ่งที่กำลังพัฒนาอยู่ได้
เราสามารถสร้าง branch ใหม่ด้วยคำสั่ง git checkout -b branch_name
หรือใช้ git branch branch_name
ก็ได้ ความต่างคือถ้าใช้ git checkout -b branch_name
git จะสร้างและย้าย HEAD ไปที branch ใหม่ให้เลย แต่ git branch branch_name
จะยังอยู่ branch เดิม เราสามารถย้ายเองได้โดยใช้ git checkout branch_name
ต่อไปเราจะเริ่มพัฒนาความสามารถใหม่ให้กับ Todo App ของเราโดยจะทำส่วนของ layout เบื้องต้น โดยให้เราแยก branch ใหม่จาก master branch ซึ่งเป็น default branch ตอนที่เราสร้าง repository ดังนี้
git checkout -b basic_layout
เมื่อเรา git status ดูจะได้
On branch basic_layout
nothing to commit, working tree clean
ตรง On branch เปลี่ยนเป็น On branch basic_layout
แล้ว
ให้เราแก้ไขไฟล์ index.html
เพื่อเพิ่มโค้ดจัดการ layout เบื้องต้นดังนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
<style>
.container {
position: relative;
}
.container .col {
float:left;
position:relative;
padding: 0 20px;
}
.clear {
clear:both;
}
.col .list {
padding-left:1em;
}
</style>
</head>
<body>
<div class="container">
<div class="col">
<header><h1>TODO</h1></header>
<ul class="list">
<li>สอน Git</li>
<li>กินกอกก</li>
</ul>
</div>
<div class="col">
<header><h1>DOING</h1></header>
<ul class="list">
<li>สอน Git</li>
<li>กินกอกก</li>
</ul>
</div>
<div class="col">
<header><h1>DONE</h1></header>
<ul class="list">
<li>สอน Git</li>
<li>กินกอกก</li>
</ul>
</div>
<div class="clear"></div>
</div>
</body>
</html>
ทำการ add และ commit code ดังนี้
git add index.html
git commit -m "First draft layout"
หลังจากที่เราทำงานใน branch basic_layout จนพอใจแล้ว ถ้าต้องการเอาโค้ดจาก branch basic_layout กลับมารวมกับ branch master ให้เรากลับไปที่ branch master ก่อนแล้วค่อย merge โดยทำดังนี้
git checkout master
git merge basic_layout
git จะแสดงผลลัพธ์ดังนี้
Updating 590fb2d..b2a0519
Fast-forward
index.html | 41 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
เราสามารถที่จะลบ branch ที่ไม่ได้ใช้แล้วด้วย
git branch -D basic_layout
เราสามารถ ลิสต์ รายชื่อ branch ได้โดยสั่ง git branch
เฉยๆ
ต่อไปเป็นสถานการณ์ที่ เราแยกจาก master เป็น branch add_item และ delete_item เสร็จแล้วเราจะ merge สอง branch นี้กลับมารวมที่ master ให้เราสร้าง branch ทั้งสองใหม่ดังนี้ครับ
git checkout master
git branch add_item
git branch delete_item
หลังจากนั้นให้เราเข้าไปที่ branch add_item แล้วแก้ไข index.html ดังนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
<style>
.container {
position: relative;
}
.container .col {
float:left;
position:relative;
padding: 0 20px;
}
.clear {
clear:both;
}
.col .list {
padding-left:1em;
}
</style>
</head>
<body>
<div class="container">
<div class="col">
<header><h1>TODO</h1></header>
<input name="new_item" /> <button id="new_item_btn">Add</button>
<ul class="list">
<li>สอน Git</li>
<li>กินกอกก</li>
</ul>
</div>
<div class="col">
<header><h1>DOING</h1></header>
<ul class="list">
<li>สอน Git</li>
<li>กินกอกก</li>
</ul>
</div>
<div class="col">
<header><h1>DONE</h1></header>
<ul class="list">
<li>สอน Git</li>
<li>กินกอกก</li>
</ul>
</div>
<div class="clear"></div>
</div>
</body>
</html>
จัดการ add และ commit ให้เรียบร้อยดังนี้
git add index.html
git commit -m "Add add item input and button"
สลับไปที่ branch delete_item จะพบว่าโค้ดที่ index.html กลับไปเปิดหน้าตาแบบเดิมเหมือนใน master ที่เราได้แยกออกมาจาก delete_item ส่วนโค้ดที่อยู่ใน add_item ที่เราเพิ่งเพิ่มเข้าไปก็จะหายไป
ให้เราแก้ไขโค้ดใน index.html เป็นดังนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
<style>
.container {
position: relative;
}
.container .col {
float:left;
position:relative;
padding: 0 20px;
}
.clear {
clear:both;
}
.col .list {
padding-left:1em;
}
</style>
</head>
<body>
<div class="container">
<div class="col">
<header><h1>TODO</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DOING</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DONE</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="clear"></div>
</div>
</body>
</html>
จัดการ add และ commit ให้เรียบร้อยดังนี้
git add index.html
git commit -m "Add deletebutton"
หลังจากนั้นให้เรากลับไปที่ master แล้วลอง merge ด้วย add_item แล้วตามด้วย delete_item
git checkout master
git merge add_item
git merge delete_item
จะเห็นว่า git จะสร้าง commit ใหม่สำหรับการ merge delete_item แต่ตอน merge add_item ไม่มี เพราะ สอง branch นี้แตกออกไปจาก master ดังนั้นตอนที่ merge add_item จึงสามารถเอา commit มาต่อกันได้เลย แต่หลังจาก merge add_item ไปแล้ว git จะต้องพยายามเอาโค้ดที่เกิดจาก delete_item มาผสมไปกับสิ่งที่เปลี่ยนไปแล้วจาก add_item เอามาต่อตรงๆไม่ได้ ทำให้เกิดมี merge commit เกิดขึ้น ซึ่งถ้าไม่มีการ conflict กันก็จะ merge ได้สำเร็จเป็นปกติ
การที่เกิด conflict นั่นคือกรณีที่มีการแก้ไขโค้ดที่จุดเดียวกัน แต่คนละ branch จนทำให้ git merge ไม่สามารถที่จะจัดการรวมให้เราได้เอง เพราะไม่สามารถตัดสินใจได้ว่าจะเลือกการเปลี่ยนแปลงจาก branch ไหนกันแน่
ทดลองทำให้เกิด conflict โดยแยกอีก 2 branch คือ add_placeholder และ add_class
git checkout master
git branch add_placeholder
git branch add_class
เสร็จแล้ว git checkout add_placehodler
แล้วแก้โค้ดใน index.html
เป็นดังนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
<style>
.container {
position: relative;
}
.container .col {
float:left;
position:relative;
padding: 0 20px;
}
.clear {
clear:both;
}
.col .list {
padding-left:1em;
}
</style>
</head>
<body>
<div class="container">
<div class="col">
<header><h1>TODO</h1></header>
<input placeholder="What you will do?" name="new_item" /> <button id="new_item_btn">Add</button>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DOING</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DONE</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="clear"></div>
</div>
</body>
</html>
แล้วจัดการ add และ commit ดังนี้
git add index.html
git commit -m "Add new TODO placeholder text"
หลังจากนั้นย้ายไปที่ branch add_class แล้วแก้โค้ด index.html
เป็นดังนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
<style>
.container {
position: relative;
}
.container .col {
float:left;
position:relative;
padding: 0 20px;
}
.clear {
clear:both;
}
.col .list {
padding-left:1em;
}
.field {
color: #666666;
}
</style>
</head>
<body>
<div class="container">
<div class="col">
<header><h1>TODO</h1></header>
<input class="field" name="new_item" /> <button id="new_item_btn">Add</button>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DOING</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DONE</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="clear"></div>
</div>
</body>
</html>
แล้วจัดการ add และ commit
git add index.html
git commit -m "Add field css class"
หลังจากนั้นให้ไปที่ branch master แล้ว merge ทั้งสอง branch ที่ละ branch เข้า master ดังนี้
git checkout master
git merge add_placehodler
git merge add_class
git จะฟ้องออกมาแบบนี้
$ git merge add_class
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
นั่นคือเกิดกการ conflict ขึ้นระหว่างการ merge เมื่อเราเปิดไฟล์ index.html แล้วโค้ดเราจะโดนแทรกบางอย่างเข้าไป กลายเป็นแบบนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
<style>
.container {
position: relative;
}
.container .col {
float:left;
position:relative;
padding: 0 20px;
}
.clear {
clear:both;
}
.col .list {
padding-left:1em;
}
.field {
color: #666666;
}
</style>
</head>
<body>
<div class="container">
<div class="col">
<header><h1>TODO</h1></header>
<<<<<<< HEAD
<input placeholder="What you will do?" name="new_item" /> <button id="new_item_btn">Add</button>
=======
<input class="field" name="new_item" /> <button id="new_item_btn">Add</button>
>>>>>>> add_class
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DOING</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DONE</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="clear"></div>
</div>
</body>
</html>
โดยที่โค้ดที่อยู่ระหว่าง <<<<<<< HEAD
จนถึง =======
จะเป็นโค้ดที่อยู่ใน branch ปัจจุบันของเรา คือ master
และโค้ดที่อยู่ระหว่าง =======
จนถึง >>>>>>> add_class
เป็นโค้ดที่อยู่ใน branch add_class
ที่เกิดการ conflict กันนั่นเอง
<<<<<<< HEAD
<input placeholder="What you will do?" name="new_item" /> <button id="new_item_btn">Add</button>
=======
<input class="field" name="new_item" /> <button id="new_item_btn">Add</button>
>>>>>>> add_class
การแก้ conflict คือให้เราจัดการโค้ดตรงส่วนนี้ให้เปิดแบบที่เราต้องการ แน่นอน ให้คุยกันก่อนกับเพื่อนร่วมทีมที่เป็นเจ้าของ branch นี้ ถ้าเกิดเป็น branch ที่เราไม่ได้สร้างเอง ว่าโค้ดจริงๆตอนรวมกันจะเป็นยังไง
สำหรับกรณีนี้ให้เราแก้โดยให้โค้ดสุดท้ายที่รวมกันเป็นแบบนี้
<!doctype html>
<html>
<head>
<title>Todo</title>
<style>
.container {
position: relative;
}
.container .col {
float:left;
position:relative;
padding: 0 20px;
}
.clear {
clear:both;
}
.col .list {
padding-left:1em;
}
.field {
color: #666666;
}
</style>
</head>
<body>
<div class="container">
<div class="col">
<header><h1>TODO</h1></header>
<input class="field" placeholder="What you will do?" name="new_item" /> <button id="new_item_btn">Add</button>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DOING</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="col">
<header><h1>DONE</h1></header>
<ul class="list">
<li>สอน Git <button name="delete_btn">x</button></li>
<li>กินกอกก <button name="delete_btn">x</button></li>
</ul>
</div>
<div class="clear"></div>
</div>
</body>
</html>
หลังจากเราแก้โค้ดที่ conflict กันแล้วให้เราสั่ง git commit
แล้ว git จะขึ้น editor โดยมี commit message ว่า Merge branch 'add_class'
ให้เราจัดการ save แล้วออกจาก editor
ตอนที่เราแยก branch ไปจาก branch อื่นๆ เช่นแยก add_class ไปจาก master จะเห็นว่าโค้ดเริ่มต้น จะเหมือนกับโค้ดหน้าตาสุดท้ายของ master
ทีนี้ เมื่อ master มีการเปลี่ยนแปลงไปจากเดิม ไม่เหมือนกับตอนที่เราแยก branch ไปแล้ว เราสามารถทำการ rebase ให้กับ branch ที่เราแยกมาก่อน
เมื่อให้ base โค้ดของ branch เราเป็นแบบใหม่ แล้ว commit ใหม่ที่เราสร้างใน branch ค่อยต่อจาก base ใหม่นั่นอีกที
เราสามารถใช้ git rebase ก่อนที่จะ merge ได้ เพื่อช่วยให้เราแก้ conflict ที่ branch ก่อนที่จะเอามา merge ใส่ master
git branch add_placehodler
git rebase master
git checkout master
git merge add_placehodler
git checkout add_class
git rebase master
ซึ่งจะเกิด conflict ขึ้น ที่ว่ารอบนี้โค้ดที่อยู่ตรงส่วน <<<<<<< HEAD
จนถึง =======
จะเป็นโค้ดที่อยู่ใน branch ที่เราเอามา rebase
และโค้ดที่อยู่ระหว่าง =======
จนถึง >>>>>>> add_class
เป็นโค้ดที่อยู่ใน branch add_class
ที่เกิดการ conflict กันนั่นเอง
หลังจากแก้ conflict เสร็จแล้วให้เราสั่ง git rebase --continue
เมื่อให้ git ทำการ rebase ต่อจนเสร็จ
แล้วให้กลับไปที่ master แล้ว merge add_class อีกครั้ง
git rebase --continue
git checkout master
git merge add_class
การใช้ rebase ก่อน จะทำให้ branch master ของเรานั้นไม่มี commit ที่ปะเป็น merge ซึ่งจะทำให้ commit log นั้นต่อเนื่องกันได้เนียนขึ้น
การ tag คล้ายๆกับ branch ต่างกันตรงนี้ branch เรามีเอาไว้แยกเพื่อปรับเปลี่ยนโค้ด แล้วค่อยเอามารวมกัน ส่วน tag เป็นการ mark จุด ระบุ version
เราสามารถใส่ tag ให้กับสถานะล่าสุดของโค้ดใน branch เราได้ดังนี้
git tag v0.0.1
git tag
v0.0.1
เราสามารถใช้ git stash เพื่อเก็บโค้ดที่เราแก้ไข เอาไว้ชั่วคราวก่อน ผมมักใช้ในกรณีที่แก้โค้ดไปแล้ว แต่อยากกลับไปสถาณะก่อนแก้ไข โดยที่ยังไม่อยากลบโค้ด หรือว่ายังไม่อยาก commit เก็บไว้ ผมจะใช้ git stash ช่วยเก็บไว้ก่อน เช่นถ้าเราแก้ไข index.html
ไป แต่อยากกลับไปสถานะเดิมของ branch ให้เราสั่งดังนี้
git stash
แล้วหลังจากเราทดสอบอะไรบางอย่างจนพอใจ ให้เราสั่ง
git stash pop
เพื่อให้โค้ดที่เราบันทึกไว้กลับมา
สมมติเรามี repository อยู่บน github อยู่แล้ว เราสามารถใช้
git clone <repository_url>
เพื่อให้โค้ดมาอยู่บนเครื่องเรา และ git จะทำการเพิ่ม remote repository ให้เราชื่อว่า origin โดยอัตโนมัติ โดยเราเช็ครายการของ remote repository ได้โดยสั่ง
git remote show
ทีนี้ถ้าเรามีโค้ดอยู่แล้ว แล้วเราไปสร้าง remote repository เช่นบน github เราต้องเพิ่ม remote repository เอง ด้วย git remote add ดังนี้
git remote add <remote_name> <repository_url>
เสร็จแล้วค่อย push ขึ้นไปด้วย git push
ซึ่งคำสั่ง git push นั้นต้องระบุ ชื่อ remote name เช่น origin
แล้วตามด้วย branch ที่เราต้องการ push ขึ้นไปเช่น master
รวมกันเป็น
git push origin master
ในการทำงานร่วมกันบน remote branch เดียวกันนั้น ถ้าเกิดมีคน push ขึ้นไปก่อนเรา เราต้อง pull โค้ดใน remote branch ที่เปลี่ยนไปแล้ว ให้ลงมารวมกับโค้ดในเครื่องเราก่อน จริงๆ pull ก็คือการ merge นั่นเองครับ แต่เป็น merge จาก remote branch มา branch ในเครื่องเรา และ ซึ่งเราสามารถใช้ rebase ได้เช่นกันโดยสั่ง --rebase เป็น option ให้กับ git pull นั่นเอง
ตัวอย่างการใช้ git pull เช่น
git pull orin master
git pull --rebase origin master
ในกรณีที่บน remote branch มี branch ใหม่เกิดขึ้น เราสามารถ checkout branch นั้นให้มาอยู่เครื่องเราได้เช่นกัน แต่ให้เราใช้ git fetch ก่อน เพราะ git fetch จะเป็นการ sync branch แล้วเราถึงจะ git checkout ได้ เช่น
git fetch
git checkout new_remote_branch
Docker คือเครื่องมือจัดการ container ส่วน container เป็นเทคโนโลยีที่ใช้ฟีเจอร์ของ OS Kernel เช่น Linux ที่ช่วยให้เราสามารถรันโปรแกรมแยกออกจากกัน โดยจำลองสภาพแวดล้อมต่างๆให้พร้อม