Author: rjollos
Date: Fri Nov 14 12:10:40 2014
New Revision: 1639617

URL: http://svn.apache.org/r1639617
Log:
Missing file adds from Trac 1.0.2.

Added:
    bloodhound/vendor/trac/current/trac/tests/compat.py
    bloodhound/vendor/trac/current/trac/ticket/templates/admin_components.html
    bloodhound/vendor/trac/current/trac/ticket/templates/admin_enums.html
    bloodhound/vendor/trac/current/trac/ticket/templates/admin_milestones.html
    bloodhound/vendor/trac/current/trac/ticket/templates/admin_versions.html
    bloodhound/vendor/trac/current/trac/timeline/tests/web_ui.py
    bloodhound/vendor/trac/current/trac/timeline/tests/wikisyntax.py
    bloodhound/vendor/trac/current/trac/util/tests/translation.py
    bloodhound/vendor/trac/current/tracopt/ticket/tests/
    bloodhound/vendor/trac/current/tracopt/ticket/tests/__init__.py
    bloodhound/vendor/trac/current/tracopt/ticket/tests/commit_updater.py
    bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/git_fs.py

Added: bloodhound/vendor/trac/current/trac/tests/compat.py
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/tests/compat.py?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/tests/compat.py (added)
+++ bloodhound/vendor/trac/current/trac/tests/compat.py Fri Nov 14 12:10:40 2014
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+"""Some test functions since Python 2.7 to provide backwards-compatibility
+with previous versions of Python from 2.5 onward.
+"""
+
+import os
+import shutil
+import sys
+import unittest
+
+
+if not hasattr(unittest.TestCase, 'assertIs'):
+    def assertIs(self, expr1, expr2, msg=None):
+        if expr1 is not expr2:
+            raise self.failureException(msg or '%r is not %r'
+                                               % (expr1, expr2))
+    unittest.TestCase.assertIs = assertIs
+
+
+if not hasattr(unittest.TestCase, 'assertIsNot'):
+    def assertIsNot(self, expr1, expr2, msg=None):
+        if expr1 is expr2:
+            raise self.failureException(msg or '%r is %r' % (expr1, expr2))
+    unittest.TestCase.assertIsNot = assertIsNot
+
+
+if not hasattr(unittest.TestCase, 'assertIsNone'):
+    def assertIsNone(self, obj, msg=None):
+        self.assertIs(obj, None, msg)
+    unittest.TestCase.assertIsNone = assertIsNone
+
+
+if not hasattr(unittest.TestCase, 'assertIsNotNone'):
+    def assertIsNotNone(self, obj, msg=None):
+        self.assertIsNot(obj, None, msg)
+    unittest.TestCase.assertIsNotNone = assertIsNotNone
+
+
+if not hasattr(unittest.TestCase, 'assertIn'):
+    def assertIn(self, member, container, msg=None):
+        if member not in container:
+            raise self.failureException(msg or '%r not in %r' %
+                                               (member, container))
+    unittest.TestCase.assertIn = assertIn
+
+
+if not hasattr(unittest.TestCase, 'assertNotIn'):
+    def assertNotIn(self, member, container, msg=None):
+        if member in container:
+            raise self.failureException(msg or '%r in %r' %
+                                               (member, container))
+    unittest.TestCase.assertNotIn = assertNotIn
+
+
+if not hasattr(unittest.TestCase, 'assertIsInstance'):
+    def assertIsInstance(self, obj, cls, msg=None):
+        if not isinstance(obj, cls):
+            raise self.failureException(msg or '%r is not an instance of %r' %
+                                               (obj, cls))
+    unittest.TestCase.assertIsInstance = assertIsInstance
+
+
+if not hasattr(unittest.TestCase, 'assertNotIsInstance'):
+    def assertNotIsInstance(self, obj, cls, msg=None):
+        if isinstance(obj, cls):
+            raise self.failureException(msg or '%r is an instance of %r' %
+                                               (obj, cls))
+    unittest.TestCase.assertNotIsInstance = assertNotIsInstance
+
+
+def rmtree(path):
+    import errno
+    def onerror(function, path, excinfo):
+        # `os.remove` fails for a readonly file on Windows.
+        # Then, it attempts to be writable and remove.
+        if function != os.remove:
+            raise
+        e = excinfo[1]
+        if isinstance(e, OSError) and e.errno == errno.EACCES:
+            mode = os.stat(path).st_mode
+            os.chmod(path, mode | 0666)
+            function(path)
+        else:
+            raise
+    if os.name == 'nt' and isinstance(path, str):
+        # Use unicode characters in order to allow non-ansi characters
+        # on Windows.
+        path = unicode(path, sys.getfilesystemencoding())
+    shutil.rmtree(path, onerror=onerror)

Added: 
bloodhound/vendor/trac/current/trac/ticket/templates/admin_components.html
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/admin_components.html?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/templates/admin_components.html 
(added)
+++ bloodhound/vendor/trac/current/trac/ticket/templates/admin_components.html 
Fri Nov 14 12:10:40 2014
@@ -0,0 +1,124 @@
+<!--!  Copyright (C) 2006-2014 Edgewall Software
+
+  This software is licensed as described in the file COPYING, which
+  you should have received as part of this distribution. The terms
+  are also available at http://trac.edgewall.com/license.html.
+
+  This software consists of voluntary contributions made by many
+  individuals. For the exact contribution history, see the revision
+  history and logs, available at http://trac.edgewall.org/.
+-->
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:xi="http://www.w3.org/2001/XInclude";
+      xmlns:py="http://genshi.edgewall.org/";
+      xmlns:i18n="http://genshi.edgewall.org/i18n";>
+  <xi:include href="admin.html" />
+  <head>
+    <title>Components</title>
+  </head>
+
+  <body>
+    <h2>Manage Components <span py:if="view == 'list'" 
class="trac-count">(${len(components)})</span></h2>
+
+    <py:def function="owner_field(default_owner='', br_after_label=False)">
+      <div class="field">
+        <label>Owner:<br py:if="br_after_label"/>
+          <py:choose>
+            <select py:when="owners" size="1" id="owner" name="owner">
+              <option py:for="owner in owners"
+                      selected="${owner == default_owner or None}" 
value="$owner">$owner</option>
+              <option py:if="default_owner and default_owner not in owners"
+                      selected="selected" 
value="$default_owner">$default_owner</option>
+            </select>
+            <input py:otherwise="" type="text" name="owner" 
value="$default_owner" />
+          </py:choose>
+        </label>
+      </div>
+    </py:def>
+
+    <py:choose test="view">
+      <form py:when="'detail'" class="mod" id="modcomp" method="post" 
action="">
+        <fieldset>
+          <legend>Modify Component:</legend>
+          <div class="field">
+            <label>Name:<br /><input type="text" name="name" 
class="trac-autofocus" value="$component.name" /></label>
+          </div>
+          ${owner_field(component.owner, True)}
+          <div class="field">
+            <fieldset>
+              <label for="description" i18n:msg="">
+                Description: (you may use <a tabindex="42" 
href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here)
+              </label>
+              <p>
+                <textarea id="description" name="description" class="wikitext 
trac-fullwidth trac-resizable"
+                          rows="6" cols="60">
+$component.description</textarea>
+              </p>
+            </fieldset>
+          </div>
+          <div class="buttons">
+            <input type="submit" name="save" class="trac-disable-on-submit" 
value="${_('Save')}"/>
+            <input type="submit" name="cancel" value="${_('Cancel')}" />
+          </div>
+        </fieldset>
+      </form>
+
+      <py:otherwise>
+        <form class="addnew" id="addcomponent" method="post" action="">
+          <fieldset>
+            <legend>Add Component:</legend>
+            <div class="field">
+              <label>Name: <input type="text" name="name"/></label>
+            </div>
+            ${owner_field()}
+            <div class="buttons">
+              <input type="submit" name="add" class="trac-disable-on-submit" 
value="${_('Add')}"/>
+            </div>
+          </fieldset>
+        </form>
+
+        <py:choose>
+          <form py:when="components" id="component_table" method="post" 
action="">
+            <table class="listing" id="complist">
+              <thead>
+                <tr><th class="sel">&nbsp;</th>
+                  <th>Name</th><th>Owner</th><th>Default</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr py:for="comp in components">
+                  <td class="sel"><input type="checkbox" name="sel" 
value="$comp.name" /></td>
+                  <td class="name">
+                    <a href="${panel_href(comp.name)}">$comp.name</a>
+                  </td>
+                  <td class="owner">$comp.owner</td>
+                  <td class="default">
+                    <input type="radio" name="default" value="$comp.name"
+                           checked="${comp.name == default or None}" />
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+            <div class="buttons">
+              <input type="submit" name="apply" value="${_('Apply changes')}" 
/>
+              <input type="submit" name="remove" 
class="trac-disable-on-submit" value="${_('Remove selected items')}"/>
+            </div>
+            <p class="help">
+              You can remove all items from this list to completely hide this
+              field from the user interface.
+            </p>
+          </form>
+
+          <p py:otherwise="" class="help">
+            As long as you don't add any items to the list, this field
+            will remain completely hidden from the user interface.
+          </p>
+        </py:choose>
+      </py:otherwise>
+    </py:choose>
+  </body>
+
+</html>

