In Progress
Unit 1, Lesson 21
In Progress

Part 2: Telling Git Your Intentions

In a prior episode, guest chef Nadia Odunayo showed us how to make tightly focused and meaningful commits using the Git patch tool. Today, Nadia returns to teach us how to incorporate newly-created files into an efficient Git patch workflow. Enjoy!

Video transcript & code

Part 2 — git add --intent-to-add

see previous episode, "Patching with Git"


It's been a long, productive day and we've got some unstaged changes in our repo.


➜  rent-a-bike git:(master) ✗ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)

    modified:   lib/docking_station.rb
    modified:   spec/docking_station_spec.rb

Untracked files:
  (use "git add ..." to include in what will be committed)

    lib/person.rb
    spec/person_spec.rb

no changes added to commit (use "git add" and/or "git commit -a")
➜  rent-a-bike git:(master) ✗ 

We want to commit all of them. What do we do?

Well, if you watched part one of this series, you know that the correct answer is to use git add --patch, or -p for short:


➜  rent-a-bike git:(master) ✗ git add --patch
diff --git a/lib/docking_station.rb b/lib/docking_station.rb
index dfd47e3..054f403 100755
--- a/lib/docking_station.rb
+++ b/lib/docking_station.rb
@@ -14,8 +14,8 @@ class DockingStation
     @bikes << bike
   end
 
-  def release bike
-    @bikes.delete(bike)
+  def release
+    @bikes.pop
   end
 
   def full?
Stage this hunk [y,n,q,a,d,/,e,?]? q

So, that's what we do.

However, when we're done reviewing the changes and we check git status we find that the new files we've added were not included in the git --patch review:


$ git status

On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)

    modified:   lib/person.rb
    modified:   spec/person_spec.rb

Because they're new, git doesn't know anything about them, and so doesn't know to look for a diff.

So, now we add those files separately:


$ git add lib/person.rb
$ git add spec/person_spec.rb

And then we review the changes using git diff --cached.


➜  rent-a-bike git:(master) ✗ git add spec/person_spec.rb
➜  rent-a-bike git:(master) ✗ 

➜  rent-a-bike git:(master) ✗ git diff --cached
diff --git a/lib/docking_station.rb b/lib/docking_station.rb
index dfd47e3..054f403 100755
--- a/lib/docking_station.rb
+++ b/lib/docking_station.rb
@@ -14,8 +14,8 @@ class DockingStation
     @bikes << bike
   end
 
-  def release bike
-    @bikes.delete(bike)
+  def release
+    @bikes.pop
   end
 
   def full?
Stage this hunk [y,n,q,a,d,/,e,?]? y

diff --git a/lib/person.rb b/lib/person.rb
new file mode 100644
:

I don't know about you but, to me, this is a very awkward workflow.

On top of that, it's very easy to catch yourself paginating through the cached diff quickly.


index e69de29..da3ea0c 100644
--- a/spec/person_spec.rb
+++ b/spec/person_spec.rb
@@ -0,0 +1,47 @@
+require 'person'
+
+describe Person do
+
+  let(:person) {Person.new}
+  let(:station) {double :station}
+
+  it 'does not have a bike' do
+    expect(person.has_bike?).to eq(false)
+  end
+
+  it 'has a bike' do
+    person = Person.new :bike
+    expect(person.has_bike?).to eq(true)
+  end
+
+  it 'can rent a bike from the station' do
+    expect(station).to receive(:release)
+    person.rent_bike_from station
+  end
+
+  it 'has a bike after renting from the station' do
+    expect(station).to receive(:release).and_return :bike
+    person.rent_bike_from station
+    expect(person.has_bike?).to eq(true)
+  end
+
+  it 'returns a bike to the station' do
+    expect(station).to receive(:dock)
+    person.return_bike_to station
+  end
+
+  it 'has no bike after returning it to the station' do
+    person = Person.new :bike
+    station = double :station, {:dock => :bike}
+    person.return_bike_to station
+    expect(person.has_bike?).to eq(false)
+  end
+
+  it 'has an accident and breaks bike' do
+    bike = double :bike
+    person = Person.new bike
+    expect(bike).to receive (:break!)
+    person.has_accident
+  end
+
+end
(END)

At this point, we're losing the benefits that we were seeking from using git add --patch in the first place.

So, how can we get all of the advantages of using git add --patch when our changes include new files?

We can use git add --intent-to-add.

Or git add -N for short:

The --intent-to-add option tells git to start tracking any files that you pass in as an argument. Here I add a . to tell git to start tracking all new files:


$ git add -N .  # tell git to track all new files

This means that when you later do a git add --patch or diff, you can see all of the content of new files alongside the changes in modified files.

So, when new files are involved, do a git add -N . to track them all, and then you can do your git add --patch as normal.


➜  rent-a-bike git:(master) ✗ git add -N .           
➜  rent-a-bike git:(master) ✗ 

➜  rent-a-bike git:(master) ✗ git add -p
diff --git a/lib/docking_station.rb b/lib/docking_station.rb
index dfd47e3..054f403 100755
--- a/lib/docking_station.rb
+++ b/lib/docking_station.rb
@@ -14,8 +14,8 @@ class DockingStation
     @bikes << bike
   end
 
-  def release bike
-    @bikes.delete(bike)
+  def release
+    @bikes.pop
   end
 
   def full?
Stage this hunk [y,n,q,a,d,/,e,?]? y

This leaves you with a streamlined workflow that enables you to carefully check all of your work and focus on crafting great commits.

Responses