Implementation of manifest defined githooks

When working within a team or corporation it is often
useful/required to use predefined git templates. This
change teaches repo to use a per-remote git hook template
structure.

The implementation is done as a continuation of the
existing projecthook functionality. The terminology is
therefore defined as projecthooks.

The downloaded projecthooks are stored in the .repo
directory as a metaproject separating them from the users
project forest.

The projecthooks are downloaded and set up when doing a
repo init and updated for each new repo init.

When downloading a mirror the projecthooks gits are
not added to the bare forest since the intention is to
ensure that the latest are used (allows for company policy
enforcement).

The projecthooks are defined in the manifest file in the
remote element as a subnode, the name refers to the
project name on the server referred to in the remote.
<remote name="myremote ..>
   <projecthook name="myprojecthookgit" revision="myrevision"/>
</remote>

The hooks found in the projecthook revision supersede
the stock hooks found in repo. This removes the need for
updating the projecthook gits for repo stock hook changes.

Change-Id: I6796b7b0342c1f83c35f4b3e46782581b069a561
Signed-off-by: Patrik Ryd <patrik.ryd@stericsson.com>
Signed-off-by: Ian Kumlien <ian.kumlien@gmail.com>
diff --git a/project.py b/project.py
index 49db02e..68bc7bd 100644
--- a/project.py
+++ b/project.py
@@ -69,27 +69,6 @@
 def sq(r):
   return "'" + r.replace("'", "'\''") + "'"
 
-_project_hook_list = None
-def _ProjectHooks():
-  """List the hooks present in the 'hooks' directory.
-
-  These hooks are project hooks and are copied to the '.git/hooks' directory
-  of all subprojects.
-
-  This function caches the list of hooks (based on the contents of the
-  'repo/hooks' directory) on the first call.
-
-  Returns:
-    A list of absolute paths to all of the files in the hooks directory.
-  """
-  global _project_hook_list
-  if _project_hook_list is None:
-    d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
-    d = os.path.join(d, 'hooks')
-    _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
-  return _project_hook_list
-
-
 class DownloadedChange(object):
   _commit_cache = None
 
@@ -2091,7 +2070,7 @@
     if GitCommand(self, cmd).Wait() != 0:
       raise GitError('%s merge %s ' % (self.name, head))
 
-  def _InitGitDir(self, mirror_git=None):
+  def _InitGitDir(self, mirror_git=None, MirrorOverride=False):
     if not os.path.exists(self.gitdir):
 
       # Initialize the bare repository, which contains all of the objects.
@@ -2133,11 +2112,38 @@
       for key in ['user.name', 'user.email']:
         if m.Has(key, include_defaults=False):
           self.config.SetString(key, m.GetString(key))
-      if self.manifest.IsMirror:
+      if self.manifest.IsMirror and not MirrorOverride:
         self.config.SetString('core.bare', 'true')
       else:
         self.config.SetString('core.bare', None)
 
+  def _ProjectHooks(self, remote, repodir):
+    """List the hooks present in the 'hooks' directory.
+
+    These hooks are project hooks and are copied to the '.git/hooks' directory
+    of all subprojects.
+
+    The remote projecthooks supplement/overrule any stockhook making it possible to
+    have a combination of hooks both from the remote projecthook and
+    .repo/hooks directories.
+
+    Returns:
+      A list of absolute paths to all of the files in the hooks directory and
+      projecthooks files, excluding the .git folder.
+    """
+    hooks = {}
+    d = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'hooks')
+    hooks = dict([(x, os.path.join(d, x)) for x in os.listdir(d)])
+    if remote is not None:
+      if remote.projecthookName is not None:
+        d = os.path.abspath('%s/projecthooks/%s/%s' % (repodir, remote.name, remote.projecthookName))
+        if os.path.isdir(d):
+          hooks.update(dict([(x, os.path.join(d, x)) for x in os.listdir(d)]))
+
+    if hooks.has_key('.git'):
+      del hooks['.git']
+    return hooks.values()
+
   def _UpdateHooks(self):
     if os.path.exists(self.gitdir):
       self._InitHooks()
@@ -2146,7 +2152,10 @@
     hooks = os.path.realpath(self._gitdir_path('hooks'))
     if not os.path.exists(hooks):
       os.makedirs(hooks)
-    for stock_hook in _ProjectHooks():
+    pr = None
+    if self is not self.manifest.manifestProject:
+      pr = self.manifest.remotes.get(self.remote.name)
+    for stock_hook in self._ProjectHooks(pr, self.manifest.repodir):
       name = os.path.basename(stock_hook)
 
       if name in ('commit-msg',) and not self.remote.review \