github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/readtree.go (about) 1 package git 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "regexp" 9 "sort" 10 "strings" 11 "time" 12 ) 13 14 // Options that may be passed in the command line to ReadTree. 15 type ReadTreeOptions struct { 16 // Perform a merge 17 Merge bool 18 // Discard changes that are not in stage 0 while doing a merge. 19 Reset bool 20 21 // Also update the work tree 22 Update bool 23 24 // -i parameter to ReadTree. Not implemented 25 IgnoreWorktreeCheck bool 26 27 // Do not write index to disk. 28 DryRun bool 29 30 // Unused, just for consistency with real git command line. 31 Verbose bool 32 33 // Not implemented 34 TrivialMerge, AggressiveMerge bool 35 36 // Read the named tree into the directory prefix/ under the index 37 Prefix string 38 39 // Used as the name of a .gitignore to look for in each directory 40 ExcludePerDirectory string 41 42 // Name of the index file to use under c.GitDir 43 IndexOutput string 44 45 // Discard all the entries in the index instead of updating it to the 46 // named Treeish. 47 Empty bool 48 49 // Disable sparse checkout. 50 // Note that it's the caller's responsibility to set this option if 51 // core.sparsecheckout is not equal to true. 52 NoSparseCheckout bool 53 } 54 55 // Helper to safely check if path is the same in p1 and p2 56 func samePath(p1, p2 IndexMap, path IndexPath) bool { 57 p1i, p1ok := p1[path] 58 p2i, p2ok := p2[path] 59 60 // It's in one but not the other directly, so it's not 61 // the same. 62 if p1ok != p2ok { 63 return false 64 } 65 66 // Avoid a nil pointer below by explicitly checking if one 67 // is missing. 68 if p1ok == false { 69 return p2ok == false 70 } 71 if p2ok == false { 72 return p1ok == false 73 } 74 75 // It's in both, so we can safely check the sha 76 return p1i.Sha1 == p2i.Sha1 77 78 } 79 80 func checkSparseMatches(c *Client, opt ReadTreeOptions, path IndexPath, patterns []IgnorePattern) bool { 81 if opt.NoSparseCheckout { 82 // If noSparseCheckout is set, just claim everything 83 // matches. 84 return true 85 } 86 fp, err := path.FilePath(c) 87 if err != nil { 88 panic(err) 89 } 90 f := fp.String() 91 matches := false 92 for _, pattern := range patterns { 93 if pattern.Negates() { 94 if pattern.Matches(f, false) { 95 matches = !matches 96 } 97 } else { 98 if pattern.Matches(f, false) { 99 matches = true 100 } 101 } 102 } 103 return matches 104 } 105 106 func parseSparsePatterns(c *Client, opt *ReadTreeOptions) []IgnorePattern { 107 if opt.NoSparseCheckout { 108 return nil 109 } 110 sparsefile := c.GitDir.File("info/sparse-checkout") 111 if !sparsefile.Exists() { 112 // If the file doesn't exist, pretend the 113 // flag to ignore the file was set since the 114 // logic is identical. 115 opt.NoSparseCheckout = true 116 return nil 117 } 118 sp, err := ParseIgnorePatterns(c, sparsefile, "") 119 if err != nil { 120 return nil 121 } 122 return sp 123 } 124 125 // ReadTreeThreeWay will perform a three-way merge on the trees stage1, stage2, and stage3. 126 // In a normal merge, stage1 is the common ancestor, stage2 is "our" changes, and 127 // stage3 is "their" changes. See git-read-tree(1) for details. 128 // 129 // If options.DryRun is not false, it will also be written to the Client's index file. 130 func ReadTreeThreeWay(c *Client, opt ReadTreeOptions, stage1, stage2, stage3 Treeish) (*Index, error) { 131 idx, err := c.GitDir.ReadIndex() 132 if err != nil { 133 return nil, err 134 } 135 136 resetremovals, err := checkReadtreePrereqs(c, opt, idx) 137 if err != nil { 138 return nil, err 139 } 140 141 origMap := idx.GetMap() 142 143 base, err := GetIndexMap(c, stage1) 144 if err != nil { 145 return nil, err 146 } 147 148 ours, err := GetIndexMap(c, stage2) 149 if err != nil { 150 return nil, err 151 } 152 153 theirs, err := GetIndexMap(c, stage3) 154 if err != nil { 155 return nil, err 156 } 157 158 // Create a slice which contins all objects in base, ours, or theirs 159 var allPaths []*IndexEntry 160 for path, _ := range base { 161 allPaths = append(allPaths, &IndexEntry{PathName: path}) 162 } 163 for path, _ := range ours { 164 allPaths = append(allPaths, &IndexEntry{PathName: path}) 165 } 166 for path, _ := range theirs { 167 allPaths = append(allPaths, &IndexEntry{PathName: path}) 168 } 169 // Sort to ensure directories come before files. 170 sort.Sort(ByPath(allPaths)) 171 172 // Remove duplicates and exclude files that aren't part of the 173 // sparse checkout rules if applicable. 174 var allObjects []IndexPath 175 for i := range allPaths { 176 if i > 0 && allPaths[i].PathName == allPaths[i-1].PathName { 177 continue 178 } 179 allObjects = append(allObjects, allPaths[i].PathName) 180 } 181 var dirs []IndexPath 182 183 // Checking for merge conflict with index. If this seems like a confusing mess, it's mostly 184 // because it was written to pass the t1000-read-tree-m-3way test case from the official git 185 // test suite. 186 // 187 // The logic can probably be cleaned up. 188 for path, orig := range origMap { 189 o, ok := ours[path] 190 if !ok { 191 // If it's been added to the index in the same state as Stage 3, and it's not in 192 // stage 1 or 2 it's fine. 193 if !base.Contains(path) && !ours.Contains(path) && samePath(origMap, theirs, path) { 194 continue 195 } 196 197 return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path) 198 } 199 200 // Variable names mirror the O/A/B from the test suite, with "c" for contains 201 oc := base.Contains(path) 202 ac := ours.Contains(path) 203 bc := theirs.Contains(path) 204 205 if oc && ac && bc { 206 oa := samePath(base, ours, path) 207 ob := samePath(base, theirs, path) 208 209 // t1000-read-tree-m-3way.sh test 75 "must match A in O && A && B && O!=A && O==B case. 210 // (This means we can't error out if the Sha1s dont match.) 211 if !oa && ob { 212 continue 213 } 214 if oa && !ob { 215 // Relevent cases: 216 // Must match and be up-to-date in O && A && B && O==A && O!=B 217 // May match B in O && A && B && O==A && O!=B 218 b, ok := theirs[path] 219 if ok && b.Sha1 == orig.Sha1 { 220 continue 221 } else if !path.IsClean(c, o.Sha1) { 222 return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path) 223 } 224 } 225 } 226 // Must match and be up-to-date in !O && A && B && A != B case test from AND 227 // Must match and be up-to-date in O && A && B && A != B case test from 228 // t1000-read-tree-m-3way.sh in official git 229 if ac && bc && !samePath(ours, theirs, path) { 230 if !path.IsClean(c, o.Sha1) { 231 return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path) 232 } 233 } 234 235 // Must match and be up-to-date in O && A && !B && !B && O != A case AND 236 // Must match and be up-to-date in O && A && !B && !B && O == A case from 237 // t1000-read-tree-m-3way.sh in official git 238 if oc && ac && !bc { 239 if !path.IsClean(c, o.Sha1) { 240 return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path) 241 } 242 } 243 244 if o.Sha1 != orig.Sha1 { 245 return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path) 246 } 247 } 248 idx = NewIndex() 249 paths: 250 for _, path := range allObjects { 251 // Handle directory/file conflicts. 252 if base.HasDir(path) || ours.HasDir(path) || theirs.HasDir(path) { 253 if !opt.Merge && !opt.Reset { 254 // If not merging, the file wins. 255 // see http://www.stackoverflow.com/questions/52175720/how-does-git-read-tree-work-without-m-or-reset-option 256 continue 257 } 258 // Keep track of what was a directory so that other 259 // other paths know if they had a conflict higher 260 // up in the tree. 261 dirs = append(dirs, path) 262 263 // Add the non-directory version fo the appropriate stage 264 if p, ok := base[path]; ok { 265 idx.AddStage(c, path, p.Mode, p.Sha1, Stage1, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}) 266 } 267 if p, ok := ours[path]; ok { 268 idx.AddStage(c, path, p.Mode, p.Sha1, Stage2, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}) 269 } 270 if p, ok := theirs[path]; ok { 271 idx.AddStage(c, path, p.Mode, p.Sha1, Stage3, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}) 272 } 273 continue 274 } 275 276 // Handle the subfiles in any directory that had a conflict 277 // by just adding them in the appropriate stage, because 278 // there's no way for a directory and file to not be in 279 // conflict. 280 for _, d := range dirs { 281 if strings.HasPrefix(string(path), string(d+"/")) { 282 if p, ok := base[path]; ok { 283 if err := idx.AddStage(c, path, p.Mode, p.Sha1, Stage1, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil { 284 return nil, err 285 } 286 } 287 if p, ok := ours[path]; ok { 288 if err := idx.AddStage(c, path, p.Mode, p.Sha1, Stage2, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true, Replace: true}); err != nil { 289 return nil, err 290 } 291 } 292 if p, ok := theirs[path]; ok { 293 if err := idx.AddStage(c, path, p.Mode, p.Sha1, Stage3, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil { 294 return nil, err 295 } 296 } 297 continue paths 298 } 299 } 300 301 // From here on out, we assume everything is a file. 302 303 // All three trees are the same, don't do anything to the index. 304 if samePath(base, ours, path) && samePath(base, theirs, path) { 305 if err := idx.AddStage(c, path, ours[path].Mode, ours[path].Sha1, Stage0, ours[path].Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil { 306 panic(err) 307 } 308 continue 309 } 310 311 // If both stage2 and stage3 are the same, the work has been done in 312 // both branches, so collapse to stage0 (use our changes) 313 if samePath(ours, theirs, path) { 314 if ours.Contains(path) { 315 if err := idx.AddStage(c, path, ours[path].Mode, ours[path].Sha1, Stage0, ours[path].Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil { 316 panic(err) 317 } 318 continue 319 } 320 } 321 322 // If stage1 and stage2 are the same, our branch didn't do anything, 323 // but theirs did, so take their changes. 324 if samePath(base, ours, path) { 325 if theirs.Contains(path) { 326 if err := idx.AddStage(c, path, theirs[path].Mode, theirs[path].Sha1, Stage0, theirs[path].Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil { 327 panic(err) 328 } 329 continue 330 } 331 } 332 333 // If stage1 and stage3 are the same, we did something 334 // but they didn't, so take our changes 335 if samePath(base, theirs, path) { 336 if ours.Contains(path) { 337 o := ours[path] 338 if err := idx.AddStage(c, path, o.Mode, o.Sha1, Stage0, o.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil { 339 panic(err) 340 } 341 continue 342 } 343 } 344 345 // We couldn't short-circuit out, so add all three stages. 346 347 // Remove Stage0 if it exists. If it doesn't, then at worst we'll 348 // remove a stage that we're about to add back. 349 idx.RemoveFile(path) 350 351 if b, ok := base[path]; ok { 352 idx.AddStage(c, path, b.Mode, b.Sha1, Stage1, b.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}) 353 } 354 if o, ok := ours[path]; ok { 355 idx.AddStage(c, path, o.Mode, o.Sha1, Stage2, o.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}) 356 } 357 if t, ok := theirs[path]; ok { 358 idx.AddStage(c, path, t.Mode, t.Sha1, Stage3, t.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}) 359 } 360 } 361 362 if err := checkMergeAndUpdate(c, opt, origMap, idx, resetremovals); err != nil { 363 return nil, err 364 } 365 366 return idx, readtreeSaveIndex(c, opt, idx) 367 } 368 369 // ReadTreeFastForward will return a new Index with parent fast-forwarded to 370 // from parent to dst. Local modifications to the work tree will be preserved. 371 // If options.DryRun is not false, it will also be written to the Client's index file. 372 func ReadTreeFastForward(c *Client, opt ReadTreeOptions, parent, dst Treeish) (*Index, error) { 373 // This is the table of how fast-forward merges work from git-read-tree(1) 374 // I == Index, H == parent, and M == dst in their terminology. (ie. It's a 375 // fast-forward from H to M while the index is in state I.) 376 // 377 // I H M Result 378 // ------------------------------------------------------- 379 // 0 nothing nothing nothing (does not happen) 380 // 1 nothing nothing exists use M 381 // 2 nothing exists nothing remove path from index 382 // 3 nothing exists exists, use M if "initial checkout", 383 // H == M keep index otherwise 384 // exists, fail 385 // H != M 386 // 387 // clean I==H I==M 388 // ------------------ 389 // 4 yes N/A N/A nothing nothing keep index 390 // 5 no N/A N/A nothing nothing keep index 391 // 392 // 6 yes N/A yes nothing exists keep index 393 // 7 no N/A yes nothing exists keep index 394 // 8 yes N/A no nothing exists fail 395 // 9 no N/A no nothing exists fail 396 // 397 // 10 yes yes N/A exists nothing remove path from index 398 // 11 no yes N/A exists nothing fail 399 // 12 yes no N/A exists nothing fail 400 // 13 no no N/A exists nothing fail 401 // 402 // clean (H==M) 403 // ------ 404 // 14 yes exists exists keep index 405 // 15 no exists exists keep index 406 // 407 // clean I==H I==M (H!=M) 408 // ------------------ 409 // 16 yes no no exists exists fail 410 // 17 no no no exists exists fail 411 // 18 yes no yes exists exists keep index 412 // 19 no no yes exists exists keep index 413 // 20 yes yes no exists exists use M 414 // 21 no yes no exists exists fail 415 idx, err := c.GitDir.ReadIndex() 416 if err != nil { 417 return nil, err 418 } 419 420 resetremovals, err := checkReadtreePrereqs(c, opt, idx) 421 if err != nil { 422 return nil, err 423 } 424 425 I := idx.GetMap() 426 427 H, err := GetIndexMap(c, parent) 428 if err != nil { 429 return nil, err 430 } 431 432 M, err := GetIndexMap(c, dst) 433 if err != nil { 434 return nil, err 435 } 436 437 // Start by iterating through the index and handling cases 5-21. 438 // (We'll create a new index instead of trying to keep track of state 439 // of the existing index while iterating through it.) 440 newidx := NewIndex() 441 442 for pathname, IEntry := range I { 443 HEntry, HExists := H[pathname] 444 MEntry, MExists := M[pathname] 445 if !HExists && !MExists { 446 // Case 4-5 447 newidx.Objects = append(newidx.Objects, IEntry) 448 continue 449 } else if !HExists && MExists { 450 if IEntry.Sha1 == MEntry.Sha1 { 451 // Case 6-7 452 newidx.Objects = append(newidx.Objects, IEntry) 453 continue 454 } 455 // Case 8-9 456 return nil, fmt.Errorf("error: Entry '%s' would be overwritten by merge. Cannot merge.", pathname) 457 } else if HExists && !MExists { 458 if pathname.IsClean(c, IEntry.Sha1) && IEntry.Sha1 == HEntry.Sha1 { 459 // Case 10. Remove from the index. 460 // (Since it's a new index, we just don't add it) 461 continue 462 } 463 // Case 11 or 13 if it's not clean, case 12 if they don't match 464 if IEntry.Sha1 != HEntry.Sha1 { 465 return nil, fmt.Errorf("error: Entry '%s' would be overwritten by merge. Cannot merge.", pathname) 466 } 467 return nil, fmt.Errorf("Entry '%v' not uptodate. Cannot merge.", pathname) 468 } else { 469 if HEntry.Sha1 == MEntry.Sha1 { 470 // Case 14-15 471 newidx.Objects = append(newidx.Objects, IEntry) 472 continue 473 } 474 // H != M 475 if IEntry.Sha1 != HEntry.Sha1 && IEntry.Sha1 != MEntry.Sha1 { 476 // Case 16-17 477 return nil, fmt.Errorf("error: Entry '%s' would be overwritten by merge. Cannot merge.", pathname) 478 } else if IEntry.Sha1 != HEntry.Sha1 && IEntry.Sha1 == MEntry.Sha1 { 479 // Case 18-19 480 newidx.Objects = append(newidx.Objects, IEntry) 481 continue 482 } else if IEntry.Sha1 == HEntry.Sha1 && IEntry.Sha1 != MEntry.Sha1 { 483 if pathname.IsClean(c, IEntry.Sha1) { 484 // Case 20. Use M. 485 newidx.Objects = append(newidx.Objects, MEntry) 486 continue 487 } else { 488 return nil, fmt.Errorf("Entry '%v' not uptodate. Cannot merge.", pathname) 489 } 490 } 491 } 492 } 493 494 // Finally, handle the cases where it's in H or M but not I by going 495 // through the maps of H and M. 496 for pathname, MEntry := range M { 497 if _, IExists := I[pathname]; IExists { 498 // If it's in I, it was already handled above. 499 continue 500 } 501 HEntry, HExists := H[pathname] 502 if !HExists { 503 // It's in M but not I or H. Case 1. Use M. 504 newidx.Objects = append(newidx.Objects, MEntry) 505 continue 506 } 507 // Otherwise it's in both H and M but not I. Case 3. 508 if HEntry.Sha1 != MEntry.Sha1 { 509 if !c.GitDir.File("index").Exists() { 510 // Case 3 from the git-read-tree(1) is weird, but this 511 // is intended to handle it. If there is no index, add 512 // the file from M 513 newidx.Objects = append(newidx.Objects, MEntry) 514 } else { 515 return nil, fmt.Errorf("Could not fast-forward (case 3.)") 516 } 517 } else { 518 // It was unmodified between the two trees, but has been 519 // removed from the index. Keep the "Deleted" state by 520 // not adding it. 521 // If there is no index, however, we add it, since it's 522 // an initial checkout. 523 if !c.GitDir.File("index").Exists() { 524 newidx.Objects = append(newidx.Objects, MEntry) 525 } 526 } 527 } 528 529 // We need to make sure the number of index entries stays is correct, 530 // it's going to be an invalid index.. 531 newidx.NumberIndexEntries = uint32(len(newidx.Objects)) 532 if err := checkMergeAndUpdate(c, opt, I, newidx, resetremovals); err != nil { 533 return nil, err 534 } 535 536 return newidx, readtreeSaveIndex(c, opt, newidx) 537 } 538 539 // Helper to ensure the DryRun option gets checked no matter what the code path 540 // for ReadTree. 541 func readtreeSaveIndex(c *Client, opt ReadTreeOptions, i *Index) error { 542 if !opt.DryRun { 543 if opt.IndexOutput == "" { 544 opt.IndexOutput = "index" 545 } 546 f, err := c.GitDir.Create(File(opt.IndexOutput)) 547 if err != nil { 548 return err 549 } 550 defer f.Close() 551 return i.WriteIndex(f) 552 } 553 return nil 554 } 555 556 // Check if the read-tree can be performed. Returns a list of files that need to be 557 // removed if --reset is specified. 558 func checkReadtreePrereqs(c *Client, opt ReadTreeOptions, idx *Index) ([]File, error) { 559 if opt.Update && opt.Prefix == "" && !opt.Merge && !opt.Reset { 560 return nil, fmt.Errorf("-u is meaningless without -m, --reset or --prefix") 561 } 562 if (opt.Prefix != "" && (opt.Merge || opt.Reset)) || 563 (opt.Merge && (opt.Prefix != "" || opt.Reset)) || 564 (opt.Reset && (opt.Prefix != "" || opt.Merge)) { 565 return nil, fmt.Errorf("Can only specify one of -u, --reset, or --prefix") 566 } 567 if opt.ExcludePerDirectory != "" && !opt.Update { 568 return nil, fmt.Errorf("--exclude-per-directory is meaningless without -u") 569 } 570 if idx == nil { 571 return nil, nil 572 } 573 574 toremove := make([]File, 0) 575 for _, entry := range idx.Objects { 576 if entry.Stage() != Stage0 { 577 if opt.Merge { 578 return nil, fmt.Errorf("You need to resolve your current index first") 579 } 580 if opt.Reset { 581 f, err := entry.PathName.FilePath(c) 582 if err != nil { 583 } 584 // Indexes are sorted, so only check if the last file is the 585 // same 586 if len(toremove) >= 1 && toremove[len(toremove)-1] == f { 587 continue 588 } 589 toremove = append(toremove, f) 590 } 591 } 592 } 593 return toremove, nil 594 595 } 596 597 // Reads a tree into the index. If DryRun is not false, it will also be written 598 // to disk. 599 func ReadTree(c *Client, opt ReadTreeOptions, tree Treeish) (*Index, error) { 600 idx, err := c.GitDir.ReadIndex() 601 if err != nil { 602 idx = NewIndex() 603 } 604 origMap := idx.GetMap() 605 606 resetremovals, err := checkReadtreePrereqs(c, opt, idx) 607 if err != nil { 608 return nil, err 609 } 610 // Convert to a new map before doing anything, so that checkMergeAndUpdate 611 // can compare the original update after we reset. 612 if opt.Empty { 613 idx.NumberIndexEntries = 0 614 idx.Objects = make([]*IndexEntry, 0) 615 if err := checkMergeAndUpdate(c, opt, origMap, idx, resetremovals); err != nil { 616 return nil, err 617 } 618 return idx, readtreeSaveIndex(c, opt, idx) 619 } 620 newidx := NewIndex() 621 if err := newidx.ResetIndex(c, tree); err != nil { 622 return nil, err 623 } 624 for _, entry := range newidx.Objects { 625 if opt.Prefix != "" { 626 // Add it to the original index with the prefix 627 entry.PathName = IndexPath(opt.Prefix) + entry.PathName 628 if err := idx.AddStage(c, entry.PathName, entry.Mode, entry.Sha1, Stage0, entry.Fsize, entry.Mtime, UpdateIndexOptions{Add: true}); err != nil { 629 return nil, err 630 } 631 } 632 if opt.Merge { 633 if oldentry, ok := origMap[entry.PathName]; ok { 634 newsha, _, err := HashFile("blob", string(entry.PathName)) 635 if err != nil && newsha == entry.Sha1 { 636 entry.Ctime, entry.Ctimenano = oldentry.Ctime, oldentry.Ctimenano 637 entry.Mtime = oldentry.Mtime 638 } 639 } 640 } 641 } 642 643 if opt.Prefix == "" { 644 idx = newidx 645 } 646 647 if err := checkMergeAndUpdate(c, opt, origMap, idx, resetremovals); err != nil { 648 return nil, err 649 } 650 return idx, readtreeSaveIndex(c, opt, idx) 651 } 652 653 // Check if the merge would overwrite any modified files and return an error if so (unless --reset), 654 // then update the file system. 655 func checkMergeAndUpdate(c *Client, opt ReadTreeOptions, origidx map[IndexPath]*IndexEntry, newidx *Index, resetremovals []File) error { 656 sparsePatterns := parseSparsePatterns(c, &opt) 657 if !opt.NoSparseCheckout { 658 leavesFile := false 659 newSparse := false 660 for _, entry := range newidx.Objects { 661 if !checkSparseMatches(c, opt, entry.PathName, sparsePatterns) { 662 if orig, ok := origidx[entry.PathName]; ok && !orig.SkipWorktree() { 663 newSparse = true 664 } 665 entry.SetSkipWorktree(true) 666 if newidx.Version <= 2 { 667 newidx.Version = 3 668 } 669 } else { 670 leavesFile = true 671 } 672 } 673 for _, entry := range origidx { 674 if checkSparseMatches(c, opt, entry.PathName, sparsePatterns) { 675 // This isn't necessarily true, but if it is we don't error out in 676 // order to make let make the git test t1011.19 pass. 677 // 678 // t1011-read-tree-sparse-checkout works in mysterious ways. 679 leavesFile = true 680 } 681 } 682 if !leavesFile && newSparse { 683 return fmt.Errorf("Sparse checkout would leave no file in work tree") 684 } 685 } 686 687 // Check for invalid path names which are disallowed by git 688 disallow := func(path, piece string) bool { 689 casesensitive := true 690 if protectHFS(c) { 691 // HFS is case insensitive and ignores zero width 692 // non-joiners anywhere in the file path 693 casesensitive = false 694 path = strings.Replace(path, "\u200c", "", -1) 695 path = strings.TrimSpace(path) 696 } 697 if protectNTFS(c) { 698 // git treats "protectNTFS" as "protect the filesystem for 699 // windows", which means it also inherits weird dos filename 700 // restrictions such as 8.3 length filenames and ~ and 701 // no dots at the end of a path because that would just be 702 // an empty file extension. 703 casesensitive = false 704 705 re := regexp.MustCompile(`(?i)(^|\\|/)git~[0-9]+($|\\|/)`) 706 if re.MatchString(path) { 707 // Handle the case where the "." was removed and 708 // we're left with nothing, or where the the .git 709 // directory is 8.3 encoded. 710 return true 711 } 712 // Handle space or dots at end of a filename and backslashes 713 re = regexp.MustCompile(`(?i)(^|\\|/)\.git( |[.])*($|\\|/)`) 714 if re.MatchString(path) { 715 return true 716 } 717 } 718 719 if !casesensitive { 720 path = strings.ToLower(path) 721 } 722 723 if path == piece { 724 return true 725 } 726 if strings.HasSuffix(path, "/"+piece) || strings.HasPrefix(path, piece+"/") { 727 return true 728 } 729 if strings.Index(path, "/"+piece+"/") != -1 { 730 return true 731 } 732 return false 733 } 734 for _, entry := range newidx.Objects { 735 path := entry.PathName.String() 736 737 if disallow(path, ".") || disallow(path, "..") || disallow(path, ".git") { 738 return fmt.Errorf("Invalid path %v", path) 739 } 740 } 741 // Keep a list of index entries to be updated by CheckoutIndex. 742 files := make([]File, 0, len(newidx.Objects)) 743 744 if opt.Merge || opt.Reset || opt.Update { 745 // Verify that merge won't overwrite anything that's been modified locally. 746 for _, entry := range newidx.Objects { 747 f, err := entry.PathName.FilePath(c) 748 if err != nil { 749 return err 750 } 751 752 if opt.Update && f.IsDir() { 753 untracked, err := LsFiles(c, LsFilesOptions{Others: true, Modified: true}, []File{f}) 754 if err != nil { 755 return err 756 } 757 if len(untracked) > 0 { 758 return fmt.Errorf("error: Updating '%s%s' would lose untracked files in it", c.SuperPrefix, entry.PathName) 759 } 760 } 761 if entry.Stage() != Stage0 { 762 // Don't check unmerged entries. One will always 763 // conflict, which means that -u won't work 764 // if we check them. 765 // (We also don't add them to files, so they won't 766 // make it to checkoutindex 767 continue 768 } 769 if entry.SkipWorktree() { 770 continue 771 } 772 if opt.Update && !f.Exists() { 773 // It doesn't exist on the filesystem, so it should be checked out. 774 files = append(files, f) 775 continue 776 } 777 orig, ok := origidx[entry.PathName] 778 if !ok { 779 // If it wasn't in the original index, make sure 780 // we check it out after verifying there's not 781 // already something there. 782 if opt.Update && f.Exists() { 783 lsopts := LsFilesOptions{Others: true} 784 if opt.ExcludePerDirectory != "" { 785 lsopts.ExcludePerDirectory = []File{File(opt.ExcludePerDirectory)} 786 } 787 untracked, err := LsFiles(c, lsopts, []File{f}) 788 if err != nil { 789 return err 790 } 791 if len(untracked) > 0 { 792 if !entry.PathName.IsClean(c, entry.Sha1) { 793 return fmt.Errorf("Untracked working tree file '%v' would be overwritten by merge", f) 794 } 795 } 796 } 797 file, err := entry.PathName.FilePath(c) 798 if err != nil { 799 return err 800 } 801 files = append(files, file) 802 continue 803 } 804 805 if orig.Sha1 == entry.Sha1 { 806 // Nothing was modified, so don't bother checking 807 // anything out 808 continue 809 } 810 if entry.PathName.IsClean(c, orig.Sha1) { 811 // it hasn't been modified locally, so we want to 812 // make sure the newidx version is checked out. 813 file, err := entry.PathName.FilePath(c) 814 if err != nil { 815 return err 816 } 817 files = append(files, file) 818 continue 819 } else { 820 // There are local unmodified changes on the filesystem 821 // from the original that would be lost by -u, so return 822 // an error unless --reset is specified. 823 if !opt.Reset { 824 return fmt.Errorf("%s has local changes. Can not merge.", entry.PathName) 825 } else { 826 // with --reset, checkout the file anyways. 827 file, err := entry.PathName.FilePath(c) 828 if err != nil { 829 return err 830 } 831 files = append(files, file) 832 } 833 } 834 } 835 } 836 837 if !opt.DryRun && (opt.Update || opt.Reset) { 838 if err := CheckoutIndexUncommited(c, newidx, CheckoutIndexOptions{Quiet: true, Force: true, Prefix: opt.Prefix}, files); err != nil { 839 return err 840 } 841 842 // Convert to a map for constant time lookup in our loop.. 843 newidxMap := newidx.GetMap() 844 845 // Before returning, delete anything that was in the old index, removed 846 // from the new index, and hasn't been changed on the filesystem. 847 for path, entry := range origidx { 848 if _, ok := newidxMap[path]; ok { 849 // It was already handled by checkout-index 850 continue 851 } 852 file, err := path.FilePath(c) 853 if err != nil { 854 // Don't error out since we've already 855 // mucked up other stuff, just carry 856 // on to the next file. 857 fmt.Fprintf(os.Stderr, "%v\n", err) 858 continue 859 860 } 861 862 // It was deleted from the new index, but was in the 863 // original index, so delete it if it hasn't been 864 // changed on the filesystem. 865 if path.IsClean(c, entry.Sha1) { 866 if err := removeFileClean(file); err != nil { 867 return err 868 } 869 } else if !opt.NoSparseCheckout { 870 if !checkSparseMatches(c, opt, path, sparsePatterns) { 871 if file.Exists() { 872 if err := removeFileClean(file); err != nil { 873 return err 874 } 875 } 876 } 877 } 878 } 879 880 // Update stat information for things changed by CheckoutIndex, and remove anything 881 // with the SkipWorktree bit set. 882 for _, entry := range newidx.Objects { 883 f, err := entry.PathName.FilePath(c) 884 if err != nil { 885 return err 886 } 887 if f.Exists() { 888 if entry.SkipWorktree() { 889 if entry.PathName.IsClean(c, entry.Sha1) { 890 if err := removeFileClean(f); err != nil { 891 return err 892 } 893 } 894 continue 895 } 896 if err := entry.RefreshStat(c); err != nil { 897 return err 898 } 899 } 900 } 901 902 if opt.Reset { 903 for _, file := range resetremovals { 904 // It may have been removed by the removal loop above 905 if file.Exists() { 906 if err := file.Remove(); err != nil { 907 return err 908 } 909 } 910 } 911 } 912 } 913 return nil 914 } 915 916 func removeFileClean(f File) error { 917 if err := f.Remove(); err != nil { 918 return err 919 } 920 // If there's nothing left in the directory, remove 921 dir := filepath.Dir(f.String()) 922 files, err := ioutil.ReadDir(dir) 923 if err != nil { 924 return err 925 } 926 if len(files) == 0 { 927 if err := os.RemoveAll(dir); err != nil { 928 return err 929 } 930 } 931 return nil 932 }