github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/project/loader.go (about) 1 // Copyright 2017 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package project 6 7 import ( 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "reflect" 14 "strings" 15 16 "github.com/btwiuse/jiri" 17 "github.com/btwiuse/jiri/gitutil" 18 ) 19 20 type importCache struct { 21 localManifest bool 22 ref string 23 24 // keeps track of first import tag, this is used to give helpful error 25 // message in the case of import conflict 26 parentImport string 27 } 28 29 type loader struct { 30 Projects Projects 31 ProjectOverrides map[string]Project 32 ImportOverrides map[string]Import 33 ProjectLocks ProjectLocks 34 Hooks Hooks 35 Packages Packages 36 PackageLocks PackageLocks 37 TmpDir string 38 localProjects Projects 39 importProjects Projects 40 importCacheMap map[string]importCache 41 importTree importTree 42 update bool 43 cycleStack []cycleInfo 44 manifests map[string]bool 45 lockfiles map[string]bool 46 parentFile string 47 } 48 49 type importTreeNode struct { 50 parents map[*importTreeNode]bool 51 children map[*importTreeNode]bool 52 tag string 53 computedAttributes attributes 54 } 55 56 type importTree struct { 57 root *importTreeNode 58 pool map[string]*importTreeNode 59 filenameMap map[string]bool 60 projectKeyMap map[ProjectKey]*importTreeNode 61 } 62 63 func newImportTree() importTree { 64 return importTree{ 65 root: &importTreeNode{ 66 make(map[*importTreeNode]bool), 67 make(map[*importTreeNode]bool), 68 "", 69 nil, 70 }, 71 pool: make(map[string]*importTreeNode), 72 filenameMap: make(map[string]bool), 73 projectKeyMap: make(map[ProjectKey]*importTreeNode), 74 } 75 } 76 77 func (t *importTree) getNode(repoPath, file, ref string) *importTreeNode { 78 key := filepath.Join(repoPath, file) + ":" + ref 79 if v, ok := t.pool[key]; ok { 80 return v 81 } 82 v := &importTreeNode{ 83 make(map[*importTreeNode]bool), 84 make(map[*importTreeNode]bool), 85 key, 86 nil, 87 } 88 if len(t.pool) == 0 { 89 t.root.addChild(v) 90 } 91 t.pool[key] = v 92 return v 93 } 94 95 func (t *importTree) buildImportAttributes() { 96 // Due to the logic in how remote imports are handled, the 97 // manifest loader will create two nodes for a single remote import, 98 // causing second one as an orphan. A simple solution is just 99 // connect the orphan nodes to the Root to build a connected tree. 100 for _, v := range t.pool { 101 if len(v.parents) == 0 { 102 t.root.addChild(v) 103 } 104 } 105 // The file name of a manifest is used as a git attributes name only if that 106 // file name is unique. 107 dupMap := make(map[string]bool) 108 for k := range t.filenameMap { 109 filename := filepath.Base(k) 110 if _, ok := dupMap[filename]; ok { 111 dupMap[filename] = true 112 } else { 113 dupMap[filename] = false 114 } 115 } 116 dfsAddAttribute(t.root, newAttributes("")) 117 for _, node := range t.pool { 118 for attr := range node.computedAttributes { 119 if v, ok := dupMap[attr]; ok && v { 120 delete(node.computedAttributes, attr) 121 } 122 } 123 } 124 } 125 126 func dfsAddAttribute(curNode *importTreeNode, parentAttrs attributes) { 127 if curNode.computedAttributes == nil { 128 curNode.computedAttributes = newAttributes(curNode.tag) 129 } 130 curNode.computedAttributes.Add(parentAttrs) 131 for k := range curNode.children { 132 dfsAddAttribute(k, curNode.computedAttributes) 133 } 134 } 135 136 // (TODO:haowei) generateAttributeGraph generate a graphviz .dot file of 137 // importTree for debugging purpose. It should be removed once .gitattribute 138 // generator is considered as stable. 139 func (t *importTree) generateAttributeGraph() string { 140 var buf bytes.Buffer 141 buf.WriteString("\ndigraph G {\n") 142 nodeCount := 1 143 nameMap := make(map[*importTreeNode]string) 144 nameMap[t.root] = "n0" 145 for _, v := range t.pool { 146 nameMap[v] = fmt.Sprintf("n%d", nodeCount) 147 nodeCount++ 148 } 149 150 // The file name of a manifest is used as a git attributes name only if that 151 // file name is unique. 152 dupMap := make(map[string]bool) 153 for k := range t.filenameMap { 154 filename := filepath.Base(k) 155 if _, ok := dupMap[filename]; ok { 156 dupMap[filename] = true 157 } else { 158 dupMap[filename] = false 159 } 160 } 161 // Output nodes 162 for k, v := range nameMap { 163 attrs := newAttributes(k.tag) 164 for attr := range attrs { 165 if v, ok := dupMap[attr]; ok && v { 166 delete(attrs, attr) 167 } 168 } 169 buf.WriteString(fmt.Sprintf("\t%s[label=\"%s,%p\"];\n", v, attrs.String(), k)) 170 } 171 buf.WriteString("\n") 172 // Output edges 173 for k := range nameMap { 174 for child := range k.children { 175 nameSource := nameMap[k] 176 nameTarget, ok := nameMap[child] 177 if !ok { 178 fmt.Printf("ERROR: cound not find target node %q,%p in nameMap\n", child.tag, child) 179 continue 180 } 181 buf.WriteString(fmt.Sprintf("\t%s -> %s;\n", nameSource, nameTarget)) 182 } 183 } 184 185 buf.WriteString("\n}") 186 return buf.String() 187 } 188 189 func (n *importTreeNode) addChild(other *importTreeNode) { 190 if other == nil { 191 return 192 } 193 n.children[other] = true 194 other.parents[n] = true 195 } 196 197 func (ld *loader) cleanup() { 198 if ld.TmpDir != "" { 199 os.RemoveAll(ld.TmpDir) 200 ld.TmpDir = "" 201 } 202 } 203 204 type cycleInfo struct { 205 file, key string 206 } 207 208 // newManifestLoader returns a new manifest loader. The localProjects are used 209 // to resolve remote imports; if nil, encountering any remote import will result 210 // in an error. If update is true, remote manifest import projects that don't 211 // exist locally are cloned under TmpDir, and inserted into localProjects. 212 // 213 // If update is true, remote changes to manifest projects will be fetched, and 214 // manifest projects that don't exist locally will be created in temporary 215 // directories, and added to localProjects. 216 func newManifestLoader(localProjects Projects, update bool, file string) *loader { 217 return &loader{ 218 Projects: make(Projects), 219 ProjectOverrides: make(map[string]Project), 220 ImportOverrides: make(map[string]Import), 221 ProjectLocks: make(ProjectLocks), 222 Hooks: make(Hooks), 223 Packages: make(Packages), 224 PackageLocks: make(PackageLocks), 225 localProjects: localProjects, 226 importProjects: make(Projects), 227 update: update, 228 importCacheMap: make(map[string]importCache), 229 manifests: make(map[string]bool), 230 lockfiles: make(map[string]bool), 231 importTree: newImportTree(), 232 parentFile: file, 233 } 234 } 235 236 // loadNoCycles checks for cycles in imports. There are two types of cycles: 237 // file - Cycle in the paths of manifest files in the local filesystem. 238 // key - Cycle in the remote manifests specified by remote imports. 239 // 240 // Example of file cycles. File A imports file B, and vice versa. 241 // file=manifest/A file=manifest/B 242 // <manifest> <manifest> 243 // <localimport file="B"/> <localimport file="A"/> 244 // </manifest> </manifest> 245 // 246 // Example of key cycles. The key consists of "remote/manifest", e.g. 247 // https://vanadium.googlesource.com/manifest/v2/default 248 // In the example, key x/A imports y/B, and vice versa. 249 // key=x/A key=y/B 250 // <manifest> <manifest> 251 // <import remote="y" manifest="B"/> <import remote="x" manifest="A"/> 252 // </manifest> </manifest> 253 // 254 // The above examples are simple, but the general strategy is demonstrated. We 255 // keep a single stack for both files and keys, and push onto each stack before 256 // running the recursive read or update function, and pop the stack when the 257 // function is done. If we see a duplicate on the stack at any point, we know 258 // there's a cycle. Note that we know the file for both local and remote 259 // imports, but we only know the key for remote imports; the key for local 260 // imports is empty. 261 // 262 // A more complex case would involve a combination of local and remote imports, 263 // using the "root" attribute to change paths on the local filesystem. In this 264 // case the key will eventually expose the cycle. 265 func (ld *loader) loadNoCycles(jirix *jiri.X, root, repoPath, file, ref, cycleKey, parentImport string, localManifest bool) error { 266 f := file 267 if repoPath != "" { 268 f = filepath.Join(repoPath, file) 269 } 270 info := cycleInfo{f, cycleKey} 271 for _, c := range ld.cycleStack { 272 switch { 273 case f == c.file: 274 return fmt.Errorf("import cycle detected in local manifest files: %q", append(ld.cycleStack, info)) 275 case cycleKey == c.key && cycleKey != "": 276 return fmt.Errorf("import cycle detected in remote manifest imports: %q", append(ld.cycleStack, info)) 277 } 278 } 279 ld.cycleStack = append(ld.cycleStack, info) 280 if err := ld.load(jirix, root, repoPath, file, ref, parentImport, localManifest); err != nil { 281 return err 282 } 283 ld.cycleStack = ld.cycleStack[:len(ld.cycleStack)-1] 284 return nil 285 } 286 287 // shortFileName returns the relative path if file is relative to root, 288 // otherwise returns the file name unchanged. 289 func shortFileName(root, repoPath, file, ref string) string { 290 if repoPath != "" { 291 return fmt.Sprintf("%s %s:%s", shortFileName(root, "", repoPath, ""), ref, file) 292 } 293 if p := root + string(filepath.Separator); strings.HasPrefix(file, p) { 294 return file[len(p):] 295 } 296 return file 297 } 298 299 func (ld *loader) Load(jirix *jiri.X, root, repoPath, file, ref, cycleKey, parentImport string, localManifest bool) error { 300 jirix.TimerPush("load " + shortFileName(jirix.Root, repoPath, file, ref)) 301 defer jirix.TimerPop() 302 return ld.loadNoCycles(jirix, root, repoPath, file, ref, cycleKey, parentImport, localManifest) 303 } 304 305 func (ld *loader) cloneManifestRepo(jirix *jiri.X, remote *Import, cacheDirPath string, localManifest bool) error { 306 if !ld.update || localManifest { 307 jirix.Logger.Warningf("import %q not found locally, getting from server. Please check your manifest file (default: .jiri_manifest).\nMake sure that the 'name' attributes on the 'import' and 'project' tags match and that there is a corresponding 'project' tag for every 'import' tag.\n\n", remote.Name) 308 } 309 jirix.Logger.Debugf("clone manifest project %q", remote.Name) 310 // The remote manifest project doesn't exist locally. Clone it into a 311 // temp directory, and add it to ld.localProjects. 312 if ld.TmpDir == "" { 313 var err error 314 if ld.TmpDir, err = ioutil.TempDir("", "jiri-load"); err != nil { 315 return fmt.Errorf("TempDir() failed: %v", err) 316 } 317 } 318 path := filepath.Join(ld.TmpDir, remote.projectKeyFileName()) 319 p, err := remote.toProject(path) 320 if err != nil { 321 return err 322 } 323 if err := os.MkdirAll(path, 0755); err != nil { 324 return fmtError(err) 325 } 326 remoteUrl := rewriteRemote(jirix, p.Remote) 327 r := remoteUrl 328 task := jirix.Logger.AddTaskMsg("Creating manifest: %s", remote.Name) 329 defer task.Done() 330 if cacheDirPath != "" { 331 logStr := fmt.Sprintf("update/create cache for project %q", remote.Name) 332 jirix.Logger.Debugf(logStr) 333 task := jirix.Logger.AddTaskMsg(logStr) 334 defer task.Done() 335 if err := updateOrCreateCache(jirix, cacheDirPath, remoteUrl, remote.RemoteBranch, remote.Revision, 0); err != nil { 336 return err 337 } 338 r = cacheDirPath 339 } 340 opts := []gitutil.CloneOpt{gitutil.ReferenceOpt(cacheDirPath), gitutil.NoCheckoutOpt(true)} 341 if jirix.Partial { 342 opts = append(opts, gitutil.OmitBlobsOpt(true)) 343 } 344 if err := clone(jirix, r, path, opts...); err != nil { 345 return err 346 } 347 scm := gitutil.New(jirix, gitutil.RootDirOpt(path)) 348 defer func() { 349 if err := scm.AddOrReplaceRemote("origin", remoteUrl); err != nil { 350 jirix.Logger.Errorf("failed to set remote back to %v for project %+v", remoteUrl, p) 351 } 352 }() 353 p.Revision = remote.Revision 354 p.RemoteBranch = remote.RemoteBranch 355 if err := checkoutHeadRevision(jirix, p, false); err != nil { 356 return fmt.Errorf("Not able to checkout head for %s(%s): %v", p.Name, p.Path, err) 357 } 358 ld.localProjects[remote.ProjectKey()] = p 359 return nil 360 } 361 362 // loadLockFile will recursively load lockfiles from dir to its parent directories until it 363 // reaches $JIRI_ROOT. It will only report errors on lockfiles such as unknown format or confliting data. 364 // All I/O related errors will be ignored. 365 func (ld *loader) loadLockFile(jirix *jiri.X, repoPath, dir, lockFileName, ref string) error { 366 lockfile := filepath.Join(dir, lockFileName) 367 lockKey := lockfile 368 if repoPath != "" { 369 lockKey = filepath.Join(repoPath, lockfile) + ":" + ref 370 } 371 if ld.lockfiles[lockKey] { 372 return nil 373 } 374 375 if !(dir == "" || dir == "." || dir == jirix.Root || dir == string(filepath.Separator)) { 376 if err := ld.loadLockFile(jirix, repoPath, filepath.Dir(dir), lockFileName, ref); err != nil { 377 return err 378 } 379 } 380 381 var data []byte 382 if repoPath != "" { 383 s, err := gitutil.New(jirix, gitutil.RootDirOpt(repoPath)).Show(ref, lockfile) 384 if err != nil { 385 // It's fine if jiri.lock cannot be find, skip this jiri.lock 386 jirix.Logger.Debugf("Could not find %q in repository %q for ref %q", lockfile, repoPath, ref) 387 return nil 388 } 389 data = []byte(s) 390 } else { 391 if _, err := os.Stat(lockfile); err != nil { 392 if os.IsNotExist(err) { 393 jirix.Logger.Debugf("could not find %q file at %q", lockFileName, lockfile) 394 } else { 395 jirix.Logger.Debugf("could not access %q file at %q due to error %v", lockFileName, lockfile, err) 396 } 397 return nil 398 } 399 temp, err := ioutil.ReadFile(lockfile) 400 if err != nil { 401 // Supress I/O errors as it is OK if a lockfile cannot be accessed. 402 return nil 403 } 404 data = temp 405 } 406 if err := ld.parseLockData(jirix, data); err != nil { 407 return err 408 } 409 if repoPath == "" { 410 jirix.Logger.Debugf("loaded lockfile at %s", lockfile) 411 } else { 412 jirix.Logger.Debugf("loaded lockfile at %q in repository %q for ref %q", lockfile, repoPath, ref) 413 } 414 ld.lockfiles[lockKey] = true 415 return nil 416 } 417 418 func (ld *loader) parseLockData(jirix *jiri.X, data []byte) error { 419 projectLocks, pkgLocks, err := UnmarshalLockEntries(data) 420 if err != nil { 421 return err 422 } 423 424 for k, v := range projectLocks { 425 if projLock, ok := ld.ProjectLocks[k]; ok { 426 if projLock != v && !jirix.UsingImportOverride { 427 return fmt.Errorf("conflicting project lock entries %+v with %+v", projLock, v) 428 } 429 } else { 430 ld.ProjectLocks[k] = v 431 } 432 } 433 434 for k, v := range pkgLocks { 435 if pkgLock, ok := ld.PackageLocks[k]; ok { 436 // Only package locks may conflict during a normal 'jiri resolve'. 437 // Treating conflicts as errors in all other scenarios. 438 if pkgLock != v && !jirix.IgnoreLockConflicts && !jirix.UsingImportOverride { 439 return fmt.Errorf("conflicting package lock entries %+v with %+v", pkgLock, v) 440 } 441 } else { 442 ld.PackageLocks[k] = v 443 } 444 } 445 446 return nil 447 } 448 449 func (ld *loader) load(jirix *jiri.X, root, repoPath, file, ref, parentImport string, localManifest bool) error { 450 f := file 451 if repoPath != "" { 452 f = filepath.Join(repoPath, file) 453 } 454 if ld.manifests[f] { 455 return nil 456 } 457 ld.manifests[f] = true 458 459 loadManifestAndLocks := func(jirix *jiri.X, file string) (*Manifest, error) { 460 if repoPath == "" { 461 m, err := ManifestFromFile(jirix, file) 462 if err != nil { 463 return nil, fmt.Errorf("Error reading from manifest file %s %s:%s:error(%s)", repoPath, ref, file, err) 464 } 465 if jirix.LockfileEnabled { 466 if err := ld.loadLockFile(jirix, repoPath, filepath.Dir(file), jirix.LockfileName, ref); err != nil { 467 return nil, err 468 } 469 } 470 return m, err 471 } 472 // repoPath != "" 473 s, err := gitutil.New(jirix, gitutil.RootDirOpt(repoPath)).Show(ref, file) 474 if err != nil { 475 return nil, fmt.Errorf("Unable to get manifest file for %s %s:%s:error(%s)", repoPath, ref, file, err) 476 } 477 m, err := ManifestFromBytes([]byte(s)) 478 if err != nil { 479 return nil, fmt.Errorf("Error reading from manifest file %s %s:%s:error(%s)", repoPath, ref, file, err) 480 } 481 if jirix.LockfileEnabled { 482 if err := ld.loadLockFile(jirix, repoPath, filepath.Dir(file), jirix.LockfileName, ref); err != nil { 483 return nil, err 484 } 485 } 486 return m, nil 487 } 488 489 m, err := loadManifestAndLocks(jirix, file) 490 if err != nil { 491 return err 492 } 493 494 if jirix.UsingSnapshot && !jirix.OverrideOptional { 495 // using attributes defined in snapshot file instead of 496 // using predefined ones in jiri init. 497 jirix.FetchingAttrs = m.Attributes 498 } 499 500 // Add override information 501 if parentImport == "" { 502 for _, p := range m.ProjectOverrides { 503 // Reuse the MakeProjectKey function in case it is changed 504 // in the future. 505 key := string(p.Key()) 506 ld.ProjectOverrides[key] = p 507 } 508 for _, p := range m.ImportOverrides { 509 // Reuse the MakeProjectKey function in case it is changed 510 // in the future. 511 key := string(p.ProjectKey()) 512 if !jirix.UsingImportOverride { 513 jirix.UsingImportOverride = true 514 } 515 ld.ImportOverrides[key] = p 516 } 517 } else if len(m.ProjectOverrides)+len(m.ImportOverrides) > 0 { 518 return fmt.Errorf("manifest %q contains overrides but was imported by %q. Overrides are allowed only in the root manifest", shortFileName(jirix.Root, repoPath, file, ref), parentImport) 519 } 520 521 // Use manifest's directory name and file name as default 522 // git attributes. It will be later expanded using the 523 // import relationships. 524 defaultGitAttrs := func() string { 525 manifestFile := file 526 if repoPath != "" { 527 manifestFile = filepath.Join(repoPath, manifestFile) 528 } 529 containingDir := filepath.Base(filepath.Dir(manifestFile)) 530 filename := file 531 ld.importTree.filenameMap[filename] = false 532 return containingDir + "," + filepath.Base(file) 533 } 534 self := ld.importTree.getNode(repoPath, file, ref) 535 self.tag = defaultGitAttrs() 536 // Process remote imports. 537 for _, remote := range m.Imports { 538 // Apply override if it exists. 539 remote, err := overrideImport(jirix, remote, ld.ProjectOverrides, ld.ImportOverrides) 540 if err != nil { 541 return err 542 } 543 nextRoot := filepath.Join(root, remote.Root) 544 remote.Name = filepath.Join(nextRoot, remote.Name) 545 key := remote.ProjectKey() 546 p, ok := ld.localProjects[key] 547 cacheDirPath, err := cacheDirPathFromRemote(jirix.Cache, remote.Remote) 548 if err != nil { 549 return err 550 } 551 552 if !ok { 553 if err := ld.cloneManifestRepo(jirix, &remote, cacheDirPath, localManifest); err != nil { 554 return err 555 } 556 p = ld.localProjects[key] 557 } 558 // Reset the project to its specified branch and load the next file. Note 559 // that we call load() recursively, so multiple files may be loaded by 560 // loadImport. 561 p.Revision = remote.Revision 562 p.RemoteBranch = remote.RemoteBranch 563 ld.importProjects[key] = p 564 pi := parentImport 565 if pi == "" { 566 pi = fmt.Sprintf("import[manifest=%q, remote=%q]", remote.Manifest, remote.Remote) 567 } 568 569 self.addChild(ld.importTree.getNode(repoPath, remote.Manifest, "")) 570 if err := ld.loadImport(jirix, nextRoot, remote.Manifest, remote.cycleKey(), cacheDirPath, pi, p, localManifest); err != nil { 571 return err 572 } 573 } 574 575 // Process local imports. 576 for _, local := range m.LocalImports { 577 nextFile := filepath.Join(filepath.Dir(file), local.File) 578 self.addChild(ld.importTree.getNode(repoPath, nextFile, ref)) 579 if err := ld.Load(jirix, root, repoPath, nextFile, ref, "", parentImport, localManifest); err != nil { 580 return err 581 } 582 } 583 584 hookMap := make(map[string][]*Hook) 585 586 for idx, _ := range m.Hooks { 587 hook := &m.Hooks[idx] 588 if err := hook.validate(); err != nil { 589 return err 590 } 591 hookMap[hook.ProjectName] = append(hookMap[hook.ProjectName], hook) 592 } 593 594 // Collect projects. 595 for _, project := range m.Projects { 596 // Apply override if it exists. 597 project, err := overrideProject(jirix, project, ld.ProjectOverrides, ld.ImportOverrides) 598 if err != nil { 599 return err 600 } 601 // normalize project attributes 602 project.ComputedAttributes = newAttributes(project.Attributes) 603 project.Attributes = project.ComputedAttributes.String() 604 // Make paths absolute by prepending <root>. 605 project.absolutizePaths(filepath.Join(jirix.Root, root)) 606 607 if hooks, ok := hookMap[project.Name]; ok { 608 for _, hook := range hooks { 609 hook.ActionPath = project.Path 610 } 611 } 612 613 // Prepend the root to the project name. This will be a noop if the import is not rooted. 614 project.Name = filepath.Join(root, project.Name) 615 key := project.Key() 616 617 if r, ok := ld.importProjects[key]; ok { 618 // update revision for this project 619 if r.Revision != "" && r.Revision != "HEAD" { 620 if project.Revision == "" || project.Revision == "HEAD" { 621 project.Revision = r.Revision 622 } else if r.Revision != project.Revision { 623 return fmt.Errorf("project %q found in %q defines different revision than its corresponding import tag.", key, shortFileName(jirix.Root, repoPath, file, ref)) 624 } 625 } 626 } 627 628 if dup, ok := ld.Projects[key]; ok && !reflect.DeepEqual(dup, project) { 629 // TODO(toddw): Tell the user the other conflicting file. 630 return fmt.Errorf("duplicate project %q found in %q", key, shortFileName(jirix.Root, repoPath, file, ref)) 631 } 632 633 // Record manifest location. 634 project.ManifestPath = f 635 636 // Associate project with importTreeNode for git attributes propagation. 637 ld.importTree.projectKeyMap[key] = self 638 639 ld.Projects[key] = project 640 } 641 642 for _, hook := range m.Hooks { 643 if hook.ActionPath == "" { 644 return fmt.Errorf("invalid hook %q for project %q. Please make sure you are importing project %q and this hook is in the manifest which directly/indirectly imports that project.", hook.Name, hook.ProjectName, hook.ProjectName) 645 } 646 key := hook.Key() 647 ld.Hooks[key] = hook 648 } 649 650 for _, pkg := range m.Packages { 651 // normalize package attributes. 652 pkg.ComputedAttributes = newAttributes(pkg.Attributes) 653 pkg.Attributes = pkg.ComputedAttributes.String() 654 // Record manifest location. 655 pkg.ManifestPath = f 656 key := pkg.Key() 657 ld.Packages[key] = pkg 658 } 659 return nil 660 } 661 662 func (ld *loader) loadImport(jirix *jiri.X, root, file, cycleKey, cacheDirPath, parentImport string, project Project, localManifest bool) (e error) { 663 lm := localManifest 664 ref := "" 665 666 if v, ok := ld.importCacheMap[strings.Trim(project.Remote, "/")]; ok { 667 // local manifest in cache takes precedence as this might be manifest mentioned in .jiri_manifest 668 lm = v.localManifest 669 ref = v.ref 670 // check conflicting imports 671 if !lm && ref != "JIRI_HEAD" { 672 if tref, err := GetHeadRevision(jirix, project); err != nil { 673 return err 674 } else if tref != ref { 675 return fmt.Errorf("Conflicting ref for import %s - %q and %q. There are conflicting imports in file:\n%s:\n'%s' and '%s'", 676 jirix.Color.Red(project.Remote), ref, tref, jirix.Color.Yellow(ld.parentFile), 677 jirix.Color.Yellow(v.parentImport), jirix.Color.Yellow(parentImport)) 678 } 679 } 680 } else { 681 // We don't need to fetch or find ref for local manifest changes 682 if !lm { 683 // We only fetch on updates. 684 if ld.update { 685 // Fetch only if project not pinned or revision not available in 686 // local git as we anyways update all the projects later. 687 fetch := true 688 if project.Revision != "" && project.Revision != "HEAD" { 689 if _, err := gitutil.New(jirix, gitutil.RootDirOpt(project.Path)).Show(project.Revision, ""); err == nil { 690 fetch = false 691 } 692 } 693 if fetch { 694 if cacheDirPath != "" { 695 remoteUrl := rewriteRemote(jirix, project.Remote) 696 if err := updateOrCreateCache(jirix, cacheDirPath, remoteUrl, project.RemoteBranch, project.Revision, 0); err != nil { 697 return err 698 } 699 } 700 if err := fetchAll(jirix, project); err != nil { 701 return fmt.Errorf("Fetch failed for project(%s), %s", project.Path, err) 702 } 703 } 704 } else { 705 // If not updating then try to get file from JIRI_HEAD 706 if _, err := gitutil.New(jirix, gitutil.RootDirOpt(project.Path)).Show("JIRI_HEAD", ""); err == nil { 707 // JIRI_HEAD available, set ref 708 ref = "JIRI_HEAD" 709 } 710 } 711 if ref == "" { 712 var err error 713 if ref, err = GetHeadRevision(jirix, project); err != nil { 714 return err 715 } 716 } 717 } 718 ld.importCacheMap[strings.Trim(project.Remote, "/")] = importCache{ 719 localManifest: lm, 720 ref: ref, 721 parentImport: parentImport, 722 } 723 } 724 if lm { 725 // load from local checked out file 726 return ld.Load(jirix, root, "", filepath.Join(project.Path, file), "", cycleKey, parentImport, false) 727 } 728 return ld.Load(jirix, root, project.Path, file, ref, cycleKey, parentImport, false) 729 } 730 731 func (ld *loader) GenerateGitAttributesForProjects(jirix *jiri.X) { 732 ld.importTree.buildImportAttributes() 733 for k, v := range ld.Projects { 734 if treeNode, ok := ld.importTree.projectKeyMap[k]; ok { 735 v.GitAttributes = treeNode.computedAttributes.String() 736 ld.Projects[k] = v 737 } 738 } 739 jirix.Logger.Debugf("Generated dot file: %s", ld.importTree.generateAttributeGraph()) 740 }