Symfony World blog is not maintained anymore. Check new sys.exit() programming blog.

force to log message SVN python

Scene from "It Happened One Night" by Frank Capra (1934)

Another blog post about Subversion. Few days ago someone asked me if I know how to block commiting a subversion revision that has no log message defined. I didn't, but I thought it's a good idea, because it could help to track bugs, changes, etc. There's no such configuration, but it can be achieved using SVN hooks.

tried to do this in PHP...

There are many resources in the web giving examples to do this using a windows batch file - but I'm a linux user. At first, I wanted to do this in PHP. Although my final solution is not PHP, maybe someone will find following code snippets useful.

So I started searching through many pages on the web to find PHP shell scripts that could block committing a revision with no log message. The first step is, of course, to create an executable pre-commit file in the hooks directory of the repository. Then make the shell use the PHP parser to execute our script. Next, define path to svnlook binary and execute it:

#!/usr/local/bin/php -q
<?php
 
$SVNLOOK = '/usr/bin/svnlook';
exec("$SVNLOOK log -t {$argv[2]} {$argv[1]}", $output, $result);
We have the output and the result of the command available. So we may just check if the message (which revision a user is just trying to commit) is empty (I guess there is no other way than to use svnlook). If the log message is empty, the revision shall be stopped - and this happens, when exit status code is different than 0 (of course, a message explaining the cause is appreciated):
if (empty($result))
{
  fwrite(STDERR, "Commit blocked, need to pass the log message.");
  exit(1);
}
else
  exit(0);
This all should work, but 99,9% of all servers will have the exec function disabled by default (in both CLI and web server PHP configuration; along with other system functions):
disable_functions = exec,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,proc_open,dl,popen,show_source
Even if you manage to remove those functions from the configuration, you lower the server security, which may risk dangerous script injection. So I forgot about using PHP...

...and finally moved to python

Finally, I managed to succeed using python:

#!/usr/bin/python
 
import sys, os, string
 
SVNLOOK='/usr/bin/svnlook'
 
def main(repos, txn):
  log_cmd = '%s log -t "%s" "%s"' % (SVNLOOK, txn, repos)
  log_msg = os.popen(log_cmd, 'r').readline().rstrip('n')
  if len(log_msg) < 10:
    sys.stderr.write ("\n> Revision comment must be defined\n")
    sys.exit(1)
  else:
    sys.exit(0)
 
if __name__ == '__main__':
  if len(sys.argv) < 3:
    sys.stderr.write("\n> Usage: %s REPOS TXN\n" % (sys.argv[0]))
  else:
    main(sys.argv[1], sys.argv[2])

svnsync tutorial

Scene from magnificent "One Flew Over the Cuckoo's Nest" By Miloš Forman

I was forced to use this tool when I wanted to move my old project repository into a new server (until then, its SVN repository was hosted on a free SVN hosting). The hosting provider forced me to pay for the svnadmin dump, so I searched for an alternative solution and svnsync did the job.

In comparison to svnadmin dump/load, svnsync is migrating subversion repository with no svnadmin access. The migration can be done when you have a working copy (checkout) of the source repository and a clean (svnadmin create) version of the target repository. The repository synchronization is just reading all revisions one by one and copying them into the target repository, unchanged.

First, take a look at this article. Here you can find that a hook needs to be created in the target repository: hooks/pre-revprop-change (removing .tmpl and granting executable permissions for the script). What is not mentioned in the article above is that you should probably put

#!/bin/sh 
exit 0;
as the hooks/pre-revprop-change script content, to avoid the error, as written in another article.

Now, initialize the synchronization (example call below):

svnsync init [target repository] [source repository]
svnsync init file:///home/user/svn/test-repo svn://free-hosting/that-forced-me-to-pay/for-the-svn-dump

and copy all revisions, while being in the working copy of the source repository (example call below again):

svnsync --non-interactive sync [target repository]
svnsync --non-interactive sync file:///home/user/svn/test-repo

Be patient and wait until it's finished. Afterwards, remove the useless hooks/pre-revprop-change script - and you're done!

extending doctrine admin module: filtered sum

Scene from "Breakfast at Tiffany's" by Blake Edwards (1961)

Another post for symfony beginners. I'll show how to display a sum (or any other function) of all elements filtered in an admin module. All elements - meaning the ones displayed on the current page (list pagination) and all the rest which is not currently visible. This is going to be really easy.

action

Let's start with calculations. We need to get the sum of all filtered elements and pass it to the View. As this article is not about MVC design pattern, I'll just override executeIndex action and put the calculations code inside (to make it as easy as possible, though calculations should be done in model, not controller).

class xxxActions extends autoXxxActions
{
  public function executeIndex(sfWebRequest $request)
  {
    parent::executeIndex($request);
    $query = $this->buildQuery()->copy();
    $root_alias = $query->getRootAlias();
    $total_data = $query
      ->limit(0)
      ->select("SUM({$root_alias}.cash_total) AS sum")
      ->fetchArray();
    $this->total_count = $total_data[0]['sum'];
  }
}
As you can see, we've got a Xxx model which holds the cash_total: decimal column, representing a sum of money. The $this->buildQuery()->clone() part does all the magic - we have the query with all filters set by the user in the interface. We will only tell doctrine to calculate the sum of all filtered elements for us ($total_count variable will be available in the indexSuccess.php template). The ->limit(0) clears the SQL limit clause, of course.

templates

This part is boring, actually. We need to override two more files: fetch them from cache and put them in the module/template directory. These files are: indexSuccess.php in which the line

<?php include_partial('event/list', array('pager' => $pager, 'sort' => $sort, 'helper' => $helper)) ?>
should be replaced with:
<?php include_partial('event/list', array('pager' => $pager, 'sort' => $sort, 'helper' => $helper, 'total_count' => $total_count)) ?>
and the _list.php partial which should have few lines added:
<tfoot>
  <tr><!-- added code starts here -->
    <th colspan="6">
      w sumie: <?php echo Tools::priceFormat($total_count, true) ?>
    </th>
  </tr><!-- added code ends here -->
  <tr>
    <th colspan="6">
       <?php if ($pager->haveToPaginate()): ?>
         <?php include_partial('event/pagination', array('pager' => $pager)) ?>
       <?php endif; ?>
As you can see, it's trivial, yet useful.

The sum of all filtered elements is visible in the footer of the doctrine admin module list table, as you can see below, but you can put it anywhere you want (as long as it's in the index action templates):

other functions

Of course, you can use other aggregate functions, such as average, minumum or maximum element - just take a look at the MySQL documentation. You may also create your own functions.