Added: bloodhound/vendor/trac/current/trac/ticket/templates/admin_enums.html
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/admin_enums.html?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/templates/admin_enums.html 
(added)
+++ bloodhound/vendor/trac/current/trac/ticket/templates/admin_enums.html Fri 
Nov 14 12:10:40 2014
@@ -0,0 +1,104 @@
+<!--!  Copyright (C) 2006-2014 Edgewall Software
+
+  This software is licensed as described in the file COPYING, which
+  you should have received as part of this distribution. The terms
+  are also available at http://trac.edgewall.com/license.html.
+
+  This software consists of voluntary contributions made by many
+  individuals. For the exact contribution history, see the revision
+  history and logs, available at http://trac.edgewall.org/.
+-->
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:xi="http://www.w3.org/2001/XInclude";
+      xmlns:i18n="http://genshi.edgewall.org/i18n";
+      xmlns:py="http://genshi.edgewall.org/";>
+  <xi:include href="admin.html" />
+  <head>
+    <title>$label_plural</title>
+  </head>
+
+  <body>
+    <h2>
+      <i18n:msg params="label_plural">Manage $label_plural</i18n:msg>
+      <span py:if="view == 'list'" class="trac-count">(${len(enums)})</span>
+    </h2>
+
+    <py:choose test="view">
+      <form py:when="'detail'" class="mod" id="modenum" method="post" 
action="">
+        <fieldset>
+          <legend i18n:msg="label_singular">Modify $label_singular:</legend>
+          <div class="field">
+            <label>Name: <input type="text" name="name" class="trac-autofocus" 
value="${enum.name}" /></label>
+          </div>
+          <div class="buttons">
+            <input type="submit" name="save" class="trac-disable-on-submit" 
value="${_('Save')}"/>
+            <input type="submit" name="cancel" value="${_('Cancel')}"/>
+          </div>
+        </fieldset>
+      </form>
+
+      <py:otherwise>
+        <form class="addnew" id="addenum" method="post" action="">
+          <fieldset>
+            <legend i18n:msg="label_singular">Add $label_singular:</legend>
+            <div class="field">
+              <label>Name: <input type="text" name="name" id="name"/></label>
+            </div>
+            <div class="buttons">
+              <input type="submit" name="add" class="trac-disable-on-submit" 
value="${_('Add')}"/>
+            </div>
+          </fieldset>
+        </form>
+
+        <py:choose>
+          <form py:when="enums" id="enumtable" method="post" action="">
+            <table class="listing" id="enumlist">
+              <thead>
+                <tr><th class="sel">&nbsp;</th>
+                  <th>Name</th><th>Default</th><th>Order</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr py:for="enum in enums">
+                  <td><input type="checkbox" name="sel" value="${enum.name}" 
/></td>
+                  <td><a href="${panel_href(enum.name)}">${enum.name}</a></td>
+                  <td class="default">
+                    <input type="radio" name="default" value="${enum.name}"
+                           checked="${enum.name==default or None}" />
+                  </td>
+                  <td class="default">
+                    <select name="value_${enum.value}">
+                      <option py:for="other in enums"
+                              selected="${other.value==enum.value or 
None}">${other.value}</option>
+                    </select>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+            <div class="buttons">
+              <input type="submit" name="apply" value="${_('Apply changes')}" 
/>
+              <input type="submit" name="remove" 
class="trac-disable-on-submit" value="${_('Remove selected items')}" />
+            </div>
+            <p class="help">
+              You can remove all items from this list to completely hide this
+              field from the user interface.
+            </p>
+            <p class="help" py:if="type=='priority'" i18n:msg="">
+              <strong>Note:</strong> The order of priorities determines the
+              coloring of entries in the ticket queries and reports.
+            </p>
+          </form>
+
+          <p py:otherwise="" class="help">
+            As long as you don't add any items to the list, this field
+            will remain completely hidden from the user interface.
+          </p>
+        </py:choose>
+      </py:otherwise>
+    </py:choose>
+  </body>
+
+</html>

Added: 
bloodhound/vendor/trac/current/trac/ticket/templates/admin_milestones.html
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/admin_milestones.html?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/templates/admin_milestones.html 
(added)
+++ bloodhound/vendor/trac/current/trac/ticket/templates/admin_milestones.html 
Fri Nov 14 12:10:40 2014
@@ -0,0 +1,166 @@
+<!--!  Copyright (C) 2006-2014 Edgewall Software
+
+  This software is licensed as described in the file COPYING, which
+  you should have received as part of this distribution. The terms
+  are also available at http://trac.edgewall.com/license.html.
+
+  This software consists of voluntary contributions made by many
+  individuals. For the exact contribution history, see the revision
+  history and logs, available at http://trac.edgewall.org/.
+-->
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:xi="http://www.w3.org/2001/XInclude";
+      xmlns:py="http://genshi.edgewall.org/";
+      xmlns:i18n="http://genshi.edgewall.org/i18n";
+      py:with="perm = req.perm('admin', 'ticket/milestones');
+               can_create = 'MILESTONE_CREATE' in perm;
+               can_modify = 'MILESTONE_MODIFY' in perm;
+               can_remove = 'MILESTONE_DELETE' in perm">
+  <xi:include href="admin.html" />
+  <head>
+    <title>Milestones</title>
+    <script type="text/javascript"
+            py:if="view == 'detail' and can_modify or
+                   view == 'list' and can_create">
+      jQuery(document).ready(function($) {
+        $("#duedate").datetimepicker();
+        $("#completeddate").datetimepicker();
+      });
+    </script>
+  </head>
+
+  <body>
+    <h2>Manage Milestones <span py:if="view == 'list'" 
class="trac-count">(${len(milestones)})</span></h2>
+
+    <py:choose test="view">
+      <form py:when="'detail'" class="mod" method="post" id="modifymilestone" 
action=""
+            py:with="disabled = 'disabled' if not can_modify else None;
+                     readonly = 'readonly' if not can_modify else None">
+        <fieldset>
+          <legend>Modify Milestone:</legend>
+          <div class="field">
+            <label>Name:<br /> <input type="text" name="name" 
class="trac-autofocus"
+                                      value="$milestone.name" 
readonly="${readonly}" /></label>
+          </div>
+          <div class="field">
+            <label>Due:<br />
+              <input type="text" id="duedate" name="duedate" 
size="${len(datetime_hint)}"
+                     value="${milestone.due and 
format_datetime(milestone.due)}" readonly="${readonly}"
+                     title="${_('Format: %(datehint)s', 
datehint=datetime_hint)}"/>
+              <span class="hint" i18n:msg="datehint">Format: 
$datetime_hint</span>
+            </label>
+          </div>
+          <div class="field">
+            <label>
+              <input type="checkbox" id="completed" name="completed"
+                     checked="${milestone.completed or None}" 
disabled="${disabled}"/>
+              Completed:<br />
+            </label>
+            <label>
+              <input type="text" id="completeddate" name="completeddate"
+                     size="${len(datetime_hint)}"
+                     value="${format_datetime(milestone.completed)}" 
readonly="${readonly}"
+                     title="${_('Format: %(datehint)s', 
datehint=datetime_hint)}" />
+              <span class="hint" i18n:msg="datehint">Format: 
$datetime_hint</span>
+            </label>
+            <script type="text/javascript">
+              jQuery(document).ready(function($) {
+                function updateCompletedDate() {
+                  $("#completeddate").enable($("#completed").checked());
+                }
+                $("#completed").click(updateCompletedDate);
+                updateCompletedDate();
+              });
+            </script>
+          </div>
+          <div class="field">
+            <fieldset>
+              <label for="description" i18n:msg="">
+                Description: (you may use <a tabindex="42" 
href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here)
+              </label>
+              <p>
+              <textarea id="description" name="description" class="wikitext 
trac-fullwidth trac-resizable"
+                        rows="6" cols="60" readonly="${readonly}">
+${milestone.description}</textarea>
+              </p>
+            </fieldset>
+          </div>
+          <div class="buttons">
+            <input type="submit" name="save" value="${_('Save')}" 
class="trac-disable-on-submit" disabled="${disabled}"/>
+            <input type="submit" name="cancel" value="${_('Cancel')}"/>
+          </div>
+        </fieldset>
+      </form>
+
+      <py:otherwise>
+        <form class="addnew" id="addmilestone" method="post" action="" 
py:if="can_create">
+          <fieldset>
+            <legend>Add Milestone:</legend>
+            <div class="field">
+              <label>Name: <input type="text" name="name" id="name" 
size="22"/></label>
+            </div>
+            <div class="field">
+              <label>
+                Due:
+                <input type="text" id="duedate" name="duedate" 
size="${len(datetime_hint)}"
+                       title="${_('Format: %(datehint)s', 
datehint=datetime_hint)}"/>
+                <span class="hint" i18n:msg="datetimehint">Format: 
$datetime_hint</span>
+              </label>
+            </div>
+            <div class="buttons">
+              <input type="submit" name="add" class="trac-disable-on-submit" 
value="${_('Add')}"/>
+            </div>
+          </fieldset>
+        </form>
+
+        <py:choose>
+          <form id="milestone_table" method="post" action="" 
py:when="milestones">
+            <table class="listing" id="millist">
+              <thead>
+                <tr><th class="sel" py:if="can_remove">&nbsp;</th>
+                  
<th>Name</th><th>Due</th><th>Completed</th><th>Default</th><th>Tickets</th>
+                </tr>
+              </thead>
+              <tbody><tr py:for="(milestone, ticket_count) in milestones">
+                <td py:if="can_remove">
+                  <input type="checkbox" name="sel" value="$milestone.name" />
+                </td>
+                <td>
+                  <a href="${panel_href(milestone.name)}">${milestone.name}</a>
+                </td>
+                <td><py:if test="milestone.due">
+                  ${format_datetime(milestone.due)}
+                </py:if></td>
+                <td><py:if test="milestone.completed">
+                  ${format_datetime(milestone.completed)}
+                </py:if></td>
+                <td class="default">
+                  <input type="radio" name="default" value="$milestone.name"
+                         checked="${milestone.name==default or None}" />
+                </td>
+                <td class="num">${ticket_count}</td>
+              </tr></tbody>
+            </table>
+            <div class="buttons">
+              <input type="submit" name="apply" value="${_('Apply changes')}" 
/>
+              <input type="submit" name="remove" 
class="trac-disable-on-submit" value="${_('Remove selected items')}" 
py:if="can_remove"/>
+            </div>
+            <p class="help">
+              You can remove all items from this list to completely hide this
+              field from the user interface.
+            </p>
+          </form>
+
+          <p py:otherwise="" class="help">
+            As long as you don't add any items to the list, this field
+            will remain completely hidden from the user interface.
+          </p>
+        </py:choose>
+      </py:otherwise>
+    </py:choose>
+  </body>
+
+</html>

