วิธีการย้อนกลับคอมไพล์หลายคอมมิท


982

ฉันมีที่เก็บ git ที่มีลักษณะเช่นนี้:

A -> B -> C -> D -> HEAD

ฉันต้องการให้หัวหน้าสาขาชี้ไปที่ A เช่นฉันต้องการให้ B, C, D และ HEAD หายไปและฉันต้องการให้หัวมีความหมายเหมือนกันกับ A

ดูเหมือนว่าฉันจะพยายามรีบูต (ไม่ได้ใช้เนื่องจากฉันผลักการเปลี่ยนแปลงระหว่าง) หรือเปลี่ยนกลับ แต่ฉันจะยกเลิกการคอมมิชชันหลายรายการได้อย่างไร ฉันจะย้อนกลับทีละครั้งหรือไม่? คำสั่งซื้อสำคัญหรือไม่


3
หากคุณเพียงต้องการรีเซ็ตรีโมตคุณสามารถปิดบังสิ่งใดก็ได้! แต่ให้เราใช้ความมุ่งมั่นที่สี่ที่ผ่านมา: git push -f HEAD~4:master(สมมติว่าสาขาระยะไกลเป็นหลัก) ใช่คุณสามารถผลักดันการกระทำเช่นนั้นได้
u0b34a0f6ae

21
git revertถ้าคนได้ดึงคุณมีที่จะทำให้การกระทำที่ย้อนกลับการเปลี่ยนแปลงการใช้
Jakub Narębski

1
ใช้ git show HEAD ~ 4 เพื่อให้แน่ใจว่าคุณกำลังผลักไปทางขวาหนึ่งทางไกล
MâttFrëëman

1
มีความเป็นไปได้ที่ซ้ำกันของวิธียกเลิกการกระทำครั้งสุดท้ายใน Git
Jim Fell

5
"ลำดับสำคัญหรือไม่" ใช่ถ้าการกระทำส่งผลกระทบต่อบรรทัดเดียวกันในไฟล์เดียวกัน จากนั้นคุณควรเริ่มต้นคืนค่าคอมมิชชันล่าสุดและดำเนินการย้อนกลับ
avandeursen

คำตอบ:


1334

ขยายสิ่งที่ฉันเขียนในความคิดเห็น

กฎทั่วไปคือคุณไม่ควรเขียน (เปลี่ยน) ประวัติที่คุณเผยแพร่เพราะบางคนอาจใช้งานของพวกเขา หากคุณเขียนใหม่ (เปลี่ยน) ประวัติคุณจะสร้างปัญหากับการรวมการเปลี่ยนแปลงของพวกเขาและการปรับปรุงสำหรับพวกเขา

ดังนั้นวิธีแก้ไขคือสร้างคอมมิชชันใหม่ที่แปลงการเปลี่ยนแปลงที่คุณต้องการกำจัด คุณสามารถทำได้โดยใช้คำสั่งgit revert

คุณมีสถานการณ์ต่อไปนี้:

A <- B <- C <- D <- master <- HEAD

(ลูกศรที่นี่หมายถึงทิศทางของตัวชี้: การอ้างอิง "พาเรนต์" ในกรณีของการกระทำการส่งสูงสุดในกรณีของหัวหน้าสาขา (การอ้างอิงสาขา) และชื่อของสาขาในกรณีของการอ้างอิง HEAD)

สิ่งที่คุณต้องสร้างมีดังต่อไปนี้:

A <- B <- C <- D <- [(BCD) ^ - 1] <- master <- HEAD

ที่ "[(BCD) ^ - 1]" หมายถึงการกระทำที่ย้อนกลับการเปลี่ยนแปลงในการกระทำ B, C, D. คณิตศาสตร์บอกเราว่า (BCD) ^ - 1 = D ^ -1 C ^ -1 B ^ -1 ดังนั้น คุณสามารถรับสถานการณ์ที่ต้องการโดยใช้คำสั่งต่อไปนี้:

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"

ทางออกอื่นคือการชำระเงิน เนื้อหาของการกระทำ A และกระทำรัฐนี้:

