• בלוג
  • גיט: איך לבטל תיקון ישן אחרי שהבנת שזה לא היה רעיון טוב

גיט: איך לבטל תיקון ישן אחרי שהבנת שזה לא היה רעיון טוב

פוסט זה כולל טיפ קצר על כלי עבודה בסביבת Linux. בשביל ללמוד יותר על עבודה בסביבת Linux ו Unix אני ממליץ לכם לבדוק את קורס Linux שיש לנו כאן באתר. הקורס כולל מעל 50 שיעורי וידאו והמון תרגול מעשי ומתאים גם למתחילים.
 

הבעיה המרכזית בממשק של גיט איננה שורת הפקודה, אלא העובדה שהוא זורק עלינו עולם מושגים שלם שלא תמיד מתחבר לעולם המושגים שלנו. אבל מי שיתאמץ להבין אותו יגלה שהרבה פעמים ההשקעה היתה שווה את זה, כמו בסיפור הבא על מחיקת תיקון ישן.

1. בואו נבנה בלוג

טוב אולי לא בלוג מלא אבל לפחות פונקציה קטנה שלוקחת פוסט ומחזירה URL לפוסט הזה. לפוסטים אצלנו אגב יש שם ומזהה, וה URL של כל פוסט מורכב מהתחילית blog ומהמזהה:

class Post
  attr_accessor :id, :name
  def initialize(id, name)
    @id = id
    @name = name
  end
end

def url_for(post)
  "/blog/#{post.id}"
end

p = Post.new(2, 'hello-world')
puts url_for(p)

הקוד בשפת רובי וכשמפעילים אותו מקבלים:

/blog/2

שהוא הקישור לפוסט שהמזהה שלו הוא 2.

נכניס את זה ל git repository כי פוסט על גיט והכל:

$ git init
$ git add .
$ git commit -m 'initial commit'

2. זמן עובר

ואתה חושב לעצמך שבטח גוגל כועס בגלל שה URL כולל מספרים ולא נראה קשור לתוכן הפוסט. אז אתה מחליט שהגיע הזמן לשנות את הפונקציה כך שתציג את ה name של כל פוסט במקום את ה id שלו. לא בעיה נכון? הנה הצעה לתיקון:

class Post
  attr_accessor :id, :name
  def initialize(id, name)
    @id = id
    @name = name
  end
end

def url_for(post_name)
  "/blog/#{post_name}"
end

p = Post.new(2, 'hello-world')
puts url_for(p.name)

ועל זה אפשר להגיע ״עקום אבל עובד״. כי ה URL עכשיו מתחיל להציג את שם הפוסט. ברור שהיה עדיף לשנות רק את תוכן הפונקציה במקום את הממשק שלה ואת כל הקוד שקורא לה, אבל בזמן אמת לא תמיד רואים את הפיתרון הברור.

מה שכן היית מספיק חכם להכניס גם את התיקון הזה ל git repository, כי פוסט על גיט וכו':

$ git add .
$ git commit -m 'Changed url_for to show friendly names'

3. הזמן עובר

ושמעת מחבר שטסטים זה הדבר הגדול הבא אז החלטת להוסיף טסט לתוכנית שנהיה בטוחים שה URL אכן מציג את השמות:

require 'minitest'
require 'minitest/rg'

class Post
  attr_accessor :id, :name
  def initialize(id, name)
    @id = id
    @name = name
  end
end

def url_for(post_name)
  "/blog/#{post_name}"
end

class PostTest < MiniTest::Test
  def test_url_uses_names
    p = Post.new(2, 'hello-world')
    q = Post.new(4, 'git-tips')
    assert_equal('/blog/hello-world', url_for(p.name))
    assert_equal('/blog/git-tips', url_for(q.name))
  end
end

Minitest.run

וסוף סוף אפשר להריץ את התוכנית ובמקום לראות URL ולשבור את הראש אם זה מה שהיה צריך להופיע נקבל:

Run options: --seed 28117

# Running:

.

Finished in 0.001478s, 676.5902 runs/s, 1353.1803 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

ומה עם הגיט? גם את השינוי הזה נוסיף לריפוזיטורי:

$ git add .
$ git commit -m 'Add tests'

4. ואז זה מגיע

אולי משהו בטסט, אולי באוויר, לא יודע. אבל פתאום הבנת שהתיקון ההוא ששינה את כל הממשק של הפונקציה url_for במקום לשנות שורה אחת בתוך גוף הפונקציה היה ממש טפשי. וזה גם הזמן לשמוח ששמרנו הכל בגיט. כך נראה הלוג:

