diff options
-rw-r--r-- | docs/manifest-format.txt | 15 | ||||
-rw-r--r-- | manifest_xml.py | 24 | ||||
-rw-r--r-- | project.py | 57 | ||||
-rw-r--r-- | subcmds/init.py | 49 |
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. | |||
306 | Attribute `name`: the manifest to include, specified relative to | 310 | Attribute `name`: the manifest to include, specified relative to |
307 | the manifest repository's root. | 311 | the manifest repository's root. |
308 | 312 | ||
313 | Element projecthook | ||
314 | ------------------- | ||
315 | |||
316 | This element is used to define a per-remote hook git that is | ||
317 | fetched and applied to all projects using the remote. The project- | ||
318 | hook functionality allows for company/team .git/hooks to be used. | ||
319 | The hooks in the supplied project and revision are supplemented to | ||
320 | the current repo stock hooks for each project. Supplemented hooks | ||
321 | overrule any stock hooks. | ||
309 | 322 | ||
310 | Local Manifests | 323 | Local 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 | ||
@@ -69,27 +69,6 @@ def not_rev(r): | |||
69 | def sq(r): | 69 | def sq(r): |
70 | return "'" + r.replace("'", "'\''") + "'" | 70 | return "'" + r.replace("'", "'\''") + "'" |
71 | 71 | ||
72 | _project_hook_list = None | ||
73 | def _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 | |||
93 | class DownloadedChange(object): | 72 | class 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: | |||
32 | from color import Coloring | 32 | from color import Coloring |
33 | from command import InteractiveCommand, MirrorSafeCommand | 33 | from command import InteractiveCommand, MirrorSafeCommand |
34 | from error import ManifestParseError | 34 | from error import ManifestParseError |
35 | from project import SyncBuffer | 35 | from project import SyncBuffer, MetaProject |
36 | from git_config import GitConfig | 36 | from git_config import GitConfig |
37 | from git_command import git_require, MIN_GIT_VERSION | 37 | from 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(): |