diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt index 1aa93965..4b979c79 100644 --- a/docs/manifest-format.txt +++ b/docs/manifest-format.txt @@ -31,7 +31,7 @@ following DTD: - + @@ -73,6 +73,10 @@ following DTD: + + + + @@ -306,6 +310,15 @@ target manifest to include - it must be a usable manifest on its own. 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 890c954d..9472a08f 100644 --- a/manifest_xml.py +++ b/manifest_xml.py @@ -64,7 +64,9 @@ class _XmlRemote(object): 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 @@ class _XmlRemote(object): 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 @@ class XmlManifest(object): 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 @@ class XmlManifest(object): 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 @@ class XmlManifest(object): 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 49db02e3..68bc7bd3 100644 --- a/project.py +++ b/project.py @@ -69,27 +69,6 @@ def not_rev(r): 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 @@ class Project(object): 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 @@ class Project(object): 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 @@ class Project(object): 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 b73de71c..c5bf2823 100644 --- a/subcmds/init.py +++ b/subcmds/init.py @@ -32,7 +32,7 @@ else: 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 @@ to update the working directory files. 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 @@ to update the working directory files. 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():