$ git log
commit 1c486c42f3a00d891188674cb99b0b383af9c2c2 (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Fri Jan 26 09:16:54 2018 +0200

    Add tests

commit d66b476e4cd25415d599bf52d9118e48f31c2c89
Author: ynonp <ynonperek@gmail.com>
Date:   Fri Jan 26 09:15:58 2018 +0200

    Changed url_for to show friendly names

commit f8e7ef78ef360fadda02ff4a73d9b561b78bf4b1
Author: ynonp <ynonperek@gmail.com>
Date:   Fri Jan 26 09:15:20 2018 +0200

    initial commit

כל מה שצריך זה למחוק את הקומיט האמצעי! והנה הזדמנות לדבר על rebase. קודם הפקודה:

$ git rebase -p --onto d66b476^ d66b476

ובעברית זה אומר לגיט:

  1. קח את כל השינויים שקרו בין קומיט d66b476 ל head, כלומר את:
$ git log d66b476..head
commit 1c486c42f3a00d891188674cb99b0b383af9c2c2 (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Fri Jan 26 09:16:54 2018 +0200

    Add tests

ושים אותם בצד.

  1. עכשיו תבצע checkout לגירסא שהעברנו ב onto, שזו גירסא אחת לפני קומיט d66b476 (כלומר הגירסא הראשונה אצלנו בריפוזיטורי). זה מוביל למחיקה של כל השינויים שקרו אחרי גירסא זו מתיקיית העבודה.

  2. עכשיו תתחיל לבצע את השינויים מ (1) לפי הסדר.

נפעיל ונראה מה קורה:

$ git rebase -p --onto d66b476^ d66b476
error: could not apply 1c486c4... Add tests

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not pick 1c486c42f3a00d891188674cb99b0b383af9c2c2

גיט מספר לנו שיש קונפליקט: הקומיט שהוסיף את הבדיקות התבסס על קוד שכבר לא קיים ולכן גיט לא ידע מה לעשות עם השורות שהתנגשו. במקום להחליט הוא שם את כל השורות בקובץ ומבקש מאתנו לשבור את הראש מה הגירסא הנכונה. הקובץ demo.rb שלנו נראה עכשיו כך:

require 'minitest'
require 'minitest/rg'

class Post
  attr_accessor :id, :name
  def initialize(id, name)
    @id = id
    @name = name
  end
end

def url_for(post)
  "/blog/#{post.id}"
end

<<<<<<< HEAD
p = Post.new(2, 'hello-world')
puts url_for(p)
=======
class PostTest < MiniTest::Test
  def test_url_uses_names
    p = Post.new(2, 'hello-world')
    q = Post.new(4, 'git-tips')
    assert_equal('/blog/hello-world', url_for(p.name))
    assert_equal('/blog/git-tips', url_for(q.name))
  end
end

Minitest.run
>>>>>>> 1c486c4... Add tests

הפונקציה url_for חזרה לגירסא הראשונה שלה שמקבלת post ומשתמשת ב id כדי לבנות את ה URL, ובנוסף יש לנו את שורות ההדפסה מהקומיט שלפני הקומיט שנמחק ואת קוד הבדיקות מהקומיט האחרון. נסדר את הקובץ ונקבל:

require 'minitest'
require 'minitest/rg'

class Post
  attr_accessor :id, :name
  def initialize(id, name)
    @id = id
    @name = name
  end
end

def url_for(post)
  "/blog/#{post.name}"
end

class PostTest < MiniTest::Test
  def test_url_uses_names
    p = Post.new(2, 'hello-world')
    q = Post.new(4, 'git-tips')
    assert_equal('/blog/hello-world', url_for(p))
    assert_equal('/blog/git-tips', url_for(q))
  end
end

Minitest.run

ועכשיו אפשר להמשיך את הריבייס:

$ git add demo.rb
$ git rebase --continue

אחרי כל השינויים הקובץ demo.rb כולל את הקוד המתוקן וצפיה בגיט לוג לא מזכירה את הטעות המביכה:

$ git log
commit 3df1c49e4d63ee8a5c67acdbcc880b83668bafb7 (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Fri Jan 26 09:16:54 2018 +0200

    Add tests

commit f8e7ef78ef360fadda02ff4a73d9b561b78bf4b1
Author: ynonp <ynonperek@gmail.com>
Date:   Fri Jan 26 09:15:20 2018 +0200

    initial commit