How do you update Drupal?

Sitting on #drupal-support on IRC, you see people drop by with update problems from time to time. With Drupal 6.21, 6.22, 7.1 and 7.2 released earlier in the week, today was such a day.

The person in question had attempted a Drupal core update via drush, but ran it in the wrong directory. Drupal had picked up this incorrect location for its core modules, so when the drupal-6.22 directory got deleted, it was unable to load any of the core modules. Oops.

This led me to wondering whether the way I manage my Drupal updates is odd and whether sharing it would be useful. Being an open source person, I am of the opinion that sharing is virtual always useful (except when it comes to cheese) so I'll document the way I manage updates here.

There's a few ways to update Drupal core to a new minor version. I'll not discuss major version upgrades, for which it's best to follow the procedure set out in the UPGRADE.txt file that comes with Drupal anyway.

Unpack tarball or zip

I imagine a lot of people follow that UPGRADE.txt file regardless, so an update means down-time for them whilst they move directories out of the way, unpack the tarball or zip and then copy the files directory back. It's also tedious. if you have a large files directory, you'll spend ages waiting for it to copy. On the plus side, you know you have a backup.

The even more tedious version of this involves a server where all you have is FTP access, so you need to unpack the archive locally and then upload it file by file.

Update via git fetch/rebase1

If you've installed Drupal from git, you can simply fetch the new tag from the Drupal git repository and then rebase your local Drupal version. Certainly anyone who hates kittens is already doing this, as it allows you to  hack core and keep your changes separate and version controlled.

It's also nice and efficient, as git fetch would download only the changes to the code, not the full Drupal source. Admittedly, that's only 1MB (or 2MB for Drupal 7) which isn't that much of a deal these days.

1 ... because we don't git merge, do we? :-)

Make a patch and apply it

The way I handle Drupal updates is by making a patch file that contains the difference between the versions. It's effectively a combination of the tarball and git ways. I like it because by applying a patch I can see if there are any conflicting changes (changing .htaccess is hacking core too!) and manage them sensibly. In addition, I can apply such a patch to any staging sites I manage via git as well as stand-alone production sites.

Make a patch

Making a patch is trivially easy. First I download a copy of the original and new Drupal versions via drush:

drush dl drupal-6.20
drush dl drupal-6.22

Then I create a patch using the diff utility, where the urN options control the patch file format. By default diff outputs to the screen, so I redirect this output to my patch file instead:

diff -urN drupal-6.20 drupal-6.22 > drupal-620-to-622.patch

If you have a look at the patch file, you'll see that the contents list what gets removed (-) and what gets added (+) at which line numbers. Pretty straighforward.

diff -urN drupal-6.20/CHANGELOG.txt drupal-6.22/CHANGELOG.txt
--- drupal-6.20/CHANGELOG.txt   2010-12-16 08:11:22.000000000 +1100
+++ drupal-6.22/CHANGELOG.txt   2011-05-26 06:43:55.000000000 +1000
@@ -1,4 +1,14 @@
-// $Id: CHANGELOG.txt,v 1.253.2.43 2010/12/15 21:11:22 goba Exp $
+
+Drupal 6.22, 2011-05-25
+----------------------
+- Made Drupal 6 work better with IIS and Internet Explorer.
+- Fixed .po file imports to work better with custom textgroups.

Apply the patch

Now all that remains is to apply the patch to any Drupal instances that need updating, using the patch utility. It reads data from standard input, so I use a redirect2 again:

cd /path/to/drupal/root
patch -p1 --dry-run < /path/to/drupal-620-to-622.patch

When you run that, patch will tell you which files it's patching and whether any errors have occurred. The -p1 option tells patch to remove the top level part of the file path from each file that's listed in the patch. In this example, it would strip off "drupal-6.20/" and "drupal-6.22", leaving it to patch the CHANGELOG.txt file in the current directory, which is what we want.

The --dry-run part means patch is not actually modifying the Drupal files yes, but only telling you what it would do. If there are any errors, you can find out what they are and then decide whether or not to apply the patch. To apply the patch for real, use:

patch -p1 < /path/to/drupal-6.20-to-6.22.patch

