Wednesday, November 28, 2007

git-stash

เมื่อวาน sirn comment ถึงเจ้า Mercurial Queues extension ของ Mercurial
ตัว mq ไม่เคยใช้ แต่อ่านดูถึง feature ที่สามารถทำ stack ของ patch ได้ก็รู้สึกเคลิ้ม

วันนี้อ่านเจอคำสั่ง git-stash ที่สามารถ pending สิ่งที่ทำอยู่ แล้วย้อนกลับไปที่ clean state ก่อนหน้าได้
อันนี้แหล่ะ use-case ที่เจอบ่อย

สมมติกำลังทำ feature A อยู่ ระหว่างที่กำลังเมามันกับการ refactor
ก็ดันเกิดมี bug ร้ายแรงที่ต้องรีบแก้
ถ้าเป็น svn ง่ายสุด ก็อาจจะ checkout อีก copy
แล้วตามไปแก้ที่นู่น
แต่ถ้าเป็น git หล่ะ
# while doing something
git stash
# fix bug
git commit -a -m "xxxxx"
git stash apply
# กลับมาเมามันต่อ

Related link from Roti

Tuesday, November 27, 2007

Git อีกทีน่า

วันนี้ลองมาดูประเด็นที่ทำให้คนที่เคยใช้ cvs หรือ svn เกิดอาการเหวอเมื่อใช้ git ได้

ขั้นตอนปกติในการใช้ git
  1. สร้าง working copy ด้วยคำสั่ง git clone url
  2. แก้ไข file ที่ต้องการ
  3. สั่ง git add yourfilename เพื่อ mark ว่าต้องการ commit file นี้ในอนาคต
  4. สั่ง git commit
ดูเผินๆแล้ว จะเห็นว่าไม่น่ามีอะไร
สำหรับ user ที่ใช้ svn มาก่อน ก็จะบอกว่า
ขั้นที่ 3 มันเป็นขั้นที่เกินมาเนี่ย ใน svn ไม่เห็นต้องสั่งอะไรแบบนั้นเลย

ถ้าไม่รู้ว่าจริงๆแล้ว git ทำอะไรในชั้นที่ 3 มันก็อาจจะทำให้เกิด error ขึ้นได้
ลองจำลองเหตุการณ์ใหม่
  1. user clone project
  2. user แก้ไข file x
  3. user สั่ง git add x เพื่อบอก git ว่าต้องการที่จะ commit file นี้
  4. user กลับไป edit file x อีกรอบ
  5. user สั่ง git commit
ผลก็คือ เฉพาะการแก้ไขในขั้นที่ 2 เท่านั้น ที่ถูก commit เข้าไป
การแก้ไขในขั้นที่ 4 ไม่รวมอยู่ในนั้นด้วย
ขั้นตอนที่ถูกก็คือ
  1. user clone project
  2. user แก้ไข file x
  3. user สั่ง git add x เพื่อบอก git ว่าต้องการที่จะ commit file นี้
  4. user กลับไป edit file x อีกรอบ
  5. user สั่ง git add x อีกรอบ
  6. user สั่ง git commit
จะเห็นว่า git มีความแตกต่างกับพวก svn หรือ cvs อยู่
ตัว git เองมองสิ่งที่ track คือ content ไม่ใช่ file ทั้ง file
การที่เราสั่ง git add เป็นการสั่ง add content ที่เปลี่ยนแปลง ณ ขณะนั้น
การเปลี่ยนแปลงที่ตามหลังมา ไม่นับว่าอยู่ในกลุ่มที่จะ commit

keyword "Content" ยังนำความแตกต่างมาสู่วิธีการ commit change ด้วย
เราสามารถจัดการ content ที่ต้องการให้้ git commit ได้ในระดับ fine-grained
สมมติว่ามีการเปลี่ยนแปลงใน file ที่เราแก้ไข 5 จุด
เราสามารถเลือกได้ว่า การ commit ครั้งนี้ เราต้องการที่จะ commit เฉพาะจุดที่ 2 กับ 3 เท่านั้นนะ

ในการที่จะจัดการกับ content ที่จะ commit
เราสามารถทำได้โดยการ switch เข้าสู่ mode interactive ด้วยคำสั่ง
git add -i
ลองดูตัวอย่างการใช้งาน
สมมติว่าผมมี file x อยู่ ใน file นี้ผมมีการแก้ไข 2 จุดก็คือ
มีการ insert บรรทัดแรกสุด กับ บรรทัดท้ายสุดลงไป

