cSvn-UI for SVN Repositories

cGit-UI – is a web interface for Subversion (SVN) Repositories. cSvn CGI script is writen in C and therefore it's fast enough

15 Commits   0 Branches   1 Tag
     5         kx 
     5         kx #ifdef HAVE_CONFIG_H
     5         kx #include <config.h>
     5         kx #endif
     5         kx 
     5         kx #include <stdlib.h>
     5         kx #include <stdio.h>
     5         kx #include <sys/sysinfo.h>
     5         kx #include <sys/types.h>
     5         kx #ifdef HAVE_INTTYPES_H
     5         kx #include <inttypes.h>
     5         kx #else
     5         kx #include <stdint.h>
     5         kx #endif
     5         kx #include <stddef.h>   /* offsetof(3) */
     5         kx #include <dirent.h>
     5         kx #include <sys/stat.h> /* chmod(2)    */
     5         kx #include <sys/file.h>
     5         kx #include <sys/mman.h>
     5         kx #include <fcntl.h>
     5         kx #include <limits.h>
     5         kx #include <string.h>   /* strdup(3)   */
     5         kx #include <libgen.h>   /* basename(3) */
     5         kx #include <ctype.h>    /* tolower(3)  */
     5         kx #include <errno.h>
     5         kx #include <time.h>
     5         kx #include <sys/time.h>
     5         kx #include <pwd.h>
     5         kx #include <grp.h>
     5         kx #include <stdarg.h>
     5         kx #include <locale.h>
     5         kx #include <unistd.h>
     5         kx 
     5         kx #include <libxml/parser.h>
     5         kx #include <libxml/tree.h>
     5         kx 
     5         kx #include <nls.h>
     5         kx 
     5         kx #include <defs.h>
     5         kx 
     5         kx #include <fatal.h>
     5         kx #include <http.h>
     5         kx #include <html.h>
     5         kx 
     5         kx #include <cmpvers.h>
     5         kx #include <dlist.h>
     5         kx #include <strbuf.h>
     5         kx #include <repolist.h>
     5         kx #include <wrapper.h>
     5         kx #include <system.h>
     5         kx #include <date.h>
     5         kx 
     5         kx #include <ctx.h>
     5         kx #include <ui-shared.h>
     5         kx 
     5         kx 
     5         kx /********************************
     5         kx   Sorted dlist functions:
     5         kx  */
     5         kx struct tree_line {
     5         kx   char *name;
     5         kx   char *revision;
     5         kx   char *date;
     5         kx   char *size;
     5         kx };
     5         kx 
     5         kx static struct dlist *directories = NULL;
     5         kx static struct dlist *files       = NULL;
     5         kx 
     5         kx static struct tree_line *tree_line_alloc( const char *name, const char *revision, const char *date, const char *size  )
     5         kx {
     5         kx   struct tree_line *line = NULL;
     5         kx   line = (struct tree_line *)xmalloc( sizeof(struct tree_line) );
     5         kx 
     5         kx   if( name )     { line->name     = xstrdup( name );     }
     5         kx   if( revision ) { line->revision = xstrdup( revision ); }
     5         kx   if( date )     { line->date     = xstrdup( date );     }
     5         kx   if( size )     { line->size     = xstrdup( size );     }
     5         kx 
     5         kx   return line;
     5         kx }
     5         kx 
     5         kx static void __line_free( void *data, void *user_data )
     5         kx {
     5         kx   struct tree_line *line = (struct tree_line *)data;
     5         kx   if( line )
     5         kx   {
     5         kx     if( line->name )     { free( line->name );     line->name     = NULL; }
     5         kx     if( line->revision ) { free( line->revision ); line->revision = NULL; }
     5         kx     if( line->date )     { free( line->date );     line->date     = NULL; }
     5         kx     if( line->size )     { free( line->size );     line->size     = NULL; }
     5         kx 
     5         kx     free( line );
     5         kx   }
     5         kx }
     5         kx 
     5         kx static struct dlist *tree_lines_free( struct dlist *lines )
     5         kx {
     5         kx   if( lines )
     5         kx   {
     5         kx     dlist_free( lines, __line_free );
     5         kx     lines = NULL;
     5         kx   }
     5         kx   return lines;
     5         kx }
     5         kx 
     5         kx static int __cmp_tree_lines_bytag( const void *a, const void *b )
     5         kx {
     5         kx   char *v1 = NULL, *v2 = NULL;
     5         kx   int   ret = -1;
     5         kx 
     5         kx   struct tree_line *line1 = (struct tree_line *)a;
     5         kx   struct tree_line *line2 = (struct tree_line *)b;
     5         kx 
     5         kx   if( line1->name && line2->name )
     5         kx   {
     5         kx     v1 = line1->name;
     5         kx     v2 = line2->name;
     5         kx     while( *v1 && !isdigit( *v1 ) ) ++v1;
     5         kx     while( *v2 && !isdigit( *v2 ) ) ++v2;
     5         kx     ret = strcmp( line1->name, line2->name );
     5         kx   }
     5         kx   else
     5         kx     return ret;
     5         kx 
     5         kx   if( !*v1 || !*v2 )
     5         kx   {
     5         kx     return ret;
     5         kx   }
     5         kx 
     5         kx   /******************************************
     5         kx     sort reversive to show newest tag first:
     5         kx    */
     5         kx   return -cmp_version( (const char *)v1, (const char *)v2 );
     5         kx }
     5         kx 
     5         kx static struct dlist *tree_lines_sort_bytag( struct dlist *lines )
     5         kx {
     5         kx   if( lines ) { lines = dlist_sort( lines, __cmp_tree_lines_bytag ); }
     5         kx   return lines;
     5         kx }
     5         kx /*
     5         kx   End of sorted dlist functions.
     5         kx  ********************************/
     5         kx 
     5         kx 
     5         kx static const char *is_file_executable( const char *relative_path, const char *name, int revision )
     5         kx {
     5         kx   const char *co_prefix = ctx.repo.checkout_ro_prefix;
     5         kx   const char *repo_name = ctx.repo.name;
     5         kx   const char *repo_root = ctx.repo.repo_root;
     5         kx   const char *ret  = "";
     5         kx   char *path = NULL;
     5         kx 
     5         kx   if( !name ) return ret;
     5         kx 
     5         kx   if( relative_path && *relative_path )
     5         kx   {
     5         kx     path = (char *)xmalloc( strlen( relative_path ) + 2 );
     5         kx     path[0] = '/';
     5         kx     sprintf( (char *)&path[1], relative_path );
     5         kx   }
     5         kx   else
     5         kx   {
     5         kx     path = (char *)xmalloc( 1 );
     5         kx     path[0] = '\0';
     5         kx   }
     5         kx 
     5         kx   if( co_prefix )
     5         kx   {
     5         kx     char repo_path[PATH_MAX] = { 0 };
     5         kx     char cmd[PATH_MAX];
     5         kx     struct strbuf buf = STRBUF_INIT;
     5         kx     pid_t p = (pid_t) -1;
     5         kx     int   rc;
     5         kx 
     5         kx     if( repo_root && *repo_root )
     5         kx     {
     5         kx       strcat( (char *)&repo_path[0], repo_root );
     5         kx       strcat( (char *)&repo_path[0], "/" );
     5         kx     }
     5         kx     strcat( (char *)&repo_path[0], repo_name );
     5         kx 
     5         kx     if( revision )
     5         kx       snprintf( (char *)&cmd[0], 1024,
     5         kx                 "svn propget svn:executable --revision %d %s/%s%s/%s 2>/dev/null",
     5         kx                 revision, co_prefix, (char *)&repo_path[0], path, name );
     5         kx     else
     5         kx       snprintf( (char *)&cmd[0], 1024,
     5         kx                 "svn propget svn:executable %s/%s%s/%s 2>/dev/null",
     5         kx                 co_prefix, (char *)&repo_path[0], path, name );
     5         kx     p = sys_exec_command( &buf, cmd );
     5         kx     rc = sys_wait_command( p, NULL );
     5         kx     if( rc != 0 )
     5         kx     {
     5         kx       strbuf_release( &buf );
     5         kx       free( path );
     5         kx       return "";
     5         kx     }
     5         kx 
     5         kx     strbuf_trim( &buf );
     5         kx     if( buf.buf[0] == '*' )
     5         kx       ret = " exec";
     5         kx     strbuf_release( &buf );
     5         kx   }
     5         kx 
     5         kx   free( path );
     5         kx   return ret;
     5         kx }
     5         kx 
     5         kx 
     5         kx static xmlNode *xml_find_node_by_name( xmlNode *root, const char *name )
     5         kx {
     5         kx   xmlNode *node = NULL;
     5         kx   xmlNode *ret  = NULL;
     5         kx 
     5         kx   if( !name || !*name || !root || !root->children ) return ret;
     5         kx 
     5         kx   for( node = root->children; node; node = node->next )
     5         kx   {
     5         kx     if( node->type == XML_ELEMENT_NODE )
     5         kx     {
     5         kx       if( !strcmp( (char *)name, (char *)node->name ) )
     5         kx       {
     5         kx         return node;
     5         kx       }
     5         kx     }
     5         kx   }
     5         kx   return ret;
     5         kx }
     5         kx 
     5         kx static void xml_csvn_tree( const struct strbuf *buf )
     5         kx {
     5         kx   xmlDocPtr doc = NULL;
     5         kx   xmlNode *root = NULL;
     5         kx 
     5         kx   if( !buf || !buf->buf ) return;
     5         kx 
     5         kx   LIBXML_TEST_VERSION
     5         kx 
     5         kx   doc = xmlReadMemory( buf->buf, buf->len, "list.xml", NULL, 0 );
     5         kx   if( !doc )
     5         kx   {
     5         kx     html_fatal( "cannot parse svn list.xml" );
     5         kx     return;
     5         kx   }
     5         kx 
     5         kx   root = xmlDocGetRootElement( doc );
     5         kx   if( !root )
     5         kx   {
     5         kx     xmlFreeDoc( doc );
     5         kx     xmlCleanupParser();
     5         kx     return;
     5         kx   }
     5         kx 
     5         kx   if( !strcmp( "lists", (char *)root->name ) )
     5         kx   {
     5         kx     xmlNode *list = NULL, *node = NULL;
     5         kx 
     5         kx     list = xml_find_node_by_name( root, "list" );
     5         kx     if( !list )
     5         kx     {
     5         kx       xmlFreeDoc( doc );
     5         kx       xmlCleanupParser();
     5         kx       return;
     5         kx     }
     5         kx 
     5         kx     /****************************
     5         kx       Collect directories first:
     5         kx      */
     5         kx     for( node = list->children; node; node = node->next )
     5         kx     {
     5         kx       if( node->type == XML_ELEMENT_NODE && !strcmp( "entry", (char *)node->name ) )
     5         kx       {
     5         kx         xmlChar *content = NULL;
     5         kx         xmlNode *param   = NULL;
     5         kx 
     5         kx         content = xmlGetProp( node, (const unsigned char *)"kind" );
     5         kx         if( !strcmp( (const char *)content, "dir" ) )
     5         kx         {
     5         kx           xmlChar *name = NULL, *revision = NULL, *date = NULL;
     5         kx 
     5         kx           for( param = node->children; param; param= param->next )
     5         kx           {
     5         kx             if( param->type == XML_ELEMENT_NODE && !strcmp( "name", (char *)param->name ) )
     5         kx             {
     5         kx               name = xmlNodeGetContent( param );
     5         kx             }
     5         kx             if( param->type == XML_ELEMENT_NODE && !strcmp( "commit", (char *)param->name ) )
     5         kx             {
     5         kx               xmlNode *commit_date = NULL;
     5         kx               revision = xmlGetProp( param, (const unsigned char *)"revision" );
     5         kx               commit_date = xml_find_node_by_name( param, "date" );
     5         kx               if( commit_date )
     5         kx                 date = xmlNodeGetContent( commit_date );
     5         kx             }
     5         kx           } /* End for entry parameters */
     5         kx 
     5         kx           if( name && revision && date )
     5         kx           {
     5         kx             struct tree_line *line = NULL;
     5         kx 
     5         kx             line = tree_line_alloc( (const char *)name, (const char *)revision, (const char *)date, NULL );
     5         kx             directories = dlist_append( directories, (void *)line );
     5         kx 
     5         kx             xmlFree( name );
     5         kx             xmlFree( revision );
     5         kx             xmlFree( date );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             if( name )     xmlFree( name );
     5         kx             if( revision ) xmlFree( revision );
     5         kx             if( date )     xmlFree( date );
     5         kx           }
     5         kx         }
     5         kx         xmlFree( content );
     5         kx       }
     5         kx     }
     5         kx 
     5         kx     /****************************
     5         kx       Collect files:
     5         kx      */
     5         kx     for( node = list->children; node; node = node->next )
     5         kx     {
     5         kx       if( node->type == XML_ELEMENT_NODE && !strcmp( "entry", (char *)node->name ) )
     5         kx       {
     5         kx         xmlChar *content = NULL;
     5         kx         xmlNode *param   = NULL;
     5         kx 
     5         kx         content = xmlGetProp( node, (const unsigned char *)"kind" );
     5         kx         if( !strcmp( (const char *)content, "file" ) )
     5         kx         {
     5         kx           xmlChar *name = NULL, *size = NULL, *revision = NULL, *date = NULL;
     5         kx 
     5         kx           for( param = node->children; param; param= param->next )
     5         kx           {
     5         kx             if( param->type == XML_ELEMENT_NODE && !strcmp( "name", (char *)param->name ) )
     5         kx             {
     5         kx               name = xmlNodeGetContent( param );
     5         kx             }
     5         kx             if( param->type == XML_ELEMENT_NODE && !strcmp( "size", (char *)param->name ) )
     5         kx             {
     5         kx               size = xmlNodeGetContent( param );
     5         kx             }
     5         kx             if( param->type == XML_ELEMENT_NODE && !strcmp( "commit", (char *)param->name ) )
     5         kx             {
     5         kx               xmlNode *commit_date = NULL;
     5         kx               revision = xmlGetProp( param, (const unsigned char *)"revision" );
     5         kx               commit_date = xml_find_node_by_name( param, "date" );
     5         kx               if( commit_date )
     5         kx                 date = xmlNodeGetContent( commit_date );
     5         kx             }
     5         kx           } /* End for entry parameters */
     5         kx 
     5         kx           if( name && size && revision && date )
     5         kx           {
     5         kx             struct tree_line *line = NULL;
     5         kx 
     5         kx             line = tree_line_alloc( (const char *)name, (const char *)revision, (const char *)date, (const char *)size );
     5         kx             files = dlist_append( files, (void *)line );
     5         kx 
     5         kx             xmlFree( name );
     5         kx             xmlFree( size );
     5         kx             xmlFree( revision );
     5         kx             xmlFree( date );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             if( name )     xmlFree( name );
     5         kx             if( size )     xmlFree( size );
     5         kx             if( revision ) xmlFree( revision );
     5         kx             if( date )     xmlFree( date );
     5         kx           }
     5         kx         }
     5         kx         xmlFree( content );
     5         kx       }
     5         kx     }
     5         kx   }
     5         kx 
     5         kx   xmlFreeDoc(doc);
     5         kx   xmlCleanupParser();
     5         kx }
     5         kx 
     5         kx 
     5         kx static void dlist_csvn_tree( struct strbuf *sb, const char *relative_path, int rev )
     5         kx {
     5         kx   char  *path = NULL;
     5         kx 
     5         kx   if( !sb || !sb->len ) return;
     5         kx 
     5         kx   if( relative_path && *relative_path )
     5         kx   {
     5         kx     path = (char *)xmalloc( strlen( relative_path ) + 2 );
     5         kx     path[0] = '/';
     5         kx     sprintf( (char *)&path[1], relative_path );
     5         kx   }
     5         kx   else
     5         kx   {
     5         kx     path = (char *)xmalloc( 1 );
     5         kx     path[0] = '\0';
     5         kx   }
     5         kx 
     5         kx   if( path && *path && !strcmp( (char *)&path[1], ctx.repo.tags ) )
     5         kx   {
     5         kx     directories = tree_lines_sort_bytag( directories );
     5         kx   }
     5         kx 
     5         kx   strbuf_addstr( sb, "\n" );
     5         kx   strbuf_addf( sb, "              <div class=\"repo-tree-header\">\n" );
     5         kx   strbuf_addf( sb, "                <div class=\"row\">\n" );
     5         kx   strbuf_addf( sb, "                  <div class=\"col-path\">Path</div>\n" );
     5         kx   strbuf_addf( sb, "                  <div class=\"col-size\"><div class=\"tree-size trunc\">Size</div></div>\n" );
     5         kx   strbuf_addf( sb, "                  <div class=\"col-rev\"><div class=\"tree-rev trunc\">Rev</div></div>\n" );
     5         kx   strbuf_addf( sb, "                  <div class=\"col-date\"><div class=\"tree-date trunc\">Date</div></div>\n" );
     5         kx   strbuf_addf( sb, "                  <div class=\"col-links\"><div class=\"tree-links trunc\">Links</div></div>\n" );
     5         kx   strbuf_addf( sb, "                </div>\n" );
     5         kx   strbuf_addf( sb, "              </div>\n\n" );
     5         kx 
     5         kx   strbuf_addf( sb, "              <div class=\"tree\">\n\n" );
     5         kx 
     5         kx   /**************************
     5         kx     Print directories first:
     5         kx    */
     5         kx   if( directories )
     5         kx   {
     5         kx     struct dlist *list = directories;
     5         kx     while( list )
     5         kx     {
     5         kx       struct tree_line *line = (struct tree_line *)list->data;
     5         kx 
     5         kx       if( line->name && line->revision && line->date )
     5         kx       {
     5         kx         struct tm   tm;
     5         kx         time_t      time = -1;
     5         kx         const char *query_string = ctx_remove_query_param( ctx.env.query_string, "rev" );
     5         kx 
     5         kx         query_string = ctx_remove_query_param( query_string, "op" );
     5         kx 
     5         kx         time = parse_date( &tm, (const char *)line->date );
     5         kx 
     5         kx         strbuf_addf( sb, "                <div class=\"row\">\n" );
     5         kx         if( ctx.env.query_string && *ctx.env.query_string )
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s/%s%s/%s/?%s\"><div class=\"tree-path dir\">%s/</div></a></div>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, ctx.env.query_string, line->name );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s%s/%s/?%s\"><div class=\"tree-path dir\">%s/</div></a></div>\n", ctx.repo.name, path, line->name, ctx.env.query_string, line->name );
     5         kx           }
     5         kx         }
     5         kx         else
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s/%s%s/%s/\"><div class=\"tree-path dir\">%s/</div></a></div>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->name );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s%s/%s/\"><div class=\"tree-path dir\">%s/</div></a></div>\n", ctx.repo.name, path, line->name, line->name );
     5         kx           }
     5         kx         }
     5         kx         strbuf_addf( sb, "                  <div class=\"col-size\"><div onclick=\"trunc(this)\" class=\"tree-size trunc\">4096</div></div>\n" );
     5         kx         strbuf_addf( sb, "                  <div class=\"col-rev\"><div onclick=\"trunc(this)\" class=\"tree-rev trunc\">%s</div></div>\n", line->revision );
     5         kx         if( time != -1 )
     5         kx         {
     5         kx           strbuf_addf( sb, "                  <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">" );
     5         kx           csvn_print_age( sb, time, 0, 0 );
     5         kx           strbuf_addf( sb, "</div></div>\n" );
     5         kx         }
     5         kx         else
     5         kx         {
     5         kx           strbuf_addf( sb, "                  <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">unknown</div></div>\n" );
     5         kx         }
     5         kx         strbuf_addf( sb, "                  <div class=\"col-links\">\n" );
     5         kx         strbuf_addf( sb, "                    <div onclick=\"trunc(this)\" class=\"tree-links trunc\">\n" );
     5         kx         if( query_string && *query_string )
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=log&rev=%s&%s\">log</a> &nbsp;\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=diff&rev=%s&%s\">diff</a>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=log&rev=%s&%s\">log</a> &nbsp;\n", ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=diff&rev=%s&%s\">diff</a>\n", ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx           }
     5         kx         }
     5         kx         else
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=log&rev=%s\">log</a> &nbsp;\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=diff&rev=%s\">diff</a>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=log&rev=%s\">log</a> &nbsp;\n", ctx.repo.name, path, line->name, line->revision );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=diff&rev=%s\">diff</a>\n", ctx.repo.name, path, line->name, line->revision );
     5         kx           }
     5         kx         }
     5         kx         strbuf_addf( sb, "                    </div>\n" );
     5         kx         strbuf_addf( sb, "                  </div>\n" );
     5         kx         strbuf_addf( sb, "                </div>\n\n" );
     5         kx       }
     5         kx 
     5         kx       list = dlist_next( list );
     5         kx     }
     5         kx     directories = tree_lines_free( directories );
     5         kx   }
     5         kx 
     5         kx   /**************************
     5         kx     Print files:
     5         kx    */
     5         kx   if( files )
     5         kx   {
     5         kx     struct dlist *list = files;
     5         kx     while( list )
     5         kx     {
     5         kx       struct tree_line *line = (struct tree_line *)list->data;
     5         kx 
     5         kx       if( line->name && line->size && line->revision && line->date )
     5         kx       {
     5         kx         struct tm   tm;
     5         kx         time_t      time = -1;
     5         kx         const char *query_string = ctx_remove_query_param( ctx.env.query_string, "rev" );
     5         kx         const char *exec         = is_file_executable( relative_path, (const char *)line->name, rev );
     5         kx 
     5         kx         query_string = ctx_remove_query_param( query_string, "op" );
     5         kx 
     5         kx         time = parse_date( &tm, (const char *)line->date );
     5         kx 
     5         kx         strbuf_addf( sb, "                <div class=\"row\">\n" );
     5         kx         if( ctx.env.query_string && *ctx.env.query_string )
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s/%s%s/%s/?%s\"><div class=\"tree-path file%s\">%s</div></a></div>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, ctx.env.query_string, exec, line->name );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s%s/%s/?%s\"><div class=\"tree-path file%s\">%s</div></a></div>\n", ctx.repo.name, path, line->name, ctx.env.query_string, exec, line->name );
     5         kx           }
     5         kx         }
     5         kx         else
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s/%s%s/%s/\"><div class=\"tree-path file%s\">%s</div></a></div>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, exec, line->name );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                  <div class=\"col-path\"><a href=\"/%s%s/%s/\"><div class=\"tree-path file%s\">%s</div></a></div>\n", ctx.repo.name, path, line->name, exec, line->name );
     5         kx           }
     5         kx         }
     5         kx         strbuf_addf( sb, "                  <div class=\"col-size\"><div onclick=\"trunc(this)\" class=\"tree-size trunc\">%s</div></div>\n", line->size );
     5         kx         strbuf_addf( sb, "                  <div class=\"col-rev\"><div onclick=\"trunc(this)\" class=\"tree-rev trunc\">%s</div></div>\n", line->revision );
     5         kx         if( time != -1 )
     5         kx         {
     5         kx           strbuf_addf( sb, "                  <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">" );
     5         kx           csvn_print_age( sb, time, 0, 0 );
     5         kx           strbuf_addf( sb, "</div></div>\n" );
     5         kx         }
     5         kx         else
     5         kx         {
     5         kx           strbuf_addf( sb, "                  <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">unknown</div></div>\n" );
     5         kx         }
     5         kx         strbuf_addf( sb, "                  <div class=\"col-links\">\n" );
     5         kx         strbuf_addf( sb, "                    <div onclick=\"trunc(this)\" class=\"tree-links trunc\">\n" );
     5         kx         if( query_string && *query_string )
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=log&rev=%s&%s\">log</a> &nbsp;\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=diff&rev=%s&%s\">diff &nbsp;</a>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=blame&rev=%s&%s\">blame</a>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=log&rev=%s&%s\">log</a> &nbsp;\n", ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=diff&rev=%s&%s\">diff &nbsp;</a>\n", ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=blame&rev=%s&%s\">blame</a>\n", ctx.repo.name, path, line->name, line->revision, query_string );
     5         kx           }
     5         kx         }
     5         kx         else
     5         kx         {
     5         kx           if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=log&rev=%s\">log</a> &nbsp;\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=diff&rev=%s\">diff</a> &nbsp;\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s/%s%s/%s/?op=blame&rev=%s\">blame</a>\n", ctx.repo.repo_root, ctx.repo.name, path, line->name, line->revision );
     5         kx           }
     5         kx           else
     5         kx           {
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=log&rev=%s\">log</a> &nbsp;\n", ctx.repo.name, path, line->name, line->revision );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=diff&rev=%s\">diff</a> &nbsp;\n", ctx.repo.name, path, line->name, line->revision );
     5         kx             strbuf_addf( sb, "                      <a href=\"/%s%s/%s/?op=blame&rev=%s\">blame</a>\n", ctx.repo.name, path, line->name, line->revision );
     5         kx           }
     5         kx         }
     5         kx         strbuf_addf( sb, "                    </div>\n" );
     5         kx         strbuf_addf( sb, "                  </div>\n" );
     5         kx         strbuf_addf( sb, "                </div>\n\n" );
     5         kx       }
     5         kx 
     5         kx       list = dlist_next( list );
     5         kx     }
     5         kx     files = tree_lines_free( files );
     5         kx   }
     5         kx 
     5         kx   strbuf_addf( sb, "              </div> <!-- End of Tree -->\n\n" );
     5         kx 
     5         kx   free( path );
     5         kx }
     5         kx 
     5         kx static void csvn_print_tree( struct strbuf *sb, const char *relative_path, int revision )
     5         kx {
     5         kx   const char *co_prefix = ctx.repo.checkout_ro_prefix;
     5         kx   const char *name      = ctx.repo.name;
     5         kx   const char *repo_root = ctx.repo.repo_root;
     5         kx   char *path = NULL;
     5         kx 
     5         kx   if( !sb ) return;
     5         kx 
     5         kx   if( relative_path && *relative_path )
     5         kx   {
     5         kx     path = (char *)xmalloc( strlen( relative_path ) + 2 );
     5         kx     path[0] = '/';
     5         kx     sprintf( (char *)&path[1], relative_path );
     5         kx   }
     5         kx   else
     5         kx   {
     5         kx     path = (char *)xmalloc( 1 );
     5         kx     path[0] = '\0';
     5         kx   }
     5         kx 
     5         kx   if( co_prefix )
     5         kx   {
     5         kx     char repo_path[PATH_MAX] = { 0 };
     5         kx     char cmd[PATH_MAX];
     5         kx     struct strbuf buf = STRBUF_INIT;
     5         kx     pid_t p = (pid_t) -1;
     5         kx     int   rc;
     5         kx 
     5         kx     if( repo_root && *repo_root )
     5         kx     {
     5         kx       strcat( (char *)&repo_path[0], repo_root );
     5         kx       strcat( (char *)&repo_path[0], "/" );
     5         kx     }
     5         kx     strcat( (char *)&repo_path[0], name );
     5         kx 
     5         kx     if( revision )
     5         kx       snprintf( (char *)&cmd[0], 1024,
     5         kx                 "svn list --revision %d --xml %s/%s%s 2>/dev/null",
     5         kx                 revision, co_prefix, (char *)&repo_path[0], path );
     5         kx     else
     5         kx       snprintf( (char *)&cmd[0], 1024,
     5         kx                 "svn list --xml %s/%s%s 2>/dev/null",
     5         kx                 co_prefix, (char *)&repo_path[0], path );
     5         kx     p = sys_exec_command( &buf, cmd );
     5         kx     rc = sys_wait_command( p, NULL );
     5         kx     if( rc != 0 )
     5         kx     {
     5         kx       strbuf_release( &buf );
     5         kx       free( path );
     5         kx       return;
     5         kx     }
     5         kx 
     5         kx     xml_csvn_tree( (const struct strbuf *)&buf );
     5         kx     strbuf_release( &buf );
     5         kx 
     5         kx     dlist_csvn_tree( sb, relative_path, revision );
     5         kx   }
     5         kx 
     5         kx   free( path );
     5         kx   return;
     5         kx }
     5         kx 
     5         kx static void csvn_print_checkout_urls( struct strbuf *sb, const char *relative_path, int revision )
     5         kx {
     5         kx   char *path = NULL;
     5         kx 
     5         kx   if( !sb ) return;
     5         kx 
     5         kx   if( relative_path && *relative_path )
     5         kx   {
     5         kx     path = (char *)xmalloc( strlen( relative_path ) + 2 );
     5         kx     path[0] = '/';
     5         kx     sprintf( (char *)&path[1], relative_path );
     5         kx   }
     5         kx   else
     5         kx   {
     5         kx     path = (char *)xmalloc( 1 );
     5         kx     path[0] = '\0';
     5         kx   }
     5         kx 
     5         kx   strbuf_addf( sb, "                <div class=\"checkout-box\">\n" );
     5         kx   strbuf_addf( sb, "                  <div class=\"checkout-header\">Checkout</div>\n" );
     5         kx   strbuf_addf( sb, "                  <div class=\"checkout-urls\">\n" );
     5         kx   strbuf_addf( sb, "                    <div class=\"checkout-line\">\n" );
     5         kx   strbuf_addf( sb, "                      <div class=\"checkout-perms\">ro:</div>\n" );
     5         kx 
     5         kx   if( revision )
     5         kx   {
     5         kx     if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx     {
     5         kx       strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout --revision %d %s/%s/%s%s</div>\n", revision, ctx.repo.checkout_ro_prefix, ctx.repo.repo_root, ctx.repo.name, path );
     5         kx     }
     5         kx     else
     5         kx     {
     5         kx       strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout --revision %d %s/%s%s</div>\n", revision, ctx.repo.checkout_ro_prefix, ctx.repo.name, path );
     5         kx     }
     5         kx   }
     5         kx   else
     5         kx   {
     5         kx     if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx     {
     5         kx       strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout %s/%s/%s%s</div>\n", ctx.repo.checkout_ro_prefix, ctx.repo.repo_root, ctx.repo.name, path );
     5         kx     }
     5         kx     else
     5         kx     {
     5         kx       strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout %s/%s%s</div>\n", ctx.repo.checkout_ro_prefix, ctx.repo.name, path );
     5         kx     }
     5         kx   }
     5         kx 
     5         kx   strbuf_addf( sb, "                    </div>\n" );
     5         kx   if( ctx.repo.checkout_prefix && *ctx.repo.checkout_prefix )
     5         kx   {
     5         kx     strbuf_addf( sb, "                    <div class=\"checkout-line\">\n" );
     5         kx     strbuf_addf( sb, "                      <div class=\"checkout-perms\">rw:</div>\n" );
     5         kx 
     5         kx     if( revision )
     5         kx     {
     5         kx       if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx       {
     5         kx         strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout --revision %d %s/%s/%s%s</div>\n", revision, ctx.repo.checkout_prefix, ctx.repo.repo_root, ctx.repo.name, path );
     5         kx       }
     5         kx       else
     5         kx       {
     5         kx         strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout --revision %d %s/%s%s</div>\n", revision, ctx.repo.checkout_prefix, ctx.repo.name, path );
     5         kx       }
     5         kx     }
     5         kx     else
     5         kx     {
     5         kx       if( ctx.repo.repo_root && *ctx.repo.repo_root )
     5         kx       {
     5         kx         strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout %s/%s/%s%s</div>\n", ctx.repo.checkout_prefix, ctx.repo.repo_root, ctx.repo.name, path );
     5         kx       }
     5         kx       else
     5         kx       {
     5         kx         strbuf_addf( sb, "                      <div class=\"checkout-url\">svn checkout %s/%s%s</div>\n", ctx.repo.checkout_prefix, ctx.repo.name, path );
     5         kx       }
     5         kx     }
     5         kx 
     5         kx     strbuf_addf( sb, "                    </div>\n" );
     5         kx   }
     5         kx   strbuf_addf( sb, "                  </div>\n" );
     5         kx   strbuf_addf( sb, "                </div>\n" );
     5         kx 
     5         kx   free( path );
     5         kx }
     5         kx 
     5         kx void csvn_print_tree_page( void )
     5         kx {
     5         kx   FILE  *fp;
     5         kx   struct strbuf buf = STRBUF_INIT;
     5         kx 
     5         kx   fp = xfopen( ctx.page.header, "r" );
     5         kx   (void)strbuf_env_fread( &buf, fp );
     5         kx   fclose( fp );
     5         kx 
     5         kx   strbuf_addf( &buf, "        <div class=\"content segment\">\n" );
     5         kx   strbuf_addf( &buf, "          <div class=\"container\">\n" );
     5         kx   strbuf_addf( &buf, "            <div class=\"csvn-main-content\">\n" );
     5         kx 
     5         kx   if( ctx.repo.relative_info.kind == KIND_DIR )
     5         kx   {
     5         kx     if( ctx.repo.name )
     5         kx     {
     5         kx       csvn_print_tree( &buf, ctx.repo.relative_path, ctx.query.rev );
     5         kx       csvn_print_checkout_urls( &buf, ctx.repo.relative_path, ctx.query.rev );
     5         kx     }
     5         kx     else
     5         kx     {
     5         kx       strbuf_addf( &buf, "              <h1>Requested resource cannot be shown</h1>\n" );
     5         kx       strbuf_addf( &buf, "              <p class='leading'>Repository '%s' not found.</p>\n", ctx.repo.name );
     5         kx     }
     5         kx   }
     5         kx   else
     5         kx   {
     5         kx     strbuf_addf( &buf, "              <h1>Requested resource cannot be shown</h1>\n" );
     5         kx     strbuf_addf( &buf, "              <p class='leading'>This page assume the repository tree to be shown.</p>\n" );
     5         kx   }
     5         kx 
     5         kx   strbuf_addf( &buf, "            </div> <!-- End of csvn-main-content -->\n" );
     5         kx   strbuf_addf( &buf, "          </div> <!-- End of container -->\n" );
     5         kx   strbuf_addf( &buf, "        </div> <!-- End of content segment -->\n" );
     5         kx 
     5         kx   fp = xfopen( ctx.page.footer, "r" );
     5         kx   (void)strbuf_env_fread( &buf, fp );
     5         kx   fclose( fp );
     5         kx 
     5         kx   ctx.page.size = buf.len;
     5         kx   csvn_print_http_headers();
     5         kx   strbuf_write( &buf, STDOUT_FILENO );
     5         kx   strbuf_release( &buf );
     5         kx }