$ git checkout -f A -- .
$ git commit -a

จากนั้นคุณจะมีสถานการณ์ต่อไปนี้:

A <- B <- C <- D <- A '<- master <- HEAD

กระทำ A 'มีเนื้อหาเช่นเดียวกับกระทำ A แต่กระทำแตกต่างกัน (กระทำข้อความผู้ปกครองวันกระทำ)

การแก้ปัญหาโดย Jeff Ferland แก้ไขโดย Charles Baileyสร้างจากแนวคิดเดียวกัน แต่ใช้การรีเซ็ต git :

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is D
$ git commit

40
หากคุณเพิ่มไฟล์ใน B, C หรือ D การgit checkout -f A -- .จะไม่ลบไฟล์เหล่านี้คุณจะต้องทำด้วยตนเอง ฉันใช้กลยุทธ์นี้แล้วขอบคุณ Jakub
oma

18
วิธีแก้ปัญหาเหล่านั้นไม่เท่ากัน ไฟล์แรกไม่ได้ลบไฟล์ที่สร้างขึ้นใหม่
m33lky

10
@Jerry: git checkout fooอาจหมายถึงสาขาการ ชำระเงินfoo(เปลี่ยนเป็นสาขา) หรือไฟล์เช็คเอาต์foo (จากดัชนี) --จะใช้ในการแก้ความกำกวมเช่นgit checkout -- fooมักจะเกี่ยวกับไฟล์
Jakub Narębski

87
นอกจากคำตอบที่ดี การจดชวเลขนี้เหมาะสำหรับฉันgit revert --no-commit D C B
welldan97

9
@ welldan97: ขอบคุณสำหรับความคิดเห็น เมื่อเขียนคำตอบนี้git revertไม่ยอมรับหลายคอมมิท มันค่อนข้างใหม่นอกจากนี้
Jakub Narębski

248

วิธีทำความสะอาดซึ่งฉันพบว่ามีประโยชน์

git revert --no-commit HEAD~3..

คำสั่งนี้ย้อนกลับ 3 การคอมมิทด้วยการคอมมิทเพียงครั้งเดียว

ยังไม่ได้เขียนประวัติ


16
นี่เป็นคำตอบที่ง่ายและดีที่สุด
Julien Deniau

3
@JohnLittle มันเป็นขั้นตอนการเปลี่ยนแปลง git commitจากนั้นจะทำการกระทำจริง
x1a4

14
สิ่งนี้จะไม่ทำงานหากคอมมิทบางอย่างเป็นการคอมมิทผสาน
MegaManX

5
จุดสองจุดสุดท้ายจะทำอะไร
กระวาน

5
@cardamom ระบุช่วงเหล่านั้น HEAD~3..เป็นเช่นเดียวกับHEAD~3..HEAD
Toine H

238

ในการทำเช่นนั้นคุณต้องใช้คำสั่งเปลี่ยนกลับระบุช่วงของการคอมมิทที่คุณต้องการรับการย้อนกลับ

เมื่อคำนึงถึงตัวอย่างของคุณคุณต้องทำเช่นนี้ (สมมติว่าคุณอยู่ใน 'ต้นแบบ' สาขา):

git revert master~3..master

สิ่งนี้จะสร้างคอมมิชชันใหม่ในพื้นที่ของคุณด้วยการกระทำผกผันของ B, C และ D (หมายความว่ามันจะยกเลิกการเปลี่ยนแปลงที่ทำโดยคอมมิทเหล่านี้):

A <- B <- C <- D <- BCD' <- HEAD

129
git revert --no-commit HEAD~2..เป็นวิธีสำนวนมากกว่าที่จะทำ หากคุณอยู่ในสาขาหลักไม่จำเป็นต้องระบุหลักอีกครั้ง --no-commitตัวเลือกที่ช่วยให้คอมไพล์พยายามที่จะย้อนกลับไปกระทำทั้งหมดในครั้งเดียวแทนการทิ้งขยะประวัติศาสตร์ที่มีหลายrevert commit ...ข้อความ (สมมติว่าของสิ่งที่คุณต้องการ)
kubi

