syncvsvn: synchronize CVS working copy with SVN working copy

published on December 23, 2008

I keep all my Drupal sites up-to-date by updating a single Drupal core instance and one install profile. And I keep Drupal core and all modules in this install profile updated through CVS. But then a problem poses: what if a file was added to or removed from CVS? Until now, you’d have to manually svn add or svn rm the file. And in the case of some modules (e.g. Views), that’s a lot of files you’ll have to check.

The solution: syncvsvn

I’m aware that this probably isn’t the best name, but it gets the job done :).

Suppose you’ve just updated the xmlsitemap module:

>: svn status
?      xmlsitemap_user/translations
M      xmlsitemap_user/xmlsitemap_user.module
M      xmlsitemap_user/xmlsitemap_user.install
?      xmlsitemap_user/CVS/Entries.Log
M      xmlsitemap_user/CVS/Entries
?      translations/includes.pot
?      translations/docs.pot
?      translations/general.pot
!      translations/da.po
!      translations/ru.po
!      translations/es.po
!      translations/fr.po
M      translations/xmlsitemap.pot
M      translations/de.po
!      translations/nl.po
!      translations/ja.po
M      translations/CVS/Entries

Your mission: ignore any CVS/Entries.log files, add all files marked with question marks and remove all files marked with exclamation marks. This is a frustrating and time consuming task. This is where syncvsvn comes in to the rescue:

>: syncvsvn
A      xmlsitemap_user/translations
Deleted xmlsitemap_user/CVS/Entries.Log
A      translations/includes.pot
A      translations/docs.pot
A      translations/general.pot
D      translations/da.po
D      translations/ru.po
D      translations/es.po
D      translations/fr.po
D      translations/nl.po
D      translations/ja.po

The script

Now, the actual script (you can also download it below):


define('REGEXP_SVN_ADD', "/^\?.{6}(.*)(?<!CVS\/Entries\.Log)$/");
define('REGEXP_SVN_RM',  "/^\!.{6}(.*)(?<!CVS\/Entries\.Log)$/");
define('REGEXP_FILE_RM', "/^\?.{6}(.*CVS\/Entries\.Log)$/");

// Run svn status.
$output = shell_exec('svn status');
$lines = explode("\n", $output);

// Add/remove files to/from the svn repository and delete CVS/Entries.Log files.
foreach ($lines as $line) {
  if (preg_match(REGEXP_SVN_ADD, $line, $matches)) {
    print shell_exec("svn add $matches[1]");
  elseif (preg_match(REGEXP_SVN_RM, $line, $matches)) {
    print shell_exec("svn rm $matches[1]");
  elseif (preg_match(REGEXP_FILE_RM, $line, $matches)) {
    shell_exec("rm $matches[1]");
    print "Deleted $matches[1]\n";

As you can see, it decides what to do based on the output of svn status. This means that if you’ve got a file

foo that’s not added to the repository yet, it will be added as well. I.e. any temporary files will be added as well. You could prevent this by actually looking at CVS’ metadata, but this was sufficient for me.

Put this wherever you store your shell scripts and chmod +x it to make it executable. In case your PHP interpreter cannot be found, use type -a php to find out its location and update this in the first line of the script.


Rob Russell's picture

Thanks, I have almost the same problem. I never used CVS versions from because I assumed it would be a pain to get them into my svn. So I grab the tar.gz and hope no files were deleted. To move to CVS I guess the only thing I need to do is figure out the easy way to stay on released versions of modules from CVS (as opposed to just the HEAD revision).

Wim Leers's picture

Wim Leers

You could use drush. Or alternatively, if you want to be able to check out a drupal module (and update it) anywhere, add these to your ~/.bash_profile:

alias dupdate="cvs update -dP"

function drupal { if [ $# -eq "2" ] then cvs -z6 checkout -d $1 -r DRUPAL-$2 drupal else cvs -z6 checkout -d $1 drupal fi }

function drupal_module { if [ $# -eq "2" ] then cvs checkout -d $1 -r DRUPAL-$2 contributions/modules/$1 else cvs checkout -d $1 contributions/modules/$1 fi }

Now you can check out the Drupal 6 branch of Views 2 with this command: drupal_module views 6--2

Or the 2.1 tag with this command: drupal_module views 6--2-1

And update to the 2.2 tag with this command: dupdate -r DRUPAL-6--2-2

I know, it’s not the most consistent or complete system, but at least it doesn’t have any dependencies. I find it comfortable enough to use.

Matt Petrowsky's picture

Hey Wim,

Here are the commands I’ve been using in my own .bash_profile.

You can also find these in the comments on my site.

These are functions in my .bashrc file Get a new drupal module

d6mod() { MODULE="${1}"; cd $DRUPALROOT/sites/all/modules/; \ cvs -z6 co -d ./$MODULE contributions/modules/$MODULE cd $MODULE drevs $MODULE.module }

Get a new drupal theme

d6theme() { THEME="${1}"; cd $DRUPALROOT/sites/all/themes/; \ cvs -z6 co -d ./$THEME contributions/themes/$THEME cd $THEME drevs page.tpl.php }

List the revisions of the supplied file

drevs() { cvs log "${1}" | egrep 'DRUPAL-.*:' | sort }

Do a cvs update using the specified revision

dup() { cvs up -dP -r "${1}" }

Wim Leers's picture

Wim Leers

Thanks for the feedback Matt :) I had already referenced your article in the comments on this page, by the way :)

Your drevs command looks very useful, thanks!

Since you seem to be very much into these kinds of handy shortcuts, you may also want to look at two more blog posts.

Bevan's picture

I’ve been contemplating writing this for some time. Thanks for taking the time to write and pubishing this! :)

Wim Leers's picture

Wim Leers

Thanks for sharing Milan! :)

I’m sticking with my script on my local machine because I find the code far more readable. But yours is probably better to get going faster :)

jjussi's picture



define(‘REGEXP_SVN_ADD’, “/^\?.{7}(.)(?<!CVS\/Entries.Log)$/”); define(‘REGEXP_SVN_RM’, “/^!.{7}(.)(?<!CVS\/Entries.Log)$/”); define(‘REGEXP_FILE_RM’, “/^\?.{7}(.*CVS\/Entries.Log)$/”);

// Run svn status. $output = shell_exec(‘svn status’); $lines = explode(“\n”, $output);

// Add/remove files to/from the svn repository and delete CVS/Entries.Log files. foreach ($lines as $line) { if (preg_match(REGEXP_SVN_ADD, $line, $matches)) { print shell_exec(“svn add "$matches[1]"”); } elseif (preg_match(REGEXP_SVN_RM, $line, $matches)) { print shell_exec(“svn rm "$matches[1]"”); } elseif (preg_match(REGEXP_FILE_RM, $line, $matches)) { shell_exec(“rm "$matches[1]"”); print “Deleted "$matches[1]"\n”; } }