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(): |