6
@Victor ฉันแก้ไขช่วงคอมมิชชันของคุณ จุดเริ่มต้นของช่วงพิเศษเฉพาะซึ่งหมายความว่ามันไม่รวม ดังนั้นหากคุณต้องการที่จะเปลี่ยนช่วง 3 กระทำคุณจะต้องเริ่มต้นช่วงจากผู้ปกครองที่ 3 master~3กระทำคือ

2
@kubi ไม่มีวิธีรวม SHAs ในข้อความการส่งข้อความโดยใช้การกระทำเดียว (วิธีการของคุณ แต่ไม่ต้องป้อนการคอมมิทที่ถูกเปลี่ยนกลับ)?
Chris S

@ChrisS ความคิดแรกของฉันคือการไม่ใช้--no-commit(เพื่อให้คุณได้รับการมอบหมายแยกต่างหากสำหรับการกลับคืนแต่ละครั้ง) จากนั้นสควอชพวกเขาทั้งหมดเข้าด้วยกันในการรีบูตแบบโต้ตอบ ข้อความคอมมิทรวมจะมี SHA ทั้งหมดและคุณสามารถจัดเรียงข้อความเหล่านั้นได้ตามที่คุณต้องการ
Radon Rosborough

71

เช่นเดียวกับคำตอบของจาคุบสิ่งนี้จะช่วยให้คุณสามารถเลือกคอมมิทที่ต่อเนื่องกันเพื่อเปลี่ยนกลับ

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'

9
โซลูชันของคุณใช้ได้ดีสำหรับฉัน แต่มีการปรับเปลี่ยนเล็กน้อย หากเรามีกรณีนี้ Z -> A -> B -> C -> D -> HEAD และถ้าฉันต้องการกลับไปที่สถานะ A แล้วก็แปลกฉันจะต้องดำเนินการคืนค่า git - no-commit Z .. HEAD
Bogdan

3
เห็นด้วยกับ @Bogdan ช่วงเปลี่ยนกลับเป็นเช่นนั้น: SHA_TO_REVERT_TO..HEAD
Vadym Tyemirov

11
ช่วงไม่ถูกต้อง มันควรจะเป็นB^..HEADมิฉะนั้น B จะถูกยกเว้น
tessus

3
เห็นด้วยกับ @tessus ดังนั้นสิ่งที่ควรทำคือ: git revert --no-commit B^..HEADหรือgit revert --no-commit A..HEAD
Yoho

64
git reset --hard a
git reset --mixed d
git commit

ที่จะทำหน้าที่เป็นย้อนกลับสำหรับพวกเขาทั้งหมดในครั้งเดียว ให้ข้อความยืนยันที่ดี


4
หากเขาต้องการHEADมีลักษณะเช่นAนั้นเขาอาจต้องการให้ดัชนีตรงกันดังนั้นgit reset --soft Dน่าจะเหมาะสมกว่า
CB Bailey

2
- การรีเซ็ตซอฟต์แวร์ไม่ได้ย้ายดัชนีดังนั้นเมื่อเขาทำคอมมิทมันดูเหมือนว่าคอมมิทจะมาจากโดยตรงแทนที่จะเป็นจาก D นั่นจะทำให้การแตกสาขา - ผสมออกจากการเปลี่ยนแปลง แต่ย้ายตัวชี้ดัชนีดังนั้น D จะกลายเป็นผู้ปกครองกระทำ
Jeff Ferland

5
ใช่ฉันคิดว่าการรีเซ็ตคอมไพล์ - ให้เป็นสิ่งที่ฉันมีอยู่ด้านบน มันออกมาในเวอร์ชั่น 1.7.1 วางจำหน่ายในเดือนเมษายน 2010 ดังนั้นคำตอบไม่ได้อยู่ในช่วงเวลานั้น
Jeff Ferland

git checkout Aถ้าอย่างนั้นgit commitข้างบนก็ไม่ได้ผลสำหรับฉัน แต่คำตอบนี้ใช้ได้
SimplGy