If there were errors that you decided to ignore during the dry-run, you'll find that patch has created created two copies of all the files it failed to patch successfully. One with the suffix .orig, which is the original unpatched copy of the file and one with the suffix .rej, which contains a listing of the parts of the patch that failed to apply to the file.

Fixing those up is effectively identical to resolving a failed git rebase or merge.

And there you have it, my reasonably fast, reasonably fool-proof and - above all - reasonably lazy way of updating a Drupal installation.

2Because I'm an opponent of the needless use of cat, as in cat drupal-6.20-to-6.22.patch | patch -p1

Comments

I too like the incremental patch approach better, and I've found this site which makes life a little easier:
http://fuerstnet.de/en/drupal-upgrade-easier

It would be great if drupal.org itself provided incremental patches between stable releases.

BTW the point about using "cat" and pipes is that when you recall the command from the shell history you are already at the _end_of_the_line_ and it is easier to edit the patch command options (adding or removing --dry-run for example), so yes, it is less efficient, but more usable for some. :)

Bye,
Antonio

I take a very lazy approach that is quite foolproof. Instead of making a patch I go the route of finding all changed files and replacing them, not replacing any files that were untouched. It leaves the /sites folder undisturbed among other things too.

1. Navigate to Drupal root
2. wget drupal-x.xx.tar.gz
3. tar -xzvf drupal-x.xx.tar.gz
4. cd drupal-x.xx
5. \cp -Rvupf * ../

The cp command with those flags does all that was said above. Flawless core upgrade every time.

All of the core files and directories are symlinked to a single version (on machines with mupltiple vhosts),

cron.php -> /var/www/vhosts/drupal/drupal-6.x/cron.php
.htaccess
includes -> /var/www/vhosts/drupal/drupal-6.x/includes
index.php -> /var/www/vhosts/drupal/drupal-6.x/index.php
install.php -> /var/www/vhosts/drupal/drupal-6.x/install.php
misc -> /var/www/vhosts/drupal/drupal-6.x/misc
modules -> /var/www/vhosts/drupal/drupal-6.x/modules
profiles -> /var/www/vhosts/drupal/drupal-6.x/profiles
robots.txt
scripts -> /var/www/vhosts/drupal/drupal-6.x/scripts
sites
themes -> /var/www/vhosts/drupal/drupal-6.x/themes
update.php -> /var/www/vhosts/drupal/drupal-6.x/update.php
xmlrpc.php -> /var/www/vhosts/drupal/drupal-6.x/xmlrpc.php

e.g. drupal-6.x which itself a symlink to the current version. So to update all sites I can run:

drush dl drupal-6.22
rm drupal-6.x; ln -s drupal-6.22 drupal-6.x;

I run the rm and ln on a single line to ensure that there is no gap between removing the old link and recreating the new one.

This is smart. Using patches to upgrade core. Drupal Project pages should have patches for upgrades in between versions. Although it is quick & easy to build them as you have outlined, if they just provide them by default, that would be great. This would only be for full releases and not dev. version

I used the drush pm-updatecode, update went fine according to the output. However when i visited my local drupal install and saw admin/report still shows i had drupal 7.0 and requires to be upgraded to drupal 7.2

I think this could be down to me using windows - let me try the update by patching.

downloaded patch for windows, hopefully will work - fingers crossed!

I used the drush pm-updatecode, update went fine according to the output. However when i visited my local drupal install and saw admin/report still shows i had drupal 7.0 and requires to be upgraded to drupal 7.2
I think this could be down to me using windows - let me try the update by patching.
downloaded patch for windows, hopefully will work - fingers crossed!
_________
grady speer from machine café nespresso

Can't beat drush, like a few others posted. I updated half a dozen sites last night, both D6 and D7 with the script below. Yeah, I know, csh isn't cool for scripting, so sue me!

#!/bin/csh -f
# update any of my sites, site subdir name is first arg to this script

if (! -e ~/www/$1) then
echo "www/$1 does not exist"
exit
endif