Added: bloodhound/vendor/trac/current/trac/ticket/templates/admin_versions.html
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/admin_versions.html?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/ticket/templates/admin_versions.html 
(added)
+++ bloodhound/vendor/trac/current/trac/ticket/templates/admin_versions.html 
Fri Nov 14 12:10:40 2014
@@ -0,0 +1,127 @@
+<!--!  Copyright (C) 2006-2014 Edgewall Software
+
+  This software is licensed as described in the file COPYING, which
+  you should have received as part of this distribution. The terms
+  are also available at http://trac.edgewall.com/license.html.
+
+  This software consists of voluntary contributions made by many
+  individuals. For the exact contribution history, see the revision
+  history and logs, available at http://trac.edgewall.org/.
+-->
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:xi="http://www.w3.org/2001/XInclude";
+      xmlns:py="http://genshi.edgewall.org/";
+      xmlns:i18n="http://genshi.edgewall.org/i18n";>
+  <xi:include href="admin.html" />
+  <head>
+    <title>Versions</title>
+    <script type="text/javascript">
+      jQuery(document).ready(function($) {
+        $("#releaseddate").datetimepicker();
+      });
+    </script>
+  </head>
+
+  <body>
+    <h2>Manage Versions <span py:if="view == 'list'" 
class="trac-count">(${len(versions)})</span></h2>
+
+    <py:choose test="view">
+      <form py:when="'detail'" class="mod" id="modifyversion" method="post" 
action="">
+        <fieldset>
+          <legend>Modify Version:</legend>
+          <div class="field">
+            <label>Name:<br />
+              <input type="text" name="name" class="trac-autofocus" 
value="${version.name}" />
+            </label>
+          </div>
+          <div class="field">
+            <label>Released:<br />
+              <input type="text" id="releaseddate" name="time" 
size="${len(datetime_hint)}"
+                     value="${format_datetime(version.time)}"
+                     title="${_('Format: %(datehint)s', 
datehint=datetime_hint)}" />
+              <span class="hint" i18n:msg="datehint">Format: 
$datetime_hint</span>
+            </label>
+          </div>
+          <div class="field">
+            <fieldset>
+              <label for="description" i18n:msg="">
+                Description: (you may use <a tabindex="42" 
href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here)
+              </label>
+              <p>
+                <textarea id="description" name="description" class="wikitext 
trac-fullwidth trac-resizable" rows="6" cols="60">
+$version.description</textarea>
+              </p>
+            </fieldset>
+          </div>
+          <div class="buttons">
+            <input type="submit" name="save" class="trac-disable-on-submit" 
value="${_('Save')}"/>
+            <input type="submit" name="cancel" value="${_('Cancel')}"/>
+          </div>
+        </fieldset>
+      </form>
+
+      <py:otherwise>
+        <form class="addnew" id="addversion" method="post" action="">
+          <fieldset>
+            <legend>Add Version:</legend>
+            <div class="field">
+              <label>Name: <input type="text" name="name" id="name" 
size="22"/></label>
+            </div>
+            <div class="field">
+              <label>
+                Released:
+                <input type="text" id="releaseddate" name="time" 
size="${len(datetime_hint)}"
+                       title="${_('Format: %(datehint)s', 
datehint=datetime_hint)}"
+                       value="${format_datetime()}"/>
+                <span class="hint" i18n:msg="datehint">Format: 
$datetime_hint</span>
+              </label>
+            </div>
+            <div class="buttons">
+              <input type="submit" name="add" class="trac-disable-on-submit" 
value="${_('Add')}" />
+            </div>
+          </fieldset>
+        </form>
+
+        <py:choose>
+          <form py:when="versions" id="version_table" method="post" action="">
+            <table class="listing" id="verlist">
+              <thead>
+                <tr><th class="sel">&nbsp;</th>
+                  <th>Name</th><th>Released</th><th>Default</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr py:for="version in versions">
+                  <td><input type="checkbox" name="sel" 
value="${version.name}"/></td>
+                  <td><a 
href="${panel_href(version.name)}">${version.name}</a></td>
+                  <td>${version.time and format_datetime(version.time)}</td>
+                  <td class="default">
+                    <input type="radio" name="default" value="${version.name}"
+                           checked="${version.name==default or None}" />
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+            <div class="buttons">
+              <input type="submit" name="apply" value="${_('Apply changes')}" 
/>
+              <input type="submit" name="remove" 
class="trac-disable-on-submit" value="${_('Remove selected items')}" />
+            </div>
+            <p class="help">
+              You can remove all items from this list to completely hide this
+              field from the user interface.
+            </p>
+          </form>
+
+          <p py:otherwise="" class="help">
+            As long as you don't add any items to the list, this field
+            will remain completely hidden from the user interface.
+          </p>
+        </py:choose>
+      </py:otherwise>
+    </py:choose>
+  </body>
+
+</html>

Added: bloodhound/vendor/trac/current/trac/timeline/tests/web_ui.py
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/timeline/tests/web_ui.py?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/timeline/tests/web_ui.py (added)
+++ bloodhound/vendor/trac/current/trac/timeline/tests/web_ui.py Fri Nov 14 
12:10:40 2014
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2014 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+import unittest
+from datetime import datetime, timedelta
+
+from trac.test import EnvironmentStub, Mock, MockPerm, locale_en
+from trac.timeline.web_ui import TimelineModule
+from trac.util.datefmt import (
+    format_date, format_datetime, format_time, pretty_timedelta, utc,
+)
+from trac.util.html import plaintext
+from trac.web.chrome import Chrome
+from trac.web.href import Href
+
+
+class PrettyDateinfoTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.env = EnvironmentStub()
+        self.req = Mock(href=Href('/'), abs_href=Href('http://example.org/'),
+                        authname='anonymous', tz=utc, locale=locale_en,
+                        lc_time=locale_en, chrome={}, perm=MockPerm(),
+                        session={})
+
+    def tearDown(self):
+        self.env.reset_db()
+
+    def _format_chrome(self, d, format, dateonly):
+        data = Chrome(self.env).populate_data(self.req, {})
+        return plaintext(data['pretty_dateinfo'](d, format=format,
+                                                 dateonly=dateonly))
+
+    def _format_timeline(self, d, format, dateonly):
+        data = Chrome(self.env).populate_data(self.req, {})
+        TimelineModule(self.env) \
+            .post_process_request(self.req, 'timeline.html', data, None)
+        return plaintext(data['pretty_dateinfo'](d, format=format,
+                                                 dateonly=dateonly))
+
+    def test_relative(self):
+        t = datetime.now(utc) - timedelta(days=1)
+        label = '%s ago' % pretty_timedelta(t)
+        self.assertEqual(label, self._format_chrome(t, 'relative', False))
+        self.assertEqual(label, self._format_timeline(t, 'relative', False))
+
+    def test_relative_dateonly(self):
+        t = datetime.now(utc) - timedelta(days=1)
+        label = pretty_timedelta(t)
+        self.assertEqual(label, self._format_chrome(t, 'relative', True))
+        self.assertEqual(label, self._format_timeline(t, 'relative', True))
+
+    def test_absolute(self):
+        t = datetime.now(utc) - timedelta(days=1)
+        label = 'on %s at %s' % \
+                (format_date(t, locale=locale_en, tzinfo=utc),
+                 format_time(t, locale=locale_en, tzinfo=utc))
+        self.assertEqual(label, self._format_chrome(t, 'absolute', False))
+        self.assertEqual(label, self._format_timeline(t, 'absolute', False))
+
+    def test_absolute_dateonly(self):
+        t = datetime.now(utc) - timedelta(days=1)
+        label = format_datetime(t, locale=locale_en, tzinfo=utc)
+        self.assertEqual(label, self._format_chrome(t, 'absolute', True))
+        self.assertEqual(label, self._format_timeline(t, 'absolute', True))
+
+    def test_absolute_iso8601(self):
+        t = datetime(2014, 1, 28, 2, 30, 44, 0, utc)
+        label = 'at 2014-01-28T02:30:44Z'
+        self.req.lc_time = 'iso8601'
+        self.assertEqual(label, self._format_chrome(t, 'absolute', False))
+        self.assertEqual(label, self._format_timeline(t, 'absolute', False))
+
+    def test_absolute_iso8601_dateonly(self):
+        t = datetime(2014, 1, 28, 2, 30, 44, 0, utc)
+        label = '2014-01-28T02:30:44Z'
+        self.req.lc_time = 'iso8601'
+        self.assertEqual(label, self._format_chrome(t, 'absolute', True))
+        self.assertEqual(label, self._format_timeline(t, 'absolute', True))
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(PrettyDateinfoTestCase))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')

Added: bloodhound/vendor/trac/current/trac/timeline/tests/wikisyntax.py
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/timeline/tests/wikisyntax.py?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/timeline/tests/wikisyntax.py (added)
+++ bloodhound/vendor/trac/current/trac/timeline/tests/wikisyntax.py Fri Nov 14 
12:10:40 2014
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+import time
+import unittest
+
+from trac.timeline.web_ui import TimelineModule
+from trac.wiki.tests import formatter
+
+TIMELINE_TEST_CASES = u"""
+============================== timeline: link resolver
+timeline:2008-01-29
+timeline:2008-01-29T15:48
+timeline:2008-01-29T15:48Z
+timeline:2008-01-29T16:48+01
+timeline:2008-01-0A
+timeline:@datestr_libc@
+------------------------------
+<p>
+<a class="timeline" href="/timeline?from=2008-01-29T00%3A00%3A00Z" title="See 
timeline at 2008-01-29T00:00:00Z">timeline:2008-01-29</a>
+<a class="timeline" 
href="/timeline?from=2008-01-29T15%3A48%3A00Z&amp;precision=minutes" title="See 
timeline at 2008-01-29T15:48:00Z">timeline:2008-01-29T15:48</a>
+<a class="timeline" 
href="/timeline?from=2008-01-29T15%3A48%3A00Z&amp;precision=minutes" title="See 
timeline at 2008-01-29T15:48:00Z">timeline:2008-01-29T15:48Z</a>
+<a class="timeline" 
href="/timeline?from=2008-01-29T15%3A48%3A00Z&amp;precision=seconds" title="See 
timeline at 2008-01-29T15:48:00Z">timeline:2008-01-29T16:48+01</a>
+<a class="timeline missing" title="&#34;2008-01-0A&#34; is an invalid date, or 
the date format is not known. Try &#34;YYYY-MM-DDThh:mm:ss±hh:mm&#34; 
instead.">timeline:2008-01-0A</a>
+<a class="timeline missing" title="&#34;@datestr_libc@&#34; is an invalid 
date, or the date format is not known. Try &#34;YYYY-MM-DDThh:mm:ss±hh:mm&#34; 
instead.">timeline:@datestr_libc@</a>
+</p>
+------------------------------
+"""
+
+
+def suite():
+    suite = unittest.TestSuite()
+    datestr_libc = time.strftime('%x', (2013, 10, 24, 0, 0, 0, 0, 0, -1))
+    suite.addTest(formatter.suite(TIMELINE_TEST_CASES.replace('@datestr_libc@',
+                                                              datestr_libc),
+                                  file=__file__))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')

Added: bloodhound/vendor/trac/current/trac/util/tests/translation.py
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/util/tests/translation.py?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/trac/util/tests/translation.py (added)
+++ bloodhound/vendor/trac/current/trac/util/tests/translation.py Fri Nov 14 
12:10:40 2014
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+import shutil
+import tempfile
+import unittest
+from pkg_resources import resource_exists, resource_filename
+try:
+    import babel
+except ImportError:
+    babel = None
+    locale_identifiers = lambda: ()
+else:
+    try:
+        from babel.localedata import locale_identifiers
+    except ImportError:
+        from babel.localedata import list as locale_identifiers
+
+from trac.test import EnvironmentStub
+from trac.util import translation
+
+
+class TranslationsProxyTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.env = EnvironmentStub()
+        self.env.path = tempfile.mkdtemp(prefix='trac-tempenv-')
+
+    def tearDown(self):
+        translation.deactivate()
+        self.env.reset_db()
+        shutil.rmtree(self.env.path)
+
+    def _get_locale_dir(self):
+        return resource_filename('trac', 'locale')
+
+    def _get_available_locales(self):
+        return sorted(locale
+                      for locale in translation.get_available_locales()
+                      if resource_exists('trac',
+                                         'locale/%s/LC_MESSAGES/messages.mo'
+                                         % locale))
+
+    def test_activate(self):
+        locales = self._get_available_locales()
+        if locales:
+            translation.activate(locales[0], self.env.path)
+
+    def test_activate_unavailable_locale(self):
+        unavailables = sorted(set(locale_identifiers()) -
+                              set(translation.get_available_locales())) or \
+                       ('en_US',)
+        locale_dir = self._get_locale_dir()
+        translation.add_domain('catalog1', self.env.path, locale_dir)
+        translation.add_domain('catalog2', self.env.path, locale_dir)
+        translation.activate(unavailables[0], self.env.path)
+
+    def test_activate_with_non_existent_catalogs(self):
+        locales = self._get_available_locales()
+        if locales:
+            locale_dir = self._get_locale_dir()
+            translation.add_domain('catalog1', self.env.path, locale_dir)
+            translation.add_domain('catalog2', self.env.path, locale_dir)
+            translation.activate(locales[0], self.env.path)
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TranslationsProxyTestCase))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')

Added: bloodhound/vendor/trac/current/tracopt/ticket/tests/__init__.py
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/ticket/tests/__init__.py?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/tracopt/ticket/tests/__init__.py (added)
+++ bloodhound/vendor/trac/current/tracopt/ticket/tests/__init__.py Fri Nov 14 
12:10:40 2014
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+import unittest
+
+from tracopt.ticket.tests import commit_updater
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(commit_updater.suite())
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')

Added: bloodhound/vendor/trac/current/tracopt/ticket/tests/commit_updater.py
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/ticket/tests/commit_updater.py?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/tracopt/ticket/tests/commit_updater.py 
(added)
+++ bloodhound/vendor/trac/current/tracopt/ticket/tests/commit_updater.py Fri 
Nov 14 12:10:40 2014
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+import unittest
+from datetime import datetime
+
+from trac.test import EnvironmentStub, Mock
+from trac.tests.contentgen import random_sentence
+from trac.ticket.model import Ticket
+from trac.util.datefmt import utc
+from trac.versioncontrol.api import Repository
+from tracopt.ticket.commit_updater import CommitTicketUpdater
+
+
+class CommitTicketUpdaterTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.env = EnvironmentStub(enable=['trac.*',
+                                           'tracopt.ticket.commit_updater.*'])
+        self.env.config.set('ticket', 'commit_ticket_update_check_perms', 
False)
+        self.repos = Mock(Repository, 'repos1', {'name': 'repos1', 'id': 1},
+                          self.env.log)
+        self.updater = CommitTicketUpdater(self.env)
+
+    def tearDown(self):
+        self.env.reset_db()
+
+    def _make_tickets(self, num):
+        self.tickets = []
+        for i in xrange(0, num):
+            ticket = Ticket(self.env)
+            ticket['reporter'] = 'someone'
+            ticket['summary'] = random_sentence()
+            ticket.insert()
+            self.tickets.append(ticket)
+
+    def test_changeset_added(self):
+        self._make_tickets(1)
+        message = 'This is the first comment. Refs #1.'
+        chgset = Mock(repos=self.repos, rev=1, message=message, author='joe',
+                      date=datetime(2001, 1, 1, 1, 1, 1, 0, utc))
+        self.updater.changeset_added(self.repos, chgset)
+        self.assertEqual("""\
+In [changeset:"1/repos1"]:
+{{{
+#!CommitTicketReference repository="repos1" revision="1"
+This is the first comment. Refs #1.
+}}}""", self.tickets[0].get_change(cnum=1)['fields']['comment']['new'])
+
+    def test_changeset_modified(self):
+        self._make_tickets(2)
+        message = 'This is the first comment. Refs #1.'
+        old_chgset = Mock(repos=self.repos, rev=1,
+                          message=message, author='joe',
+                          date=datetime(2001, 1, 1, 1, 1, 1, 0, utc))
+        message = 'This is the first comment after an edit. Refs #1, #2.'
+        new_chgset = Mock(repos=self.repos, rev=1,
+                          message=message, author='joe',
+                          date=datetime(2001, 1, 2, 1, 1, 1, 0, utc))
+        self.updater.changeset_added(self.repos, old_chgset)
+        self.updater.changeset_modified(self.repos, new_chgset, old_chgset)
+        self.assertEqual("""\
+In [changeset:"1/repos1"]:
+{{{
+#!CommitTicketReference repository="repos1" revision="1"
+This is the first comment. Refs #1.
+}}}""", self.tickets[0].get_change(cnum=1)['fields']['comment']['new'])
+        self.assertEqual("""\
+In [changeset:"1/repos1"]:
+{{{
+#!CommitTicketReference repository="repos1" revision="1"
+This is the first comment after an edit. Refs #1, #2.
+}}}""", self.tickets[1].get_change(cnum=1)['fields']['comment']['new'])
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(CommitTicketUpdaterTestCase))
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')