ทำไมgit reset --mixed Dต้องมี? โดยเฉพาะทำไมreset? เป็นเพราะถ้าไม่มีการรีเซ็ตเป็น D แล้ว HEAD จะชี้ไปที่ A ทำให้ B, C และ D เป็น "ห้อย" และเก็บขยะ - ซึ่งไม่ใช่สิ่งที่เขาต้องการ แต่ทำไม--mixedล่ะ คุณตอบแล้ว "การ--softรีเซ็ตไม่ย้ายดัชนี ... " ดังนั้นโดยการย้ายดัชนีนี่หมายความว่าดัชนีจะมีการเปลี่ยนแปลงของ D ในขณะที่ไดเรกทอรีการทำงานจะมีการเปลี่ยนแปลงของ A - ด้วยวิธีนี้git statusหรือgit diff(ซึ่งเปรียบเทียบดัชนี [D] ถึง Working Directory [A]) จะแสดงเนื้อหา; ผู้ใช้รายนั้นเปลี่ยนจาก D กลับเป็น A
ถั่วแดง

39

ก่อนอื่นต้องแน่ใจว่าสำเนาการทำงานของคุณไม่ได้ถูกแก้ไข แล้ว:

git diff HEAD commit_sha_you_want_to_revert_to | git apply

และจากนั้นเพียงแค่กระทำ อย่าลืมจัดทำเอกสารเหตุผลในการเปลี่ยนกลับ


1
ทำงานให้ฉัน! มีปัญหากับการเปลี่ยนแปลงสาขาฟีลด์ที่เกิดขึ้นในการพัฒนาสาขา (การแก้ไขข้อบกพร่องบางอย่าง) ดังนั้นสาขาฟีเจอร์ต้องเขียนทับการเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นในการพัฒนา (รวมถึงการลบไฟล์บางส่วน)
silentser

1
จะไม่ทำงานกับไฟล์ไบนารี:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
GabLeRoux

2
นี่เป็นคำตอบที่ยืดหยุ่นกว่าคำตอบที่ยอมรับ ขอบคุณ!
Brian Kung

2
เรื่อง: ไฟล์ไบนารีใช้ - ตัวเลือกไบนารี: git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
weinerk

2
สิ่งนี้จะใช้ได้แม้ว่าคุณต้องการย้อนกลับช่วงของการคอมมิทที่มีการคอมมิทรวม เมื่อใช้git revert A..Zคุณจะได้รับerror: commit X is a merge but no -m option was given.
Juliusz Gonera

35

ฉันผิดหวังมากที่คำถามนี้ไม่สามารถตอบได้ คำถามอื่น ๆ ทุกข้อเกี่ยวข้องกับวิธีการย้อนกลับอย่างถูกต้องและรักษาประวัติ คำถามนี้บอกว่า"ฉันต้องการให้หัวหน้าสาขาชี้ไปที่ A เช่นฉันต้องการให้ B, C, D และ HEAD หายไปและฉันต้องการให้หัวมีความหมายเหมือนกันกับ A. "

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f

ฉันเรียนรู้การอ่านโพสต์ของจาคุบมาก แต่มีบางคนใน บริษัท (ที่สามารถเข้าถึงสาขา "การทดสอบ" ของเราโดยไม่ต้องขอแบบดึงได้) ผลักเหมือน 5 ข้อผิดพลาดที่พยายามแก้ไขและแก้ไขและแก้ไขข้อผิดพลาด ไม่เพียงแค่นั้น แต่ยอมรับคำขอการดึงหนึ่งหรือสองครั้งซึ่งตอนนี้ไม่ดี ดังนั้นลืมฉันพบความมุ่งมั่นสุดท้าย (abc1234) และเพิ่งรันสคริปต์พื้นฐาน:

git checkout testing
git reset --hard abc1234
git push -f

ฉันบอกอีก 5 คนที่ทำงานใน repo นี้ว่าพวกเขาควรจดบันทึกการเปลี่ยนแปลงของพวกเขาในสองสามชั่วโมงที่ผ่านมาและ Wipe / Re-Branch จากการทดสอบล่าสุด จบเรื่อง


2
ฉันไม่ได้เผยแพร่ความมุ่งมั่นดังนั้นนี่คือคำตอบที่ฉันต้องการ ขอบคุณ @Suamere
Tom Barron

วิธีที่ดีกว่าในการทำเช่นนี้คือgit push --force-with-leaseซึ่งจะเขียนประวัติศาสตร์เฉพาะในกรณีที่ไม่มีใครมุ่งมั่นที่สาขาหลังจากหรือภายในช่วงที่มุ่งมั่นที่จะ vapourize หากคนอื่นใช้สาขาแล้วประวัติศาสตร์ของมันไม่ควรถูกเขียนใหม่และการกระทำนั้นควรจะถูกเปลี่ยนกลับอย่างเห็นได้ชัด
frandroid

1
@frandroid "ประวัติไม่ควรถูกเขียนใหม่" เพียงข้อตกลง sith ใน absolutes คำถามของเธรดนี้และจุดคำตอบของฉันนั้นแน่นอนว่าสำหรับสถานการณ์เฉพาะประวัติทั้งหมดควรถูกลบทิ้ง
Suamere

@Suamere แน่นอนว่าเป็นคำถาม แต่เมื่อคำตอบของคุณกล่าวถึงสิ่งที่คุณต้องบอกคนอื่น ๆ อาจมีปัญหาได้ จากประสบการณ์ส่วนตัว push -f สามารถทำให้รหัสฐานของคุณยุ่งเหยิงหากคนอื่น ๆ มีความมุ่งมั่นหลังจากสิ่งที่คุณพยายามลบ - บังคับกับการเช่าทำให้ได้ผลลัพธ์เดียวกันยกเว้นว่ามันจะช่วยตูดคุณหากคุณกำลังจะทำธุรกรรมซื้อคืน ทำไมถึงมีโอกาส หาก --force-with-lease ล้มเหลวคุณสามารถดูว่าการกระทำใดได้รับในทางประเมินอย่างถูกต้องปรับและลองอีกครั้ง
frandroid

1
@Suamere ขอบคุณ! ฉันเห็นด้วยกับคำถามที่ระบุไว้อย่างชัดเจนว่ามันต้องการที่จะเขียนประวัติศาสตร์ ฉันอยู่ในสถานการณ์เดียวกับคุณและฉันเดาว่า OP มีใครบางคนที่ทำสิ่งที่น่าเกลียดและการกระทำผิดปกติและการแปลงกลับโดยอุบัติเหตุโดยบังเอิญ ไม่ว่าในกรณีใด ๆ พร้อมกับคำเตือนที่ดีต่อสุขภาพนี่ควรเป็นคำตอบที่ได้รับการยอมรับ
Lee Richardson

9

นี่คือการขยายตัวของหนึ่งในโซลูชั่นที่ให้ไว้ในคำตอบของจาคุบ

ฉันต้องเผชิญกับสถานการณ์ที่ความมุ่งมั่นที่ฉันต้องการย้อนกลับค่อนข้างซับซ้อนด้วยความมุ่งมั่นหลายอย่างที่รวมเข้าด้วยกันและฉันต้องหลีกเลี่ยงการเขียนประวัติศาสตร์ใหม่ ฉันไม่สามารถใช้ชุดgit revertคำสั่งได้ในที่สุดฉันก็พบกับข้อขัดแย้งระหว่างการเปลี่ยนแปลงการพลิกกลับที่เพิ่มเข้ามา ฉันลงเอยด้วยขั้นตอนต่อไปนี้

ขั้นแรกให้ตรวจสอบเนื้อหาของเป้าหมายที่กระทำขณะออกจาก HEAD ที่ส่วนปลายของสาขา:

$ git checkout -f <target-commit> -- .

(- ทำให้แน่ใจว่า<target-commit>ถูกตีความว่าเป็นการกระทำมากกว่าไฟล์;. อ้างถึงไดเรกทอรีปัจจุบัน)

จากนั้นพิจารณาว่าไฟล์ใดที่ถูกเพิ่มลงในคอมมิทที่ถูกย้อนกลับและจำเป็นต้องลบ:

$ git diff --name-status --cached <target-commit>

ไฟล์ที่เพิ่มควรแสดงด้วย "A" ที่จุดเริ่มต้นของบรรทัดและไม่ควรมีความแตกต่างอื่น ๆ ตอนนี้หากจำเป็นต้องลบไฟล์ใด ๆ ให้ทำการลบไฟล์เหล่านี้:

$ git rm <filespec>[ <filespec> ...]

ในที่สุดกระทำการพลิกกลับ:

$ git commit -m 'revert to <target-commit>'

หากต้องการตรวจสอบให้แน่ใจว่าเรากลับสู่สถานะที่ต้องการ:

$git diff <target-commit> <current-commit>

ไม่ควรมีความแตกต่าง


คุณแน่ใจแล้วหรือว่าคุณสามารถgitเป็นหัวหน้าได้ด้วยแค่ปลายกิ่ง?
Suamere

2
นี่เป็นวิธีแก้ปัญหาที่ดีกว่าสำหรับฉันตั้งแต่ฉันรวมเข้าด้วย
sovemp

3

วิธีที่ง่ายที่จะเปลี่ยนกลุ่มกระทำในพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน (ที่คนใช้และคุณต้องการที่จะรักษาประวัติศาสตร์) คือการใช้งานร่วมกับคอมไพล์git revert rev-listคนหลังจะให้รายชื่อของคุณกับคำมั่นสัญญาอดีตจะทำการคืนค่าตัวเอง

มีสองวิธีในการทำเช่นนั้น หากคุณต้องการเปลี่ยนกลับหลายกระทำในการใช้กระทำเดียว:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $i; done

สิ่งนี้จะเปลี่ยนกลุ่มของการกระทำที่คุณต้องการ แต่ปล่อยให้การเปลี่ยนแปลงทั้งหมดในแผนผังการทำงานของคุณคุณควรกระทำทั้งหมดตามปกติ

ตัวเลือกอื่นคือให้มีการคอมมิชชันเดียวต่อการเปลี่ยนแปลงที่ย้อนกลับ:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

ตัวอย่างเช่นหากคุณมีต้นไม้ที่ผูกพัน

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa

หากต้องการเปลี่ยนการเปลี่ยนแปลงจากeeeเป็นbbbให้เรียกใช้

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done

เพิ่งใช้มัน ขอบคุณ!
Ran Biron

2

ไม่มีสิ่งใดที่เหมาะกับฉันดังนั้นฉันมีสามข้อผูกพันที่จะคืนค่า (สามข้อตกลงล่าสุด) ดังนั้นฉันจึง:

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash

ทำงานเหมือนมีเสน่ห์ :)


2
นี่เป็นตัวเลือกที่ทำงานได้เฉพาะในกรณีที่การเปลี่ยนแปลงของคุณยังไม่ถูกผลักดัน
kboom

0

ในความคิดของฉันเป็นวิธีที่ง่ายและสะอาดมากอาจเป็น:

กลับไปที่ A

git checkout -f A

ชี้ไปที่สถานะปัจจุบัน

git symbolic-ref HEAD refs/heads/master

บันทึก

git commit

1
คุณช่วยอธิบายสาเหตุของการ downvoting ได้ไหม?
nulll

ใช้งานได้ดี อะไรคือจุดที่ทำให้ downvote สำหรับคำตอบที่มีประโยชน์นี้หรือใคร ๆ สามารถอธิบายได้ว่าแนวปฏิบัติที่ดีที่สุดคืออะไร
Levent Divilioglu

เป็นเช่นเดียวกับgit checkout master; git reset --hard A? หรือถ้าคุณไม่สามารถอธิบายเพิ่มเติมเกี่ยวกับสิ่งนี้ได้บ้าง
MM

ใช้งานได้ แต่หัวอ้างอิงเชิงสัญลักษณ์ดูเหมือนจะไม่ใช่คำสั่ง "ปลอดภัย"
Sérgio

เอ่อฉันไม่ต้องการหัวหน้าแก้ไขฉันต้องการแก้ไขสาขา
Sérgio

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.