Drupal Zombie

Dezombifying Drupal developers since 2011

    Braaains!
Eat braaains!
News feed

When zombies attack (your code), GIT can help

Submitted by Kieran on

GIT is the new Drupal version control system. It's not just for Drupal.Org, though. You can use it on your own modules, even if you don't plan to contribute them.

GIT is complex. It can zombify you. Strange terms, commands, switches, workflows. Your brain can overload, and stop altogether. Zombie!

But learning a little GIT can be a good thing. Let's say you're writing the next Great Module. It will analyze zombie flocking behavior, looking for signs of sentient zombie leaders. Flocking data is flocking hard to analyze. You'll need to try different mathematical methods, parameterized in different ways.

You want to be able to experiment with your code, keep the stuff that works, throw away what doesn't. GIT can help. You can be a Fearless Experimenter, adding, changing, and deleting code all over the place. GIT has your back.

You don't have to learn all of GIT to get most of its benefits. A little dab'll do ya.

Let's see how GIT can help make it safe for you to experiment. With code, that is. We won't use any command line stuff. All GUI, all the way.

A warning: I'm not a GIT expert. The instructions below work for me. But there may be better ways.

Snapshots, commits, and other GITty words

The first word is a "revision." It's a copy of all of your files at a point in time. Like making a zip file of everything, and storing it away. If zombies smash your code, you can always grab the last revision, or the revision before that.

I prefer the word "snapshot." The word evokes an image of something captured in time.

At any moment, the code you're working on is a combination of (1) the code in a snapshot, and (2) changes you have made since the snapshot was made. When you make a new snapshot, you fold those two together.

Snapshots

Figure 1. Snapshots

The changes in Figure 1 are called "commits" in GITese. When you commit, you create a new snapshot.

A note for people used to older systems, like CVS: in GIT, "commit" does not mean "send changes to a central repository." GIT separates committing (creating a new snapshot) from pushing (sending changes to another repository).

The collection of shapshots for a project is a "repository," or "repo."

Repository

Figure 2. Repository

The most recent snapshot is often called "head," or "HEAD." That stands for "hierarchically expressed application data." ... No, I made that up. It doesn't stand for anything.

Example: I'm so over pirates vs. ninjas

"Pirates or ninjas?" was an important question last year. Not so much anymore. The new question is: "Plants or zombies?"

Let's write a module that replaces the old question with the new. It won't change the content in the database, but will affect the way it's rendered.

Setting up the files

To start, we'll do a fresh Drupal 7 install, and throw in admin_menu and devel modules. Then add a directory for the module, and .info and .module files. Here are the files we'll start with:

Files

Figure 3. Files

This screen shot is from Netbeans, my IDE of choice. It has a GIT plugin.

Here's the .info file:

  1. name = Pirates to plants
  2. description = From pirates vs ninjas, to plants vs zombies.
  3. package = Drupal Zombie
  4. core = 7.x
  5. files[] = pirates_to_plants.module

Here's the .module:

  1. <?php
  2. function pirates_to_plants_node_view_alter(&$build) {
  3.   drupal_set_message('Plants, not pirates');
  4. }

hook_node_view_alter() lets a module change a node before it is rendered.

Enable the module, create an article, and see:

Message

Figure 4. Message

The module is working! Yay!

But it's GITless. Let's add version control.

Creating a repository

Select a file, like pirates_to_plants.info, and click the menu item Team | Git | Initialize:

Create repository

Figure 5. Create repository

Netbeans likes to GIT entire projects, not just directories inside them. It's possible to GIT just our module, but for now let Netbeans' GIT plugin do what it wants.

Here's what we get:

Initial state - files

Initial state - code

Figure 6. Initial state

 

NetBeans' GIT plugin color codes stuff:

  • Green means "new."
  • Blue means "changed."
  • Red means something has been removed.

When a file name is green, it's a new file, not stored in the last revision. A blue icon on a directory means files in the directory have uncommitted changes. In the code, the green bar shows the code is new, and has not yet been committed.

Commit

Let's commit everything so far. This will be our first snapshot.

First commit

Figure 7. First commit

A dialog appears. Enter a comment about the commit, and click the Commit button:

Committing

Figure 8. Committing

Netbeans' GIT plugin does its thing. The green goes away. The files look like this:

Files

Figure 3 (again). Files

The code window:

No uncommitted code

Figure 9. No uncommitted code

Everything's green-free.

Team | Repository browser opens a new window:

Repository browser

Figure 10. Repository browser

master is the name of a "branch." Branching will be the subject of a future article. Maybe. If all the planets align.

That strange hexadecimal stuff (4032c66...) is the "object name" of the revision. It's a hash, that is, the value is computed from the contents of the files at this point.

Exploring a NAD

Recall that we're using hook_node_view_alter(). It fires before a node is  rendered. Here's the function's signature:

hook_node_view(&$build)

$build: A renderable array representing the node's content.

Notice the & in front of the parameter. It means "pass by reference." That means we can change $build, and the changes will be passed back to the caller.

