chacadwa.com

Technical blog and writings by Micah Webner.

How I manage Drupal with Git

Image depicting merged git branches

Ever since I started using Git version control to manage projects, I've struggled to find the best way to use it for managing complete Drupal projects. From day one, it has worked out well enough for me for managing custom modules and themes, HTML or Classic ASP projects (yes, I still have to deal with one of those) and even Linux server /etc directories, but I never quite new what to do with Drupal deployments. I use drush to manage Drupal code, and of course I don't use proscribed methods for doing that, either. (For example, I never use drush up because I don't really like the way it works.) So what was I to do? I finally landed upon a solution, and while this may not be the best (or recommended) method, it does work for me.

Clone Drupal core and get started.

We start by cloning Drupal core from source. The -n option tells git to not perform a checkout. We don't want to open a development branch, we'll work from the latest stable release tag (7.28 at the time of this writing) instead.

git clone -n http://git.drupal.org/project/drupal.git directoryname
cd directoryname
git checkout 7.28

This leaves us in "detached HEAD" state, which means that our current commit is not on a known branch. We can resolve this using checkout -b to create a new branch from the current commit. I name my branches based on the Drupal core branch name combined with the project version, so:

git checkout -b 7.x-1.x

This creates a new 7.x-1.x branch which matches the 7.28 tag, as shown by looking at the top part of the commit log:

Drupal core commit log

Now, we'll want to store our repository somewhere (I use Bitbucket) but we also want to keep our remote at drupal.org so we can fetch updates to Drupal core, so we'll rename that to drupalorg first, then create a new project on our remote provider and add it as the new origin.

git remote rename origin drupalorg
git remote add origin git@bitbucket.org:micahw156/PROJECTNAME.git
git push -u origin 7.x-1.x
git push origin 7.28

I would not push --tags because it will push every tag for every version of Drupal core ever. I only push the specific tags of Drupal versions used by each repo.

At this point, we can create a new settings.php file and install Drupal normally, but we'll also want to add some contrib modules and possibly some custom code.

Adding contributed code

Adding themes and modules from contrib to this repo is actually pretty straightforward. I begin by adding subdirectories under sites/all/modules for contrib, custom, and (if needed) features. Drush will use the contrib directory automatically if it exists.

mkdir sites/all/modules/contrib sites/all/modules/custom

Now for each project I need, I'll download it and commit it to the repository. A lot of times I do this all in one commit, but it's not too hard to split them into their own commits, either, and this makes a more descriptive commit log.

drush dl advanced_help backup_migrate ctools globalredirect google_analytics pathauto quick_cache_cleaner redirect token transliteration views
for EACH in advanced_help backup_migrate ctools globalredirect google_analytics pathauto quick_cache_cleaner redirect token transliteration views; do git add -A sites/all/modules/contrib/${EACH}; git commit -m "by micahw156: Added ${EACH} from contrib."; done

Why not use submodule here?

Experienced git users are probably asking why we don't use git submodule here. The answer is simple: contrib repositories on drupal.org don't have the metadata necessary for checking updates. The available updates report will be able to tell if core is out of date, but we don't get the same info for modules when we install them using git. Downloading modules with drush gives us full release information.

Adding custom code

If I want to make a custom module or theme for a specific project, I'll just add the code for it to the site repository. However, at HFC we use several custom modules on every project. Add these to the tree using git submodule.

git submodule add git@bitbucket.org:hfccwebdev/theme_hfccbase.git sites/all/themes/hfccbase
git commit -m 'by micahw156: Added hfccbase theme as submodule.'
git submodule add git@bitbucket.org:hfccwebdev/hfcc_global.git sites/all/modules/custom/hfcc_global
git commit -m 'by micahw156: Added hfcc_global as submodule.'
git submodule init
git submodule status

Now when we look at our commit log, we see our updates stacked on top of the core commit history.

Custom Drupal commit log

So far, we haven't done anything really interesting, but the update process makes it all worthwhile.

Updating Drupal core

Using this method, updating Drupal core just got really easy. (But I still wouldn't recommend doing this on a production server. Be safe, as usual. Also, this is for applying Drupal 6 or 7 point releases, not for complete base version upgrades!) All we have to do is fetch the latest source and merge the release tag we want.

Let's say, for example, that Drupal 7.29 was just released. We'll back up our site database, then simply merge the changes to core into our custom branch.

Always start by making sure the tree is clean!

git status

If everything looks clean, we fetch and merge core updates.

git fetch drupalorg
git merge 7.29 -m 'Merged changes from 7.29 core release.'

If all goes well, new commits from core will appear in our commit log, and our merge will be the most recent commit. If things do not go well, we simply resolve merge conflicts and then cap things off with a new commit, using the same commit message we wanted for the merge.

Updating contrib code

For contrib updates, drush does most of the work, and we just commit the changes. Let's say we have to apply a security fix to views. Again, use git status to check for a clean tree before starting!

We are going to use drush dl here. The drush up or upd commands create their own backups of old versions, and we don't need those, since we're doing our own version control. Also, drush dl will remove files that are no longer needed, so we'll use git add -A to make sure we track that correctly. Lastly, I like to note whether an update was for security or maintenance releases.

drush dl -y views
git add -A sites/all/modules/contrib/views
git commit -m 'by micahw156: Updated views from contrib. (security)'

Updating custom code

For completeness, I'll talk about updating custom code here. First commit and push the changes in the submodule directory. Then, go back to the main repository and commit changes to the submodule.

cd sites/all/modules/custom/mymodulename
git add -A
git commit -m 'by micahw156: Fixed some stupid bug.'
git push
cd -
git add sites/all/modules/custom/modulename
git commit -m 'by micahw156: Updated mymodulename submodule for stupid bug.'

Important, do not put a trailing / at the end of the git add line, or git will not handle the submodule correctly. See the tutorial linked above for more details.

Merging an existing tree with Drupal core

Ok, this is great, but what if I already have a repo for my Drupal site and now I want to connect it to Drupal core. I played around with this and figured out how to merge two repositories that have no common commits. Warning, this is probably a bad idea. Make sure you have good backups before you try this!

So let's use a real-world example for this. I had a Drupal 6.29 site that was in its own repo from before I figured out how to do all of this. I have all of the history of my changes and core updates, etc, and I want to keep all of that. For sake of argument, let's say my site was in the 6.x-1.x branch of my repo, because at least I wasn't just using master for this.

So what happens? Well first, we back everything up. Second, we don't try this in production! Third, we make a new branch. (After all, this is basically now the second version of our site, right?)

git status
git checkout -b 6.x-2.x

The other thing we need to do is remove any version tags from our local and remote repos. Especially any that match core release numbers.

git tag -d 6.29
git tag -d 6.28
git push origin :6.29
git push origin :6.28

Now we can add the drupalorg remote and fetch core history

git remote add drupalorg http://git.drupal.org/project/drupal.git
git fetch drupalorg

Now we're ready to merge core history with our history. Spoiler alert! It will not go flawlessly!

git merge 6.29 -m 'Merged core 6.29 history into local repository.'
git status

Doing that git status check will show us all the files that failed to merge. At a minumum, all of core's .info files won't go. That's okay, we know we want to start using the ones from Drupal history, so we can use a quick shell command to just check all of those out.

find modules themes -name '*.info'|while read EACH; do git checkout 6.29 ${EACH}; done

Do another git status check and resolve any remaining conflicts, then commit your changes.

git commit -m 'Merged core 6.29 history into local repository.'

When I did this the other day, I also needed to update core to 6.31 to apply some new security updates. We do this the way I described above.

git merge 6.31 -m 'Merged changes from 6.31 core release.'

I also applied security updates for a few modules. When I was done, the combined log shows core release information meshed in with my own manual updates of core and other modules.

Merged Drupal commit log

In the future I can use the update methods shown above to maintain this site, too.

Conclusion

So again, this might not be the cleanest way to manage Drupal with git, but so far it's working out quite well for me. In one case, I actually merged about five different repositories that had no common commits, including Drupal core, to greatly simplify code management for a project. When trying stuff like this in git, there's really only one thing to remember. If you copy a git repository to a new directory, it's just another directory on your filesystem, and you can't actually hurt anything playing around with it as long as you don't push anything broken to a remote. Git is only useful if your workflow doesn't bog you down, so find a process that works for you and doesn't break things and run with it.

Topics: