Skip to content

lab 10 Working with Changes Directly

Goals

Git provides rich functionality to choose precisely what changes you want to stage. This includes both choosing individual lines to stage as well as editing the changes before they are staged without changing the existing file. These capabilities allow you to make and commit secondary changes while working on a task with minimal distractions.

First Change: Allow Any Number of Names

Change the “Hello, World” program to allow users to input any number of names.

hello.rb

name = ARGV || ["World"]

puts "Hello, #{name.join(" ")}!"

Add and Commit the Change

Now you’ll add the change using git add --patch. This will present you with an interactive interface to select “hunks” which are sets of changes. For now, you’ll have one hunk. Press the ‘y’ key to select it for staging. You can press the ‘?’ key to get help about the other options.

Execute:

git add --patch

Execute:

git commit -m "Can specify multiple names"

Note: I used a utility called yes to simulate pressing the ‘y’ key when the git add --patch command prompted me for it. I recommend you don’t do this and instead enter your input manually.

Output:

$ git add --patch
diff --git a/hello.rb b/hello.rb
index 437e63c..d6755ed 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,4 +1,3 @@
-# Default is "World"
-name = ARGV.first || "World"
+name = ARGV || ["World"]
 
-puts "Hello, #{name}!"
+puts "Hello, #{name.join(" ")}!"
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? 

$ git commit -m "Can specify multiple names"
[main 8cdd2cd] Can specify multiple names
 1 file changed, 2 insertions(+), 3 deletions(-)

Second Change: Add Some More Output

Change the “Hello, World” program to print out the number of names a user entered.

hello.rb

name = ARGV || ["World"]

puts "Hello, #{name.join(" ")}!"
puts "You have #{name.length} names!"

Third Change: Rename the name Variable

The name variable is indicating it’s singular when it’s actually plural. Change its name to match its usage.

hello.rb

names = ARGV || ["World"]

puts "Hello, #{names.join(" ")}!"
puts "You have #{names.length} names!"

Add The Changes

Now you’ve made two changes which should be committed as two commits. Instead of undoing one of the changes, committing it, and redoing it you’ll use git add --patch to let you do it directly. Run the following command and press the ‘e’ key when prompted.

Execute:

git add --patch

You’ll see the following open up in your editor. It is a description of the changes git will stage when the file is saved and closed. Editing this file will change exactly what is staged.

Output:

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,3 +1,4 @@
-name = ARGV || ["World"]
+names = ARGV || ["World"]

-puts "Hello, #{name.join(" ")}!"
+puts "Hello, #{names.join(" ")}!"
+puts "You have #{names.length} names!"
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

Let’s do the variable rename first. Edit the file so it matches the example below. I just removed the last line starting with a ‘+’.

Output:

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,3 +1,4 @@
-name = ARGV || ["World"]
+names = ARGV || ["World"]

-puts "Hello, #{name.join(" ")}!"
+puts "Hello, #{names.join(" ")}!"
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

Save and exit, then let’s check that we did everything correctly.

Execute:

git diff --staged

You should see output like below

Note: the --staged option to git diff will only show your currently staged changes.

Output:

$ git diff --staged
diff --git a/hello.rb b/hello.rb
index d6755ed..42754aa 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,3 +1,4 @@
-name = ARGV || ["World"]
+names = ARGV || ["World"]
 
-puts "Hello, #{name.join(" ")}!"
+puts "Hello, #{names.join(" ")}!"
+puts "You have #{names.length} names!"

Finally let’s commit the change.

Execute:

git commit -m "Rename variable to match its usage"

Output:

$ git commit -m "Rename variable to match its usage"
[main c72af6b] Rename variable to match its usage
 1 file changed, 3 insertions(+), 2 deletions(-)

Now you’ll add the new output. This one isn’t necessary to edit, so you can just press the ‘y’ key to add it.

Execute:

git add --patch

Execute:

git commit -m "Tell user how many names they have"

Output:

$ git add --patch
diff --git a/hello.rb b/hello.rb
index 42754aa..1910891 100644
--- a/hello.rb
+++ b/hello.rb
@@ -2,3 +2,4 @@ names = ARGV || ["World"]
 
 puts "Hello, #{names.join(" ")}!"
 puts "You have #{names.length} names!"
+puts "You have #{names.length} names!"
(1/1) Stage this hunk [y,n,q,a,d,e,?]? 

$ git commit -m "Tell user how many names they have"
[main 1dee7f9] Tell user how many names they have
 1 file changed, 1 insertion(+)

Wrapping Up

In this lab you learned how to interact directly with the changes to be staged before they are staged. You simply staged some lines independently of others, but you could also have modified the content of the lines as well. Note that while the interface through git can be somewhat cumbersome there are several tools that integrate with your editor to do this for you. Here is an example of one for VSCode