Added: bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/git_fs.py
URL: 
http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/git_fs.py?rev=1639617&view=auto
==============================================================================
--- bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/git_fs.py 
(added)
+++ bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/git_fs.py 
Fri Nov 14 12:10:40 2014
@@ -0,0 +1,521 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2014 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+import os
+import tempfile
+import unittest
+from datetime import datetime, timedelta
+from subprocess import Popen, PIPE
+
+from trac.core import TracError
+from trac.test import EnvironmentStub, Mock, MockPerm, locate
+from trac.tests.compat import rmtree
+from trac.util import create_file
+from trac.util.compat import close_fds
+from trac.util.datefmt import to_timestamp, utc
+from trac.versioncontrol.api import Changeset, DbRepositoryProvider, \
+                                    NoSuchChangeset, NoSuchNode, \
+                                    RepositoryManager
+from trac.versioncontrol.web_ui.browser import BrowserModule
+from trac.versioncontrol.web_ui.log import LogModule
+from trac.web.href import Href
+from tracopt.versioncontrol.git.PyGIT import StorageFactory
+from tracopt.versioncontrol.git.git_fs import GitCachedRepository, \
+                                              GitConnector, GitRepository
+
+
+git_bin = None
+
+
+class GitCommandMixin(object):
+
+    def _git_commit(self, *args, **kwargs):
+        env = kwargs.get('env') or os.environ.copy()
+        if 'date' in kwargs:
+            self._set_committer_date(env, kwargs.pop('date'))
+        args = ('commit',) + args
+        kwargs['env'] = env
+        return self._git(*args, **kwargs)
+
+    def _git(self, *args, **kwargs):
+        args = (git_bin,) + args
+        proc = Popen(args, stdout=PIPE, stderr=PIPE, close_fds=close_fds,
+                     cwd=self.repos_path, **kwargs)
+        stdout, stderr = proc.communicate()
+        self.assertEqual(0, proc.returncode,
+                         'git exits with %r, args %r, stdout %r, stderr %r' %
+                         (proc.returncode, args, stdout, stderr))
+        return proc
+
+    def _git_date_format(self, dt):
+        if dt.tzinfo is None:
+            dt = dt.replace(tzinfo=utc)
+        offset = dt.utcoffset()
+        secs = offset.days * 3600 * 24 + offset.seconds
+        hours, rem = divmod(abs(secs), 3600)
+        return '%d %c%02d:%02d' % (to_timestamp(dt), '-' if secs < 0 else '+',
+                                   hours, rem / 60)
+
+    def _set_committer_date(self, env, dt):
+        if not isinstance(dt, basestring):
+            if dt.tzinfo is None:
+                dt = dt.replace(tzinfo=utc)
+            dt = self._git_date_format(dt)
+        env['GIT_COMMITTER_DATE'] = dt
+        env['GIT_AUTHOR_DATE'] = dt
+
+
+class BaseTestCase(unittest.TestCase, GitCommandMixin):
+
+    def setUp(self):
+        self.env = EnvironmentStub()
+        self.repos_path = tempfile.mkdtemp(prefix='trac-gitrepos-')
+        if git_bin:
+            self.env.config.set('git', 'git_bin', git_bin)
+
+    def tearDown(self):
+        self._repomgr.reload_repositories()
+        StorageFactory._clean()
+        self.env.reset_db()
+        if os.path.isdir(self.repos_path):
+            rmtree(self.repos_path)
+
+    @property
+    def _repomgr(self):
+        return RepositoryManager(self.env)
+
+    @property
+    def _dbrepoprov(self):
+        return DbRepositoryProvider(self.env)
+
+    def _add_repository(self, reponame='gitrepos', bare=False):
+        path = self.repos_path \
+               if bare else os.path.join(self.repos_path, '.git')
+        self._dbrepoprov.add_repository(reponame, path, 'git')
+
+    def _git_init(self, data=True, bare=False):
+        if bare:
+            self._git('init', '--bare')
+        else:
+            self._git('init')
+        if not bare and data:
+            self._git('config', 'user.name', 'Joe')
+            self._git('config', 'user.email', 'j...@example.com')
+            create_file(os.path.join(self.repos_path, '.gitignore'))
+            self._git('add', '.gitignore')
+            self._git_commit('-a', '-m', 'test',
+                             date=datetime(2001, 1, 29, 16, 39, 56))
+
+
+class SanityCheckingTestCase(BaseTestCase):
+
+    def test_bare(self):
+        self._git_init(bare=True)
+        self._dbrepoprov.add_repository('gitrepos', self.repos_path, 'git')
+        self._repomgr.get_repository('gitrepos')
+
+    def test_non_bare(self):
+        self._git_init(bare=False)
+        self._dbrepoprov.add_repository('gitrepos.1',
+                                        os.path.join(self.repos_path, '.git'),
+                                        'git')
+        self._repomgr.get_repository('gitrepos.1')
+        self._dbrepoprov.add_repository('gitrepos.2', self.repos_path, 'git')
+        self._repomgr.get_repository('gitrepos.2')
+
+    def test_no_head_file(self):
+        self._git_init(bare=True)
+        os.unlink(os.path.join(self.repos_path, 'HEAD'))
+        self._dbrepoprov.add_repository('gitrepos', self.repos_path, 'git')
+        self.assertRaises(TracError, self._repomgr.get_repository, 'gitrepos')
+
+    def test_no_objects_dir(self):
+        self._git_init(bare=True)
+        rmtree(os.path.join(self.repos_path, 'objects'))
+        self._dbrepoprov.add_repository('gitrepos', self.repos_path, 'git')
+        self.assertRaises(TracError, self._repomgr.get_repository, 'gitrepos')
+
+    def test_no_refs_dir(self):
+        self._git_init(bare=True)
+        rmtree(os.path.join(self.repos_path, 'refs'))
+        self._dbrepoprov.add_repository('gitrepos', self.repos_path, 'git')
+        self.assertRaises(TracError, self._repomgr.get_repository, 'gitrepos')
+
+
+class PersistentCacheTestCase(BaseTestCase):
+
+    def test_persistent(self):
+        self.env.config.set('git', 'persistent_cache', 'enabled')
+        self._git_init()
+        self._add_repository()
+        youngest = self._repository.youngest_rev
+        self._repomgr.reload_repositories()  # clear repository cache
+
+        self._commit(datetime(2014, 1, 29, 16, 44, 54, 0, utc))
+        self.assertEqual(youngest, self._repository.youngest_rev)
+        self._repository.sync()
+        self.assertNotEqual(youngest, self._repository.youngest_rev)
+
+    def test_non_persistent(self):
+        self.env.config.set('git', 'persistent_cache', 'disabled')
+        self._git_init()
+        self._add_repository()
+        youngest = self._repository.youngest_rev
+        self._repomgr.reload_repositories()  # clear repository cache
+
+        self._commit(datetime(2014, 1, 29, 16, 44, 54, 0, utc))
+        youngest_2 = self._repository.youngest_rev
+        self.assertNotEqual(youngest, youngest_2)
+        self._repository.sync()
+        self.assertNotEqual(youngest, self._repository.youngest_rev)
+        self.assertEqual(youngest_2, self._repository.youngest_rev)
+
+    def _commit(self, date):
+        gitignore = os.path.join(self.repos_path, '.gitignore')
+        create_file(gitignore, date.isoformat())
+        self._git_commit('-a', '-m', date.isoformat(), date=date)
+
+    @property
+    def _repository(self):
+        return self._repomgr.get_repository('gitrepos')
+
+
+class HistoryTimeRangeTestCase(BaseTestCase):
+
+    def test_without_cache(self):
+        self._test_timerange('disabled')
+
+    def test_with_cache(self):
+        self._test_timerange('enabled')
+
+    def _test_timerange(self, cached_repository):
+        self.env.config.set('git', 'cached_repository', cached_repository)
+
+        self._git_init()
+        filename = os.path.join(self.repos_path, '.gitignore')
+        start = datetime(2000, 1, 1, 0, 0, 0, 0, utc)
+        ts = datetime(2014, 2, 5, 15, 24, 6, 0, utc)
+        for idx in xrange(3):
+            create_file(filename, 'commit-%d.txt' % idx)
+            self._git_commit('-a', '-m', 'commit %d' % idx, date=ts)
+        self._add_repository()
+        repos = self._repomgr.get_repository('gitrepos')
+        repos.sync()
+
+        revs = [repos.youngest_rev]
+        while True:
+            parents = repos.parent_revs(revs[-1])
+            if not parents:
+                break
+            revs.extend(parents)
+        self.assertEqual(4, len(revs))
+
+        csets = list(repos.get_changesets(start, ts))
+        self.assertEqual(1, len(csets))
+        self.assertEqual(revs[-1], csets[0].rev)  # is oldest rev
+
+        csets = list(repos.get_changesets(start, ts + timedelta(seconds=1)))
+        self.assertEqual(revs, [cset.rev for cset in csets])
+
+
+class GitNormalTestCase(BaseTestCase):
+
+    def _create_req(self, **kwargs):
+        data = dict(args={}, perm=MockPerm(), href=Href('/'), chrome={},
+                    authname='trac', tz=utc, get_header=lambda name: None)
+        data.update(kwargs)
+        return Mock(**data)
+
+    def test_get_node(self):
+        self.env.config.set('git', 'persistent_cache', 'false')
+        self.env.config.set('git', 'cached_repository', 'false')
+
+        self._git_init()
+        self._add_repository()
+        repos = self._repomgr.get_repository('gitrepos')
+        rev = repos.youngest_rev
+        self.assertNotEqual(None, rev)
+        self.assertEqual(40, len(rev))
+
+        self.assertEqual(rev, repos.get_node('/').rev)
+        self.assertEqual(rev, repos.get_node('/', rev[:7]).rev)
+        self.assertEqual(rev, repos.get_node('/.gitignore').rev)
+        self.assertEqual(rev, repos.get_node('/.gitignore', rev[:7]).rev)
+
+        self.assertRaises(NoSuchNode, repos.get_node, '/non-existent')
+        self.assertRaises(NoSuchNode, repos.get_node, '/non-existent', rev[:7])
+        self.assertRaises(NoSuchNode, repos.get_node, '/non-existent', rev)
+        self.assertRaises(NoSuchChangeset,
+                          repos.get_node, '/', 'invalid-revision')
+        self.assertRaises(NoSuchChangeset,
+                          repos.get_node, '/.gitignore', 'invalid-revision')
+        self.assertRaises(NoSuchChangeset,
+                          repos.get_node, '/non-existent', 'invalid-revision')
+
+        # git_fs doesn't support non-ANSI strings on Windows
+        if os.name != 'nt':
+            self._git('branch', u'tïckét10605', 'master')
+            repos.sync()
+            self.assertEqual(rev, repos.get_node('/', u'tïckét10605').rev)
+            self.assertEqual(rev, repos.get_node('/.gitignore',
+                                                 u'tïckét10605').rev)
+
+    def _test_on_empty_repos(self, cached_repository):
+        self.env.config.set('git', 'persistent_cache', 'false')
+        self.env.config.set('git', 'cached_repository', cached_repository)
+
+        self._git_init(data=False, bare=True)
+        self._add_repository(bare=True)
+        repos = self._repomgr.get_repository('gitrepos')
+        repos.sync()
+        youngest_rev = repos.youngest_rev
+        self.assertEqual(None, youngest_rev)
+        self.assertEqual(None, repos.oldest_rev)
+        self.assertEqual(None, repos.normalize_rev(''))
+        self.assertEqual(None, repos.normalize_rev(None))
+
+        node = repos.get_node('/', youngest_rev)
+        self.assertEqual([], list(node.get_entries()))
+        self.assertEqual([], list(node.get_history()))
+        self.assertRaises(NoSuchNode, repos.get_node, '/path', youngest_rev)
+
+        req = self._create_req(path_info='/browser/gitrepos')
+        browser_mod = BrowserModule(self.env)
+        self.assertTrue(browser_mod.match_request(req))
+        rv = browser_mod.process_request(req)
+        self.assertEqual('browser.html', rv[0])
+        self.assertEqual(None, rv[1]['rev'])
+
+        req = self._create_req(path_info='/log/gitrepos')
+        log_mod = LogModule(self.env)
+        self.assertTrue(log_mod.match_request(req))
+        rv = log_mod.process_request(req)
+        self.assertEqual('revisionlog.html', rv[0])
+        self.assertEqual([], rv[1]['items'])
+
+    def test_on_empty_and_cached_repos(self):
+        self._test_on_empty_repos('true')
+
+    def test_on_empty_and_non_cached_repos(self):
+        self._test_on_empty_repos('false')
+
+
+class GitRepositoryTestCase(BaseTestCase):
+
+    cached_repository = 'disabled'
+
+    def setUp(self):
+        BaseTestCase.setUp(self)
+        self.env.config.set('git', 'cached_repository', self.cached_repository)
+
+    def _create_merge_commit(self):
+        for idx, branch in enumerate(('alpha', 'beta')):
+            self._git('checkout', '-b', branch, 'master')
+            for n in xrange(2):
+                filename = 'file-%s-%d.txt' % (branch, n)
+                create_file(os.path.join(self.repos_path, filename))
+                self._git('add', filename)
+                self._git_commit('-a', '-m', filename,
+                                 date=datetime(2014, 2, 2, 17, 12,
+                                               n * 2 + idx))
+        self._git('checkout', 'alpha')
+        self._git('merge', '-m', 'Merge branch "beta" to "alpha"', 'beta')
+
+    def test_repository_instance(self):
+        self._git_init()
+        self._add_repository('gitrepos')
+        self.assertEqual(GitRepository,
+                         type(self._repomgr.get_repository('gitrepos')))
+
+    def test_reset_head(self):
+        self._git_init()
+        create_file(os.path.join(self.repos_path, 'file.txt'), 'text')
+        self._git('add', 'file.txt')
+        self._git_commit('-a', '-m', 'test',
+                         date=datetime(2014, 2, 2, 17, 12, 18))
+        self._add_repository('gitrepos')
+        repos = self._repomgr.get_repository('gitrepos')
+        repos.sync()
+        youngest_rev = repos.youngest_rev
+        entries = list(repos.get_node('').get_history())
+        self.assertEqual(2, len(entries))
+        self.assertEqual('', entries[0][0])
+        self.assertEqual(Changeset.EDIT, entries[0][2])
+        self.assertEqual('', entries[1][0])
+        self.assertEqual(Changeset.ADD, entries[1][2])
+
+        self._git('reset', '--hard', 'HEAD~')
+        repos.sync()
+        new_entries = list(repos.get_node('').get_history())
+        self.assertEqual(1, len(new_entries))
+        self.assertEqual(new_entries[0], entries[1])
+        self.assertNotEqual(youngest_rev, repos.youngest_rev)
+
+    def test_tags(self):
+        self._git_init()
+        self._add_repository('gitrepos')
+        repos = self._repomgr.get_repository('gitrepos')
+        repos.sync()
+        self.assertEqual(['master'], self._get_quickjump_names(repos))
+        self._git('tag', 'v1.0', 'master')  # add tag
+        repos.sync()
+        self.assertEqual(['master', 'v1.0'], self._get_quickjump_names(repos))
+        self._git('tag', '-d', 'v1.0')  # delete tag
+        repos.sync()
+        self.assertEqual(['master'], self._get_quickjump_names(repos))
+
+    def test_branchs(self):
+        self._git_init()
+        self._add_repository('gitrepos')
+        repos = self._repomgr.get_repository('gitrepos')
+        repos.sync()
+        self.assertEqual(['master'], self._get_quickjump_names(repos))
+        self._git('branch', 'alpha', 'master')  # add branch
+        repos.sync()
+        self.assertEqual(['alpha', 'master'], self._get_quickjump_names(repos))
+        self._git('branch', '-m', 'alpha', 'beta')  # rename branch
+        repos.sync()
+        self.assertEqual(['beta', 'master'], self._get_quickjump_names(repos))
+        self._git('branch', '-D', 'beta')  # delete branch
+        repos.sync()
+        self.assertEqual(['master'], self._get_quickjump_names(repos))
+
+    def test_parent_child_revs(self):
+        self._git_init()
+        self._git('branch', 'initial')
+        self._create_merge_commit()
+        self._git('branch', 'latest')
+
+        self._add_repository('gitrepos')
+        repos = self._repomgr.get_repository('gitrepos')
+        repos.sync()
+
+        rev = repos.normalize_rev('initial')
+        children = repos.child_revs(rev)
+        self.assertEqual(2, len(children), 'child_revs: %r' % children)
+        parents = repos.parent_revs(rev)
+        self.assertEqual(0, len(parents), 'parent_revs: %r' % parents)
+        self.assertEqual(1, len(repos.child_revs(children[0])))
+        self.assertEqual(1, len(repos.child_revs(children[1])))
+
+        rev = repos.normalize_rev('latest')
+        children = repos.child_revs(rev)
+        self.assertEqual(0, len(children), 'child_revs: %r' % children)
+        parents = repos.parent_revs(rev)
+        self.assertEqual(2, len(parents), 'parent_revs: %r' % parents)
+        self.assertEqual(1, len(repos.parent_revs(parents[0])))
+        self.assertEqual(1, len(repos.parent_revs(parents[1])))
+
+    def _get_quickjump_names(self, repos):
+        return sorted(name for type, name, path, rev
+                           in repos.get_quickjump_entries('HEAD'))
+
+
+class GitCachedRepositoryTestCase(GitRepositoryTestCase):
+
+    cached_repository = 'enabled'
+
+    def test_repository_instance(self):
+        self._git_init()
+        self._add_repository('gitrepos')
+        self.assertEqual(GitCachedRepository,
+                         type(self._repomgr.get_repository('gitrepos')))
+
+    def test_sync(self):
+        self._git_init()
+        for idx in xrange(3):
+            filename = 'file%d.txt' % idx
+            create_file(os.path.join(self.repos_path, filename))
+            self._git('add', filename)
+            self._git_commit('-a', '-m', filename,
+                             date=datetime(2014, 2, 2, 17, 12, idx))
+        self._add_repository('gitrepos')
+        repos = self._repomgr.get_repository('gitrepos')
+        revs = [entry[1] for entry in repos.repos.get_node('').get_history()]
+        revs.reverse()
+        revs2 = []
+        def feedback(rev):
+            revs2.append(rev)
+        repos.sync(feedback=feedback)
+        self.assertEqual(revs, revs2)
+        self.assertEqual(4, len(revs2))
+
+        revs2 = []
+        def feedback_1(rev):
+            revs2.append(rev)
+            if len(revs2) == 2:
+                raise StopSync
+        def feedback_2(rev):
+            revs2.append(rev)
+        try:
+            repos.sync(feedback=feedback_1, clean=True)
+        except StopSync:
+            self.assertEqual(revs[:2], revs2)
+            repos.sync(feedback=feedback_2)  # restart sync
+        self.assertEqual(revs, revs2)
+
+    def test_sync_merge(self):
+        self._git_init()
+        self._create_merge_commit()
+
+        self._add_repository('gitrepos')
+        repos = self._repomgr.get_repository('gitrepos')
+        youngest_rev = repos.repos.youngest_rev
+        oldest_rev = repos.repos.oldest_rev
+
+        revs = []
+        def feedback(rev):
+            revs.append(rev)
+        repos.sync(feedback=feedback)
+        self.assertEqual(6, len(revs))
+        self.assertEqual(youngest_rev, revs[-1])
+        self.assertEqual(oldest_rev, revs[0])
+
+        revs2 = []
+        def feedback_1(rev):
+            revs2.append(rev)
+            if len(revs2) == 3:
+                raise StopSync
+        def feedback_2(rev):
+            revs2.append(rev)
+        try:
+            repos.sync(feedback=feedback_1, clean=True)
+        except StopSync:
+            self.assertEqual(revs[:3], revs2)
+            repos.sync(feedback=feedback_2)  # restart sync
+        self.assertEqual(revs, revs2)
+
+
+class StopSync(Exception):
+    pass
+
+
+def suite():
+    global git_bin
+    suite = unittest.TestSuite()
+    git_bin = locate('git')
+    if git_bin:
+        suite.addTest(unittest.makeSuite(SanityCheckingTestCase))
+        suite.addTest(unittest.makeSuite(PersistentCacheTestCase))
+        suite.addTest(unittest.makeSuite(HistoryTimeRangeTestCase))
+        suite.addTest(unittest.makeSuite(GitNormalTestCase))
+        suite.addTest(unittest.makeSuite(GitRepositoryTestCase))
+        suite.addTest(unittest.makeSuite(GitCachedRepositoryTestCase))
+    else:
+        print("SKIP: tracopt/versioncontrol/git/tests/git_fs.py (git cli "
+              "binary, 'git', not found)")
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')


Reply via email to