We need to grab content from $build, so we can change the text, changing "pirate" to "plant," and "ninja" to "zombie." We'll only mess with two fields for now: the title and the body.

$build is, of course, a Nested Array of Doom (NAD). That's the Drupal way. How do we figure out where in $build the content is? The dpm() function can help:

  1. <?php
  2. function pirates_to_plants_node_view_alter(&$build) {
  3.   drupal_set_message('Plants, not pirates');
  4.   dpm($build);
  5. }

dpm() will output an array in a friendly form, so we can find the pieces we need to change.

The code window looks like this:

Code window

Figure 11. Code window

The green shows that this is new code.

The project window shows:

File changed

Figure 12. File changed

The .module file is blue. Blue means that the file has changed.

Drupal outputs $build:

dpm() output

Figure 13. dpm() output

I had to hunt and experiment for quite a while to find the right elements. $build['body']['#object']->title has the title. The body is in $build['body'][0]['#markup'].

Ooo! Exciting! Let's commit now (even though I don't really need the dpm() call any more). Select the project, then Team | Commit:

Select project

Commit

Figure 14. Commit

A dialog appears. Enter a commit message and click the Commit button:

Commit message

Figure 15. Commit message

Here's the repository browser:

New object name

Figure 16. New object name

Notice that the object name is different. It used to start with "4032c66." Now it starts with "8eda4a." Recall that the object name is a hash, computed from the contents of the files. Since one of the files has changed, we get a new value.

History and diff

The NetBeans GIT plugin can show the commit history:

Commit history

Figure 17. Commit history

There are the two snapshots. The commit messages show up, too; they're cut off in this screen shot.

The "Diff" link lets us compare the code in the snapshots.

Graphical diff

Figure 18. Graphical diff

As usual, green means "new stuff."

There are two tabs at the top of Figure 18: Graphical, and Textual. Figure 18 shows the graphical view. Here's the textual view:

Textual diff

Figure 19. Textual diff

This is a "patch file." The Unix patch command can use this to make changes to code. You sometimes see patch files in Drupal issue queues. For example, Views has the issue Custom Date format ajax support. One person asked for a new feature, and another person wrote a patch for it. To apply patches in NetBeans, use Tools | Apply diff patch.

Let's say I want to restore the old version of the .module file, from the original. Looking at the menus, you might think to use the revert command. That makes sense, but isn't right. Revert undoes changes that have not been committed yet. But we want to restore a file that has already been committed.

To restore a file from a snapshot, use "checkout." It doesn't sound right, but it is. Right-click on the .module file in the project window, and select GIT | Checkout Files.

The checkout command is used for a couple of different things, so there are some options we have to set. First, check the Update Index checkbox, so GIT knows you want to restore from a previous snapshot (aka revision):

Restore from previous shapshot

Figure 20. Restore from a previous shapshot

Now click the Select button (cut off in Figure 20), and choose the snapshot you want to restore from:

Select snapshot

Figure 21. Select snapshot

Click a couple of "OK, go do it" buttons. NetBean's GIT plugin will restore the file, the original version without dpm(). The file will be different from the latest shapshot (which has dpm()), so NetBeans will show the usual change indicators.

Showing changes

Showing changes

Figure 22. Showing changes

Remember that red means something has been deleted. In this case, a call to dpm().

The right NAD elements?

OK, time to write some module code. First, grab the title and body, and show them, to make sure that we're accessing the right NAD pieces:

  1. function pirates_to_plants_node_view($node, $view_mode) {
  2.   drupal_set_message('Title:' . $node->title);
  3.   drupal_set_message('Body: ' . $build['body'][0]['#markup']);
  4. }

Here's what we get:

Title and body

Figure 23. Title and body

Hooray! I'm so excited that I'm going to commit the changes, so I won't lose this code:

Select project

Commit

Figure 14 (again). Commit

My commit message: Showing the right NAD components.

First try

Let's use the PHP function str_replace(). Here's what the documentation says:

mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )

This function returns a string or an array with all occurrences of search in subject replaced with the given replace value.

Seems easy enough. We'll just do the title for now. Here's my new code:

  1. function pirates_to_plants_node_view_alter(&$build) {
  2.   $title = $build['body']['#object']->title;
  3.   $title = str_replace('pirate', 'plant', $title);
  4.   $title = str_replace('ninja', 'zombie', $title);
  5.   $build['body']['#object']->title = $title;
  6. }

Line 2 grabs the title from the NAD. Line 3 replaces "pirate" with "plant." Line 4 replaces "ninja" with "zombie." Line 5 puts the new title back into the NAD.

Here's what the result looks like:

Title partly right

Figure 24. Title partly right

Wait. It's supposed to be "Plants or zombies," not "Pirates or zombies." What's the deal?

(Think, think.)

Oh. "Pirates" and "pirates" are different. I need to use str_ireplace(), a case-insensitive version of str_replace().

Thing is, I'm not sure about this change. Let's commit before we edit, so we can easily undo everything.