cd ~/www/$1
echo "Backing up files and DB"
tar cfz ~/backup/$1/files-`date +%m%d%Y`.tgz .
drush sql-dump > ~/backup/$1/db-`date +%m%d%Y`.sql
echo
echo "Updating Drupal"
drush up
echo
echo "All done. Please test the site immediately!"

I used drush on 6 d7 sites and 36 d6 sites across 7 multisite installs for my day.
Here is an example of my commands on an install with 2 sites, I take the sites offline, update the core against the default install, run update.php on a site then run any module updates, manually check site, then finally bring back out of maintenance mode:

sudo cp ./.htaccess ~/
sudrush -l site1.com vset --yes site_offline 1
sudrush -l site2.com vset --yes site_offline 1
sudrush up
sudrush -l site1.com updb
sudrush -l site1.com up
sudrush -l site1.com vset --yes site_offline 0
sudrush -l site2.com updb
sudrush -l site2.com up
sudrush -l site2.com vset --yes site_offline 0
sudo mv ~/.htaccess ./

D7 is almost the same except for putting into maintenance mode:

sudrush -l d7site.com vset --yes maintenance_mode 0
sudrush -l d7site.com cache-clear all

Though I don't use drush to update the core code directly (too much magic for me) I do use it to process updates and clear cache on multisite installs. I've scripted it a little bit further than you, though. I use a variation of this script to run Drupal crons as well, actually.

Rather than having to specify each site, it'll just process all sites defined via their own directory under /sites/ (but not default).

#!/bin/bash
VERSION=6

if [ $# -eq 1 ]; then
  VERSION="${1}"
else
  echo "Usage: $(basename $0) version"
  exit 1
fi

SITES_ROOT="/srv/drupal-${VERSION}/root"

if [ ! -d "${SITES_ROOT}" ]; then
  echo "No Drupal \"${VERSION}\" was found."
  exit 2
fi

echo -n "Run updates on all Drupal ${VERSION}.x sites (Y/n) "
read ans
case $ans in
  y|Y|'')
    ;;
  *)
    echo Aborted
    exit 1
    ;;
esac

case ${VERSION} in
  7)
    OFFLINE=maintenance_mode
    ;;
  *)
    OFFLINE=site_offline
    ;;
esac

SITES_PATH="${SITES_ROOT}/sites"
PWD=$(pwd)
LOGS=/var/log/drush
DATE=$(date +%Y%m%d%H%M)

pushd "${SITES_PATH}"

echo "Updating Drupal ${VERSION}.x installations."

for site in `find ./ -maxdepth 1 -type d | cut -d/ -f2 | egrep -v '(.bzr|.svn|.git|all|default|^$)' | sort`
do
  if [ -f "${site}/settings.php" ]; then
    LOGFILE="${LOGS}/${DATE}-${site}.log"
    echo ${site} >${LOGFILE}
    echo -n "Updating ${site}: "

    echo -n "set offline, "
    drush -r ${SITES_ROOT} -l ${site} vset --always-set --yes ${OFFLINE} 1 >>${LOGFILE} 2>&1

    echo -n "run updates "
    drush -r ${SITES_ROOT} -l ${site} updatedb --yes >>${LOGFILE} 2>&1
    ret=$?
    if [ $ret -gt 0 ]; then
        echo -n "(error ${ret}), "
    else
        echo -n "(ok), "
    fi

    echo -n "clear cache, "
    drush -r ${SITES_ROOT} -l ${site} cc all >>${LOGFILE} 2>&1

    echo -n "set online,"
    drush -r ${SITES_ROOT} -l ${site} vset --always-set --yes ${OFFLINE} 0 >>${LOGFILE} 2>&1

    echo " Done."
    if [ $ret -gt 0 ]; then
        echo -e "\tPlease see ${LOGFILE} for error details."
    fi
  fi
done
# Lets go back to where we started
popd

Drush aliases are really key for this sort of thing.

You can simply specify: drush @sites cron --root=/path/to/drupal to run cron on all mulitsites in the specified root.

You can also set groups of sites like @dev or @staging if you want to define some sites, but not all.

Yes and no - they're handy, but it would mean I'd have to remember to update my drush aliases file whenever I add a new site. The script just finds them without me having to edit a config file.

