Merge "Implementation of manifest defined githooks"
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 1aa9396..4b979c7 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -31,7 +31,7 @@
<!ELEMENT notice (#PCDATA)>
- <!ELEMENT remote (EMPTY)>
+ <!ELEMENT remote (projecthook?)>
<!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED>
@@ -73,6 +73,10 @@
<!ATTLIST extend-project path CDATA #IMPLIED>
<!ATTLIST extend-project groups CDATA #IMPLIED>
+ <!ELEMENT projecthook (EMPTY)>
+ <!ATTLIST projecthook name CDATA #REQUIRED>
+ <!ATTLIST projecthook revision CDATA #REQUIRED>
+
<!ELEMENT remove-project (EMPTY)>
<!ATTLIST remove-project name CDATA #REQUIRED>
@@ -306,6 +310,15 @@
Attribute `name`: the manifest to include, specified relative to
the manifest repository's root.
+Element projecthook
+-------------------
+
+This element is used to define a per-remote hook git that is
+fetched and applied to all projects using the remote. The project-
+hook functionality allows for company/team .git/hooks to be used.
+The hooks in the supplied project and revision are supplemented to
+the current repo stock hooks for each project. Supplemented hooks
+overrule any stock hooks.
Local Manifests
===============
diff --git a/manifest_xml.py b/manifest_xml.py
index 890c954..9472a08 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -64,7 +64,9 @@
fetch=None,
manifestUrl=None,
review=None,
- revision=None):
+ revision=None,
+ projecthookName=None,
+ projecthookRevision=None):
self.name = name
self.fetchUrl = fetch
self.manifestUrl = manifestUrl
@@ -72,6 +74,8 @@
self.reviewUrl = review
self.revision = revision
self.resolvedFetchUrl = self._resolveFetchUrl()
+ self.projecthookName = projecthookName
+ self.projecthookRevision = projecthookRevision
def __eq__(self, other):
return self.__dict__ == other.__dict__
@@ -167,6 +171,11 @@
e.setAttribute('review', r.reviewUrl)
if r.revision is not None:
e.setAttribute('revision', r.revision)
+ if r.projecthookName is not None:
+ ph = doc.createElement('projecthook')
+ ph.setAttribute('name', r.projecthookName)
+ ph.setAttribute('revision', r.projecthookRevision)
+ e.appendChild(ph)
def _ParseGroups(self, groups):
return [x for x in re.split(r'[,\s]+', groups) if x]
@@ -629,7 +638,13 @@
if revision == '':
revision = None
manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
- return _XmlRemote(name, alias, fetch, manifestUrl, review, revision)
+ projecthookName = None
+ projecthookRevision = None
+ for n in node.childNodes:
+ if n.nodeName == 'projecthook':
+ projecthookName, projecthookRevision = self._ParseProjectHooks(n)
+ break
+ return _XmlRemote(name, alias, fetch, manifestUrl, review, revision, projecthookName, projecthookRevision)
def _ParseDefault(self, node):
"""
@@ -933,3 +948,8 @@
diff['added'].append(toProjects[proj])
return diff
+
+ def _ParseProjectHooks(self, node):
+ name = self._reqatt(node, 'name')
+ revision = self._reqatt(node, 'revision')
+ return name, revision
diff --git a/project.py b/project.py
index 5f47899..68d7421 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
@@ -2106,7 +2085,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.
@@ -2148,11 +2127,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()
@@ -2161,7 +2167,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 \
diff --git a/subcmds/init.py b/subcmds/init.py
index b73de71..c5bf282 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -32,7 +32,7 @@
from color import Coloring
from command import InteractiveCommand, MirrorSafeCommand
from error import ManifestParseError
-from project import SyncBuffer
+from project import SyncBuffer, MetaProject
from git_config import GitConfig
from git_command import git_require, MIN_GIT_VERSION
@@ -374,6 +374,52 @@
print(' rm -r %s/.repo' % self.manifest.topdir)
print('and try again.')
+ def _SyncProjectHooks(self, opt, repodir):
+ """Downloads the defined hooks supplied in the projecthooks element
+
+ """
+ # Always delete projecthooks and re-download for every new init.
+ projecthooksdir = os.path.join(repodir, 'projecthooks')
+ if os.path.exists(projecthooksdir):
+ shutil.rmtree(projecthooksdir)
+ for remotename in self.manifest.remotes:
+ r = self.manifest.remotes.get(remotename)
+ if r.projecthookName is not None and r.projecthookRevision is not None:
+ projecthookurl = r.resolvedFetchUrl.rstrip('/') + '/' + r.projecthookName
+
+ ph = MetaProject(manifest = self.manifest,
+ name = r.projecthookName,
+ gitdir = os.path.join(projecthooksdir,'%s/%s.git' % (remotename, r.projecthookName)),
+ worktree = os.path.join(projecthooksdir,'%s/%s' % (remotename, r.projecthookName)))
+
+ ph.revisionExpr = r.projecthookRevision
+ is_new = not ph.Exists
+
+ if is_new:
+ if not opt.quiet:
+ print('Get projecthook %s' % \
+ GitConfig.ForUser().UrlInsteadOf(projecthookurl), file=sys.stderr)
+ ph._InitGitDir(MirrorOverride=True)
+
+ phr = ph.GetRemote(remotename)
+ phr.name = 'origin'
+ phr.url = projecthookurl
+ phr.ResetFetch()
+ phr.Save()
+
+ if not ph.Sync_NetworkHalf(quiet=opt.quiet, is_new=is_new, clone_bundle=False):
+ print('fatal: cannot obtain projecthook %s' % phr.url, file=sys.stderr)
+
+ # Better delete the git dir if we created it; otherwise next
+ # time (when user fixes problems) we won't go through the "is_new" logic.
+ if is_new:
+ shutil.rmtree(ph.gitdir)
+ sys.exit(1)
+
+ syncbuf = SyncBuffer(ph.config)
+ ph.Sync_LocalHalf(syncbuf)
+ syncbuf.Finish()
+
def Execute(self, opt, args):
git_require(MIN_GIT_VERSION, fail=True)
@@ -389,6 +435,7 @@
self._SyncManifest(opt)
self._LinkManifest(opt.manifest_name)
+ self._SyncProjectHooks(opt, self.manifest.repodir)
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
if opt.config_name or self._ShouldConfigureUser():