เริ่มด้วยคำสั่ง git add -i
git ก็จะแสดงให้เห็นว่า file x มีการเปลี่ยนแปลงเพิ่มบรรทัดอยู่ 2 จุดนะ

pphetra@pann:~/temp/s$ git add -i
staged unstaged path
1: unchanged +2/-0 x

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help

ลองสั่ง help ดู ก็จะได้คำอธิบายย่อๆดังนี้

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 8
status - show paths with changes
update - add working tree state to the staged set of changes
revert - revert staged set of changes back to the HEAD version
patch - pick hunks and update selectively
diff - view diff between HEAD and index
add untracked - add contents of untracked files to the staged set of changes

ลองเลือก menu patch
มันก็จะแสดง file ขึ้นมา ให้เราก็ระบุหมายเลข file ที่ต้องการ

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 5
staged unstaged path
1: unchanged +2/-0 x
Patch update> 1
diff --git a/x b/x
index abe1496..61916db 100644
--- a/x
+++ b/x
@@ -1,3 +1,5 @@
+hi bunny
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/s/?]? ?
y - stage this hunk
n - do not stage this hunk
a - stage this and all the remaining hunks
d - do not stage this hunk nor any of the remaining hunks
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks

กรณีนี้เราต้องการ ที่จะ commit เฉพาะ row บนสุดเท่านั้น
เราก็สั่ง split
จากนั้น git จะแสดงการเปลี่ยนแปลงให้เราตัดสินใจทีละอัน

@@ -1,3 +1,5 @@
+hi bunny
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/s/?]? s
Split into 2 hunks.
@@ -1,3 +1,4 @@
+hi bunny
hello
hi pok
hi pann
Stage this hunk [y/n/a/d/j/J/?]? y
@@ -1,3 +2,4 @@
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/K/?]? d
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 7
Bye.
จะเห็นได้ว่า การจัดการ patch ของ git เร้าใจกว่า svn เยอะ
แต่ก็อาจจะนำความเวียนหัวมาสู่ผู้ที่คุ้นเคยกับโลกของ svn อย่างเดียวได้

Related link from Roti

Monday, November 26, 2007

Dojo DateWidget in Thai [Dojo 1.0]

จากปีก่อน ที่เขียนถึง Dojo DateWidget in Thai สำหรับ version 0.4
วันนี้ได้ฤกษ์ลอง version 1.0 บ้าง



ใน version 1.0, widget สำหรับ date ใช้ชื่อว่า DateTextBox
ซึ่งประกอบด้วย widget 2 ตัวประกอบกัน ก็คือ TextBox กับ _Calendar
โดยตัว _Calendar จะเป็น popup ที่ทำหน้าที่แสดงปฎิทิน
(เครื่องหมาย _ นำหน้า หมายความว่า dojo ตั้งใจให้ widget นี้เป็น internal widget)

สิ่งที่เราต้องการ ก็คือ เราต้องการให้ internal state ของ DateTextBox
ยังเก็บค่า Date เป็น Date object ปกติของ javascript อยู่
แต่ต้องการให้เฉพาะส่วนการแสดงผลเท่านั้น ที่แสดงผลเป็นปี พ.ศ.
นอกจากนั้นยังต้องการให้ calendar ที่ popup ขึ้นมาแสดงผลเป็นภาษาไทยด้วย

ใน dojo 1.0, ตัว widget ซับซ้อนขึ้น
มีการ reuse เยอะขึ้น อย่างเช่น DateTextBox นั้นมี inherited structure ดังนี้

DateTextBox -> TimeTextBox -> RangeBoundTextBox ->
MappedTextBox -> ValidationTextBox -> TextBox

