summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJimmie Wester <jimmie.wester@stericsson.com>2012-10-24 14:35:05 +0200
committerDavid Pursehouse <david.pursehouse@sonymobile.com>2015-02-03 16:01:15 +0900
commit38e4387f8eb8cffd6359d726c38a7c524fef07e3 (patch)
tree897b7f640822d21019d740fd45aa564fba6a18f9
parentee6908442102008df57b46271323d9b06d5fdfbf (diff)
downloadgit-repo-38e4387f8eb8cffd6359d726c38a7c524fef07e3.tar.gz
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>
-rw-r--r--docs/manifest-format.txt15
-rw-r--r--manifest_xml.py24
-rw-r--r--project.py57
-rw-r--r--subcmds/init.py49
4 files changed, 117 insertions, 28 deletions
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:
31 31
32 <!ELEMENT notice (#PCDATA)> 32 <!ELEMENT notice (#PCDATA)>
33 33
34 <!ELEMENT remote (EMPTY)> 34 <!ELEMENT remote (projecthook?)>
35 <!ATTLIST remote name ID #REQUIRED> 35 <!ATTLIST remote name ID #REQUIRED>
36 <!ATTLIST remote alias CDATA #IMPLIED> 36 <!ATTLIST remote alias CDATA #IMPLIED>
37 <!ATTLIST remote fetch CDATA #REQUIRED> 37 <!ATTLIST remote fetch CDATA #REQUIRED>
@@ -73,6 +73,10 @@ following DTD:
73 <!ATTLIST extend-project path CDATA #IMPLIED> 73 <!ATTLIST extend-project path CDATA #IMPLIED>
74 <!ATTLIST extend-project groups CDATA #IMPLIED> 74 <!ATTLIST extend-project groups CDATA #IMPLIED>
75 75
76 <!ELEMENT projecthook (EMPTY)>
77 <!ATTLIST projecthook name CDATA #REQUIRED>
78 <!ATTLIST projecthook revision CDATA #REQUIRED>
79
76 <!ELEMENT remove-project (EMPTY)> 80 <!ELEMENT remove-project (EMPTY)>
77 <!ATTLIST remove-project name CDATA #REQUIRED> 81 <!ATTLIST remove-project name CDATA #REQUIRED>
78 82
@@ -306,6 +310,15 @@ target manifest to include - it must be a usable manifest on its own.
306Attribute `name`: the manifest to include, specified relative to 310Attribute `name`: the manifest to include, specified relative to
307the manifest repository's root. 311the manifest repository's root.
308 312
313Element projecthook
314-------------------
315
316This element is used to define a per-remote hook git that is
317fetched and applied to all projects using the remote. The project-
318hook functionality allows for company/team .git/hooks to be used.
319The hooks in the supplied project and revision are supplemented to
320the current repo stock hooks for each project. Supplemented hooks
321overrule any stock hooks.
309 322
310Local Manifests 323Local Manifests
311=============== 324===============
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):
64 fetch=None, 64 fetch=None,
65 manifestUrl=None, 65 manifestUrl=None,
66 review=None, 66 review=None,
67 revision=None): 67 revision=None,
68 projecthookName=None,
69 projecthookRevision=None):
68 self.name = name 70 self.name = name
69 self.fetchUrl = fetch 71 self.fetchUrl = fetch
70 self.manifestUrl = manifestUrl 72 self.manifestUrl = manifestUrl
@@ -72,6 +74,8 @@ class _XmlRemote(object):
72 self.reviewUrl = review 74 self.reviewUrl = review
73 self.revision = revision 75 self.revision = revision
74 self.resolvedFetchUrl = self._resolveFetchUrl() 76 self.resolvedFetchUrl = self._resolveFetchUrl()
77 self.projecthookName = projecthookName
78 self.projecthookRevision = projecthookRevision
75 79
76 def __eq__(self, other): 80 def __eq__(self, other):
77 return self.__dict__ == other.__dict__ 81 return self.__dict__ == other.__dict__
@@ -167,6 +171,11 @@ class XmlManifest(object):
167 e.setAttribute('review', r.reviewUrl) 171 e.setAttribute('review', r.reviewUrl)
168 if r.revision is not None: 172 if r.revision is not None:
169 e.setAttribute('revision', r.revision) 173 e.setAttribute('revision', r.revision)
174 if r.projecthookName is not None:
175 ph = doc.createElement('projecthook')
176 ph.setAttribute('name', r.projecthookName)
177 ph.setAttribute('revision', r.projecthookRevision)
178 e.appendChild(ph)
170 179
171 def _ParseGroups(self, groups): 180 def _ParseGroups(self, groups):
172 return [x for x in re.split(r'[,\s]+', groups) if x] 181 return [x for x in re.split(r'[,\s]+', groups) if x]
@@ -629,7 +638,13 @@ class XmlManifest(object):
629 if revision == '': 638 if revision == '':
630 revision = None 639 revision = None
631 manifestUrl = self.manifestProject.config.GetString('remote.origin.url') 640 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
632 return _XmlRemote(name, alias, fetch, manifestUrl, review, revision) 641 projecthookName = None
642 projecthookRevision = None
643 for n in node.childNodes:
644 if n.nodeName == 'projecthook':
645 projecthookName, projecthookRevision = self._ParseProjectHooks(n)
646 break
647 return _XmlRemote(name, alias, fetch, manifestUrl, review, revision, projecthookName, projecthookRevision)
633 648
634 def _ParseDefault(self, node): 649 def _ParseDefault(self, node):
635 """ 650 """
@@ -933,3 +948,8 @@ class XmlManifest(object):
933 diff['added'].append(toProjects[proj]) 948 diff['added'].append(toProjects[proj])
934 949
935 return diff 950 return diff
951
952 def _ParseProjectHooks(self, node):
953 name = self._reqatt(node, 'name')
954 revision = self._reqatt(node, 'revision')
955 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):
69def sq(r): 69def sq(r):
70 return "'" + r.replace("'", "'\''") + "'" 70 return "'" + r.replace("'", "'\''") + "'"
71 71
72_project_hook_list = None
73def _ProjectHooks():
74 """List the hooks present in the 'hooks' directory.
75
76 These hooks are project hooks and are copied to the '.git/hooks' directory
77 of all subprojects.
78
79 This function caches the list of hooks (based on the contents of the
80 'repo/hooks' directory) on the first call.
81
82 Returns:
83 A list of absolute paths to all of the files in the hooks directory.
84 """
85 global _project_hook_list
86 if _project_hook_list is None:
87 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
88 d = os.path.join(d, 'hooks')
89 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
90 return _project_hook_list
91
92
93class DownloadedChange(object): 72class DownloadedChange(object):
94 _commit_cache = None 73 _commit_cache = None
95 74
@@ -2091,7 +2070,7 @@ class Project(object):
2091 if GitCommand(self, cmd).Wait() != 0: 2070 if GitCommand(self, cmd).Wait() != 0:
2092 raise GitError('%s merge %s ' % (self.name, head)) 2071 raise GitError('%s merge %s ' % (self.name, head))
2093 2072
2094 def _InitGitDir(self, mirror_git=None): 2073 def _InitGitDir(self, mirror_git=None, MirrorOverride=False):
2095 if not os.path.exists(self.gitdir): 2074 if not os.path.exists(self.gitdir):
2096 2075
2097 # Initialize the bare repository, which contains all of the objects. 2076 # Initialize the bare repository, which contains all of the objects.
@@ -2133,11 +2112,38 @@ class Project(object):
2133 for key in ['user.name', 'user.email']: 2112 for key in ['user.name', 'user.email']:
2134 if m.Has(key, include_defaults=False): 2113 if m.Has(key, include_defaults=False):
2135 self.config.SetString(key, m.GetString(key)) 2114 self.config.SetString(key, m.GetString(key))
2136 if self.manifest.IsMirror: 2115 if self.manifest.IsMirror and not MirrorOverride:
2137 self.config.SetString('core.bare', 'true') 2116 self.config.SetString('core.bare', 'true')
2138 else: 2117 else:
2139 self.config.SetString('core.bare', None) 2118 self.config.SetString('core.bare', None)
2140 2119
2120 def _ProjectHooks(self, remote, repodir):
2121 """List the hooks present in the 'hooks' directory.
2122
2123 These hooks are project hooks and are copied to the '.git/hooks' directory
2124 of all subprojects.
2125
2126 The remote projecthooks supplement/overrule any stockhook making it possible to
2127 have a combination of hooks both from the remote projecthook and
2128 .repo/hooks directories.
2129
2130 Returns:
2131 A list of absolute paths to all of the files in the hooks directory and
2132 projecthooks files, excluding the .git folder.
2133 """
2134 hooks = {}
2135 d = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'hooks')
2136 hooks = dict([(x, os.path.join(d, x)) for x in os.listdir(d)])
2137 if remote is not None:
2138 if remote.projecthookName is not None:
2139 d = os.path.abspath('%s/projecthooks/%s/%s' % (repodir, remote.name, remote.projecthookName))
2140 if os.path.isdir(d):
2141 hooks.update(dict([(x, os.path.join(d, x)) for x in os.listdir(d)]))
2142
2143 if hooks.has_key('.git'):
2144 del hooks['.git']
2145 return hooks.values()
2146
2141 def _UpdateHooks(self): 2147 def _UpdateHooks(self):
2142 if os.path.exists(self.gitdir): 2148 if os.path.exists(self.gitdir):
2143 self._InitHooks() 2149 self._InitHooks()
@@ -2146,7 +2152,10 @@ class Project(object):
2146 hooks = os.path.realpath(self._gitdir_path('hooks')) 2152 hooks = os.path.realpath(self._gitdir_path('hooks'))
2147 if not os.path.exists(hooks): 2153 if not os.path.exists(hooks):
2148 os.makedirs(hooks) 2154 os.makedirs(hooks)
2149 for stock_hook in _ProjectHooks(): 2155 pr = None
2156 if self is not self.manifest.manifestProject:
2157 pr = self.manifest.remotes.get(self.remote.name)
2158 for stock_hook in self._ProjectHooks(pr, self.manifest.repodir):
2150 name = os.path.basename(stock_hook) 2159 name = os.path.basename(stock_hook)
2151 2160
2152 if name in ('commit-msg',) and not self.remote.review \ 2161 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:
32from color import Coloring 32from color import Coloring
33from command import InteractiveCommand, MirrorSafeCommand 33from command import InteractiveCommand, MirrorSafeCommand
34from error import ManifestParseError 34from error import ManifestParseError
35from project import SyncBuffer 35from project import SyncBuffer, MetaProject
36from git_config import GitConfig 36from git_config import GitConfig
37from git_command import git_require, MIN_GIT_VERSION 37from git_command import git_require, MIN_GIT_VERSION
38 38
@@ -374,6 +374,52 @@ to update the working directory files.
374 print(' rm -r %s/.repo' % self.manifest.topdir) 374 print(' rm -r %s/.repo' % self.manifest.topdir)
375 print('and try again.') 375 print('and try again.')
376 376
377 def _SyncProjectHooks(self, opt, repodir):
378 """Downloads the defined hooks supplied in the projecthooks element
379
380 """
381 # Always delete projecthooks and re-download for every new init.
382 projecthooksdir = os.path.join(repodir, 'projecthooks')
383 if os.path.exists(projecthooksdir):
384 shutil.rmtree(projecthooksdir)
385 for remotename in self.manifest.remotes:
386 r = self.manifest.remotes.get(remotename)
387 if r.projecthookName is not None and r.projecthookRevision is not None:
388 projecthookurl = r.resolvedFetchUrl.rstrip('/') + '/' + r.projecthookName
389
390 ph = MetaProject(manifest = self.manifest,
391 name = r.projecthookName,
392 gitdir = os.path.join(projecthooksdir,'%s/%s.git' % (remotename, r.projecthookName)),
393 worktree = os.path.join(projecthooksdir,'%s/%s' % (remotename, r.projecthookName)))
394
395 ph.revisionExpr = r.projecthookRevision
396 is_new = not ph.Exists
397
398 if is_new:
399 if not opt.quiet:
400 print('Get projecthook %s' % \
401 GitConfig.ForUser().UrlInsteadOf(projecthookurl), file=sys.stderr)
402 ph._InitGitDir(MirrorOverride=True)
403
404 phr = ph.GetRemote(remotename)
405 phr.name = 'origin'
406 phr.url = projecthookurl
407 phr.ResetFetch()
408 phr.Save()
409
410 if not ph.Sync_NetworkHalf(quiet=opt.quiet, is_new=is_new, clone_bundle=False):
411 print('fatal: cannot obtain projecthook %s' % phr.url, file=sys.stderr)
412
413 # Better delete the git dir if we created it; otherwise next
414 # time (when user fixes problems) we won't go through the "is_new" logic.
415 if is_new:
416 shutil.rmtree(ph.gitdir)
417 sys.exit(1)
418
419 syncbuf = SyncBuffer(ph.config)
420 ph.Sync_LocalHalf(syncbuf)
421 syncbuf.Finish()
422
377 def Execute(self, opt, args): 423 def Execute(self, opt, args):
378 git_require(MIN_GIT_VERSION, fail=True) 424 git_require(MIN_GIT_VERSION, fail=True)
379 425
@@ -389,6 +435,7 @@ to update the working directory files.
389 435
390 self._SyncManifest(opt) 436 self._SyncManifest(opt)
391 self._LinkManifest(opt.manifest_name) 437 self._LinkManifest(opt.manifest_name)
438 self._SyncProjectHooks(opt, self.manifest.repodir)
392 439
393 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: 440 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
394 if opt.config_name or self._ShouldConfigureUser(): 441 if opt.config_name or self._ShouldConfigureUser():