mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-01-02 16:14:25 +00:00
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>
This commit is contained in:
parent
ee69084421
commit
38e4387f8e
@ -31,7 +31,7 @@ following DTD:
|
||||
|
||||
<!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 @@ following DTD:
|
||||
<!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 @@ 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
|
||||
===============
|
||||
|
@ -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
|
||||
|
57
project.py
57
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 \
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user