จากการไล่การทำงานของ DateTextBox เราจะพบจุดตัดที่เราสามารถ override
ส่วนการแสดงผลได้ที่ method format และ parse
สามารถนำมา declare class ตัวใหม่ที่ชื่อ ThaiDateTextBox ได้ดังนี้
dojo.declare("ThaiDateTextBox", [dijit.form.DateTextBox], {

// กำหนดตัว calendar ตัวใหม่ที่แสดงผลภาษาไทย
_popupClass: "ThaiCalendar",

// value ที่รับเข้ามา มี type เป็น date object
// เมื่อได้มาแล้วก็โยนไปให้ dojo.date.locale.format จัดการ format ให้ก่อน
// แล้วค่อยนำมาตัด ปี เพื่อ + 543 หรือ 43 เข้าไปอีกที (ขึ้นอยู่กับ format)
// note: มี bug กรณี format yy ที่จะมี overflow
format: function(value, constraints) {
if(!value || value.toString() == this._invalid){ return null; }
var sidx = constraints.datePattern.indexOf('yy');
var fvalue = dojo.date.locale.format(value, constraints);
var ycnt = 2;
var adj = 43;
if (/yyyy/.exec(constraints.datePattern)) {
ycnt = 4;
adj = 543;
}
return fvalue.substr(0,sidx) + (parseInt(fvalue.substr(sidx, ycnt)) + adj);
},

// value ที่รับเข้ามา จะอยู่ในรูป datePattern ที่เรากำหนดตอน declare widget
// เมื่อได้มา ก็ทำการ - 543 เพื่อแปลงเป็น ค.ศ. ก่อนส่งให้ dojo.date.locale.parse
parse: function(value, constraints) {
var sidx = constraints.datePattern.indexOf('yy');
if (/yyyy/.exec(constraints.datePattern)) {
nyear = parseInt(value.substr(sidx, 4)) - 543;
} else {
nyear = String(parseInt(value.substr(sidx, 2)) + 2500 - 543).substr(2,2);
}
var newValue = value.substr(0, sidx) + nyear;
return dojo.date.locale.parse(newValue, constraints);
}
});


ส่วนตัว Calendar ที่ popup ขึ้นมาให้ user เลือกนั้น
ถ้าไล่ดู code ภายใน จะเห็นได้ว่า เขามีกลไก i18n ในการแสดงผลพวกชื่อเดือน ชื่อวัน
แต่เนื่องจากเจ้า dojo 1.0 มันไม่ได้ bundle locale ของไทยมาให้ด้วย
เราจึงต้องสร้างขึ้นมาเอง

ในการที่จะสร้าง locale th
เจ้า dojo ได้เตรียมกลไกไว้เรียบร้อยแล้ว
โดย script ที่จะใช้สร้าง จะอยู่ใน directory util/buildscript/cldr
ซึ่ง dojo ได้นำข้อมูล xml จาก Common Locale Data Repository
และเตรียม xlst file ซึ่งใช้สำหรับ generate เป็น javascript code ให้เราแล้ว
แค่เราเข้าไปแก้ไข build.xml เพิ่ม "th-TH" เข้าไปในรายการที่ต้องการให้ generate

<!-- Arbitrary defaults. locales and currencies properties can be altered or eliminated to build the entire set -->
<property name="locales" value="en-us,th-th"/>


ประเด็นปัญหาถัดไปก็คือตัวปี พ.ศ.
ถึงแม้เราจะเลือกใช้ locale th-TH แล้วก็ตาม
มันก็ยังมีปัญหาว่า ปี ยังได้เป็นปี ค.ศ. อยู่ดี
ตรงส่วนนี้ เราต้องแอบเข้าไป override method ใน Calendar widget
ในใน dojo 0.4, จุดตัดสำหรับเรื่องนี้ค่อนข้างง่าย เพราะคนเขียนเขาแบ่ง method ออกเป็น method ย่อยๆ เพื่อให้อ่านง่าย
แต่ใน dojo 1.0 คนเขียน เขาเขียนส่วน render ยาวเป็นพรืด ทำให้เราเลือก override ยากหน่อย

สำหรับกรณีนี้ เราเลือก override method "_setText"
ซึ่งเป็นทางเลือกที่ไม่ค่อยดีนัก เพราะเป็นการ override แบบที่แอบไปใช้กลไกภายในของมัน
เพราะมันมีผลว่า ถ้ามันมีการ upgrade internal structure เมื่อไร
class ของเราก็จะมีโอกาส broken ได้โดยง่าย
dojo.declare("ThaiCalendar", [dijit._Calendar], {
_setText: function(node, text) {
if (/YearLabelNode/.exec(node.getAttribute("dojoattachpoint"))) {
arguments[1] = parseInt(text) + 543;
}
return this.inherited('_setText', arguments);
}
});


เวลานำไปใช้ ก็ใช้แบบนี้
<script type="text/javascript" src="./js/dojo/dojo.js"
djConfig="isDebug: true, parseOnLoad: true, locale: 'th'"></script>

...

<span id="d1" dojoType="ThaiDateTextBox"
constraints="{datePattern:'dd/MM/yy'}"/>

Related link from Roti