Use Team | Commit, with a message of "Title partly right."

OK, that code is saved in a snapshot.

Time to experiment. Change to str_ireplace().

 Testing a code change

Figure 25. Testing a code change

Recall that blue means that the lines have changed.

Save the file and run:

Tried ireplace()

Figure 26. The result

Argh! (No, I'm not turning into a pirate.) Lost the capital P in Plants! That didn't work.

We don't want to commit the last set of changes. We want to get rid of them, roll back the code to the last snapshot.

That's what the "revert" command does. Revert restores files to what they were in the last snapshot. Only the last snapshot. Not an earlier one. You cannot revert changes that have been committed. You need to use "checkout" to go further back.

Let's make sure we know the difference between revert and checkout. Revert returns files to the last snapshot:

Revert

Figure 27. Revert

Pat changed some code, but didn't commit. She tested, and the code didn't work. She decided to use a different approach. She reverted, and all the files went back to what they had been at the last snapshot.

Here's checkout:

Checkout

Figure 28. Checkout

Pat committed. She did some coding, and committed again. Then she realized that she didn't mean to commit the changes to one of the files. Revert says, "Too late, they were already committed." Checkout says, "Nuh uh, not too late." Pat can checkout from any snapshot, but revert from the last one.

OK, is that straight? Back to reverting the last change to our program.

Select the project:

Select project

Figure 29. Select project

Then the Team | Revert Modifications command. It shows a dialog:

Revert dialog

Figure 30. Revert dialog

Leave the first one selected. (We don't care about the others now.) Click Revert.

Reverted code

Figure 31. Reverted code

The code it back to str_replace(). It's i-less.

Now what?

(Think, write, test, write, test, document.)

OK, got it.

  1. <?php
  2.  
  3. /*
  4.  * Implements hook_node_view_alter.
  5.  */
  6. function pirates_to_plants_node_view_alter(&$build) {
  7.   //Get the title from the NAD.
  8.   $title = $build['body']['#object']->title;
  9.   //Replace words.
  10.   $title = pirates_to_plants_str_replace('pirate', 'plant', $title);
  11.   $title = pirates_to_plants_str_replace('ninja', 'zombie', $title);
  12.   //Back into the NAD.
  13.   $build['body']['#object']->title = $title;
  14.   //Get the body from the NAD.
  15.   $body = $build['body'][0]['#markup'];
  16.   //Replace words.
  17.   $body = pirates_to_plants_str_replace('pirate', 'plant', $body);
  18.   $body = pirates_to_plants_str_replace('ninja', 'zombie', $body);
  19.   //Back into the NAD.
  20.   $build['body'][0]['#markup'] = $body;
  21. }
  22.  
  23. /**
  24.  * Replace $find in $subject with $replace_with. For each instance of
  25.  * $find in $subject, maintain the case of the first character
  26.  * of the piece of $subject that is to be replaced.
  27.  *
  28.  * @param string $find The string to be replaced.
  29.  * @param string $replace_with The string to replace it with.
  30.  * @param string $subject The string to be changed.
  31.  * @return string The result.
  32.  */
  33. function pirates_to_plants_str_replace($find, $replace_with, $subject) {
  34.   $replace_with = strtolower($replace_with);
  35.   while ( stripos($subject, $find) !== FALSE ) {
  36.     $pos = stripos($subject, $find);
  37.     //Break subject into components.
  38.     $left = substr($subject, 0, $pos);
  39.     $to_replace = substr($subject, $pos, strlen($find));
  40.     $right = substr($subject, $pos + strlen($find));
  41.     //Set case of replacement.
  42.     $replacement = $replace_with;
  43.     $first_char = substr($to_replace, 0, 1);
  44.     if ( $first_char >= 'A' && $first_char <= 'Z') {
  45.       $replacement = ucfirst($replacement);
  46.     }
  47.     else if ( $first_char >= 'a' && $first_char <= 'z') {
  48.       $replacement = lcfirst($replacement);
  49.     }
  50.     $subject = $left . $replacement . $right;
  51.   } //End while
  52.   return $subject;
  53. }

Here's the result:

Finished!

Figure 32. Finished!

There. Now nobody would know the page was originally about pirates and ninjas.

Commit the changes. We're done!

Move along, nothing more to see

This article has introduced some of the simplest uses of GIT. You'll also want to know about branching, merging, and pushing and pulling from a remote repository.

Not now, though. Time for Borderlands.

Add a comment

Basic WYSIWYG

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <blockquote> <br> <cite> <code> <dd> <div> <dl> <dt> <em> <h2> <h3> <h4> <h5> <img> <li> <ol> <p> <pre> <span> <strong> <ul>
    Allowed Style properties: border, border-style, border-width, float, height, margin, margin-left, position, text-align, width

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Images
Images for your comment.
Files must be less than 1 MB.
Allowed file types: png gif jpg jpeg.
CAPTCHA
Prove that your are sentient. Code has letters only.
Image CAPTCHA
Enter the characters shown in the image.