There are a couple of aliases that exist with Drush even without modifying the aliases file. @sites and @self are two that I use. @sites will do all sites with no additional configuration even after you add new sites.

You can use git to make the process of creating the patch even easier. Since the entire history of Drupal is kept in git you won't have to download both the current and previous versions of Drupal to be able to make the patch. It is also faster to just update the git repository than downloading full new copies from drupal.org.

  1. If you already have a clone of the Drupal core git repository, update it so you have the latest version:
    git fetch
    If you don't have one yet, fetch it with Drush (or using the git instructions from drupal.org):
    drush dl drupal --package-handler=git_drupalorg
  2. Checkout the latest version (currently 6.22):
    git checkout 6.22
  3. And finally create the patch from the previous version (currently 6.20):
    git diff 6.20 > drupal-620-to-622.patch

You can apply the patch with:
patch -p1 < drupal-620-to-622.patch

I had a few sites in which the old CVS tags were preventing successful application of the patch.

The problem was that the files were starting with a full CVS id string, for example:
// $Id: jquery.js,v 1.12.2.3 2008/06/25 09:38:39 goba Exp $

and the patch was expecting:
// $Id$

This can be fixed with regex. Run this command in the root folder:
perl -pi -e 's/ \$Id: .+Exp \$/ \$Id\$/g' `find . -type f`

I get a bunch of errors? Help

macbook:dwc mhardy11$ patch -p1 --dry-run < /Users/mhardy11/drupal-620-to-622.patch
patching file .htaccess
Reversed (or previously applied) patch detected! Assume -R? [n] n
Apply anyway? [n] y
Hunk #1 FAILED at 113.
1 out of 1 hunk FAILED -- saving rejects to file .htaccess.rej
patching file CHANGELOG.txt
patching file COPYRIGHT.txt
patching file modules/aggregator/aggregator.info
Hunk #1 FAILED at 1.
1 out of 1 hunk FAILED -- saving rejects to file modules/aggregator/aggregator.info.rej
patching file modules/aggregator/aggregator.install
patching file sites/all/README.txt
Reversed (or previously applied) patch detected! Assume -R? [n] n
Apply anyway? [n] n
Skipping patch.
1 out of 1 hunk ignored -- saving rejects to file sites/all/README.txt.rej
patching file sites/default/default.settings.php
patch: **** Can't rename file /var/folders/x6/x6h0-t1-GMuYoCzerl7ycE+++TI/-Tmp-//poVpxLek to sites/default/default.settings.php : Permission denied

Please help. Thanks

There are a few reasons a path might fail. If the version you're patching from does match or if an affected file was modified from the original, you'll see such errors.

In this case, because some parts of the patch succeed and some fail, my guess is that the Drupal you're upgrading is not 6.20 but maybe 6.21 or might be a 6.20 that has had individual fixes applied to it.

The final error, on default.settings.php is due to directory permissions. The user that is running the patch command doesn't have permission to create or write to files in sites/default.

I have installed drupal 6.21 but I can`t install any plugin. Please help me how to setup a plugin on drupl site.

Thanks for posting this, I just had the drush up voodoo magic fail on me again. Seemed like a good time to give your method a whirl.

The advantage of patching is that I get a better understanding of what I'm doing. Another step toward enlightenment...

When you have downloaded a regular (ie non-git) copy of Drupal core and update it with a patch generated with "git diff" the update status screen will still report the wrong (previous) version. This is caused by the packaging information that is added to the .info files. There is a discrepancy between the packaging information and the actual Drupal version and this confuses the update module.

If you are affected by this you can remove all packaging information from all .info files with this oneliner, to be run from the drupal root folder. Mind that this will also happily remove all packaging information from your contrib and custom themes and modules ;)


grep -lr "; Information added by drupal.org packaging script" * --null | xargs -0 sed -i '/; Information added by drupal.org packaging script/,$d'

I also typically use drush up.

And I believe the canonical phrase is 'useless use of cat'.

What's Going down i'm new to this, I stumbled upon this I have discovered It positively useful and it has aided me out loads. I am hoping to contribute & assist different customers like its aided me. Good job.