本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

955 lines
20KB

  1. package git
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. stdioutil "io/ioutil"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "sync"
  12. "gopkg.in/src-d/go-git.v4/config"
  13. "gopkg.in/src-d/go-git.v4/plumbing"
  14. "gopkg.in/src-d/go-git.v4/plumbing/filemode"
  15. "gopkg.in/src-d/go-git.v4/plumbing/format/gitignore"
  16. "gopkg.in/src-d/go-git.v4/plumbing/format/index"
  17. "gopkg.in/src-d/go-git.v4/plumbing/object"
  18. "gopkg.in/src-d/go-git.v4/plumbing/storer"
  19. "gopkg.in/src-d/go-git.v4/utils/ioutil"
  20. "gopkg.in/src-d/go-git.v4/utils/merkletrie"
  21. "gopkg.in/src-d/go-billy.v4"
  22. "gopkg.in/src-d/go-billy.v4/util"
  23. )
  24. var (
  25. ErrWorktreeNotClean = errors.New("worktree is not clean")
  26. ErrSubmoduleNotFound = errors.New("submodule not found")
  27. ErrUnstagedChanges = errors.New("worktree contains unstaged changes")
  28. ErrGitModulesSymlink = errors.New(gitmodulesFile + " is a symlink")
  29. ErrNonFastForwardUpdate = errors.New("non-fast-forward update")
  30. )
  31. // Worktree represents a git worktree.
  32. type Worktree struct {
  33. // Filesystem underlying filesystem.
  34. Filesystem billy.Filesystem
  35. // External excludes not found in the repository .gitignore
  36. Excludes []gitignore.Pattern
  37. r *Repository
  38. }
  39. // Pull incorporates changes from a remote repository into the current branch.
  40. // Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are
  41. // no changes to be fetched, or an error.
  42. //
  43. // Pull only supports merges where the can be resolved as a fast-forward.
  44. func (w *Worktree) Pull(o *PullOptions) error {
  45. return w.PullContext(context.Background(), o)
  46. }
  47. // PullContext incorporates changes from a remote repository into the current
  48. // branch. Returns nil if the operation is successful, NoErrAlreadyUpToDate if
  49. // there are no changes to be fetched, or an error.
  50. //
  51. // Pull only supports merges where the can be resolved as a fast-forward.
  52. //
  53. // The provided Context must be non-nil. If the context expires before the
  54. // operation is complete, an error is returned. The context only affects to the
  55. // transport operations.
  56. func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error {
  57. if err := o.Validate(); err != nil {
  58. return err
  59. }
  60. remote, err := w.r.Remote(o.RemoteName)
  61. if err != nil {
  62. return err
  63. }
  64. fetchHead, err := remote.fetch(ctx, &FetchOptions{
  65. RemoteName: o.RemoteName,
  66. Depth: o.Depth,
  67. Auth: o.Auth,
  68. Progress: o.Progress,
  69. Force: o.Force,
  70. })
  71. updated := true
  72. if err == NoErrAlreadyUpToDate {
  73. updated = false
  74. } else if err != nil {
  75. return err
  76. }
  77. ref, err := storer.ResolveReference(fetchHead, o.ReferenceName)
  78. if err != nil {
  79. return err
  80. }
  81. head, err := w.r.Head()
  82. if err == nil {
  83. if !updated && head.Hash() == ref.Hash() {
  84. return NoErrAlreadyUpToDate
  85. }
  86. ff, err := isFastForward(w.r.Storer, head.Hash(), ref.Hash())
  87. if err != nil {
  88. return err
  89. }
  90. if !ff {
  91. return ErrNonFastForwardUpdate
  92. }
  93. }
  94. if err != nil && err != plumbing.ErrReferenceNotFound {
  95. return err
  96. }
  97. if err := w.updateHEAD(ref.Hash()); err != nil {
  98. return err
  99. }
  100. if err := w.Reset(&ResetOptions{
  101. Mode: MergeReset,
  102. Commit: ref.Hash(),
  103. }); err != nil {
  104. return err
  105. }
  106. if o.RecurseSubmodules != NoRecurseSubmodules {
  107. return w.updateSubmodules(&SubmoduleUpdateOptions{
  108. RecurseSubmodules: o.RecurseSubmodules,
  109. Auth: o.Auth,
  110. })
  111. }
  112. return nil
  113. }
  114. func (w *Worktree) updateSubmodules(o *SubmoduleUpdateOptions) error {
  115. s, err := w.Submodules()
  116. if err != nil {
  117. return err
  118. }
  119. o.Init = true
  120. return s.Update(o)
  121. }
  122. // Checkout switch branches or restore working tree files.
  123. func (w *Worktree) Checkout(opts *CheckoutOptions) error {
  124. if err := opts.Validate(); err != nil {
  125. return err
  126. }
  127. if opts.Create {
  128. if err := w.createBranch(opts); err != nil {
  129. return err
  130. }
  131. }
  132. c, err := w.getCommitFromCheckoutOptions(opts)
  133. if err != nil {
  134. return err
  135. }
  136. ro := &ResetOptions{Commit: c, Mode: MergeReset}
  137. if opts.Force {
  138. ro.Mode = HardReset
  139. } else if opts.Keep {
  140. ro.Mode = SoftReset
  141. }
  142. if !opts.Hash.IsZero() && !opts.Create {
  143. err = w.setHEADToCommit(opts.Hash)
  144. } else {
  145. err = w.setHEADToBranch(opts.Branch, c)
  146. }
  147. if err != nil {
  148. return err
  149. }
  150. return w.Reset(ro)
  151. }
  152. func (w *Worktree) createBranch(opts *CheckoutOptions) error {
  153. _, err := w.r.Storer.Reference(opts.Branch)
  154. if err == nil {
  155. return fmt.Errorf("a branch named %q already exists", opts.Branch)
  156. }
  157. if err != plumbing.ErrReferenceNotFound {
  158. return err
  159. }
  160. if opts.Hash.IsZero() {
  161. ref, err := w.r.Head()
  162. if err != nil {
  163. return err
  164. }
  165. opts.Hash = ref.Hash()
  166. }
  167. return w.r.Storer.SetReference(
  168. plumbing.NewHashReference(opts.Branch, opts.Hash),
  169. )
  170. }
  171. func (w *Worktree) getCommitFromCheckoutOptions(opts *CheckoutOptions) (plumbing.Hash, error) {
  172. if !opts.Hash.IsZero() {
  173. return opts.Hash, nil
  174. }
  175. b, err := w.r.Reference(opts.Branch, true)
  176. if err != nil {
  177. return plumbing.ZeroHash, err
  178. }
  179. if !b.Name().IsTag() {
  180. return b.Hash(), nil
  181. }
  182. o, err := w.r.Object(plumbing.AnyObject, b.Hash())
  183. if err != nil {
  184. return plumbing.ZeroHash, err
  185. }
  186. switch o := o.(type) {
  187. case *object.Tag:
  188. if o.TargetType != plumbing.CommitObject {
  189. return plumbing.ZeroHash, fmt.Errorf("unsupported tag object target %q", o.TargetType)
  190. }
  191. return o.Target, nil
  192. case *object.Commit:
  193. return o.Hash, nil
  194. }
  195. return plumbing.ZeroHash, fmt.Errorf("unsupported tag target %q", o.Type())
  196. }
  197. func (w *Worktree) setHEADToCommit(commit plumbing.Hash) error {
  198. head := plumbing.NewHashReference(plumbing.HEAD, commit)
  199. return w.r.Storer.SetReference(head)
  200. }
  201. func (w *Worktree) setHEADToBranch(branch plumbing.ReferenceName, commit plumbing.Hash) error {
  202. target, err := w.r.Storer.Reference(branch)
  203. if err != nil {
  204. return err
  205. }
  206. var head *plumbing.Reference
  207. if target.Name().IsBranch() {
  208. head = plumbing.NewSymbolicReference(plumbing.HEAD, target.Name())
  209. } else {
  210. head = plumbing.NewHashReference(plumbing.HEAD, commit)
  211. }
  212. return w.r.Storer.SetReference(head)
  213. }
  214. // Reset the worktree to a specified state.
  215. func (w *Worktree) Reset(opts *ResetOptions) error {
  216. if err := opts.Validate(w.r); err != nil {
  217. return err
  218. }
  219. if opts.Mode == MergeReset {
  220. unstaged, err := w.containsUnstagedChanges()
  221. if err != nil {
  222. return err
  223. }
  224. if unstaged {
  225. return ErrUnstagedChanges
  226. }
  227. }
  228. if err := w.setHEADCommit(opts.Commit); err != nil {
  229. return err
  230. }
  231. if opts.Mode == SoftReset {
  232. return nil
  233. }
  234. t, err := w.getTreeFromCommitHash(opts.Commit)
  235. if err != nil {
  236. return err
  237. }
  238. if opts.Mode == MixedReset || opts.Mode == MergeReset || opts.Mode == HardReset {
  239. if err := w.resetIndex(t); err != nil {
  240. return err
  241. }
  242. }
  243. if opts.Mode == MergeReset || opts.Mode == HardReset {
  244. if err := w.resetWorktree(t); err != nil {
  245. return err
  246. }
  247. }
  248. return nil
  249. }
  250. func (w *Worktree) resetIndex(t *object.Tree) error {
  251. idx, err := w.r.Storer.Index()
  252. if err != nil {
  253. return err
  254. }
  255. b := newIndexBuilder(idx)
  256. changes, err := w.diffTreeWithStaging(t, true)
  257. if err != nil {
  258. return err
  259. }
  260. for _, ch := range changes {
  261. a, err := ch.Action()
  262. if err != nil {
  263. return err
  264. }
  265. var name string
  266. var e *object.TreeEntry
  267. switch a {
  268. case merkletrie.Modify, merkletrie.Insert:
  269. name = ch.To.String()
  270. e, err = t.FindEntry(name)
  271. if err != nil {
  272. return err
  273. }
  274. case merkletrie.Delete:
  275. name = ch.From.String()
  276. }
  277. b.Remove(name)
  278. if e == nil {
  279. continue
  280. }
  281. b.Add(&index.Entry{
  282. Name: name,
  283. Hash: e.Hash,
  284. Mode: e.Mode,
  285. })
  286. }
  287. b.Write(idx)
  288. return w.r.Storer.SetIndex(idx)
  289. }
  290. func (w *Worktree) resetWorktree(t *object.Tree) error {
  291. changes, err := w.diffStagingWithWorktree(true)
  292. if err != nil {
  293. return err
  294. }
  295. idx, err := w.r.Storer.Index()
  296. if err != nil {
  297. return err
  298. }
  299. b := newIndexBuilder(idx)
  300. for _, ch := range changes {
  301. if err := w.checkoutChange(ch, t, b); err != nil {
  302. return err
  303. }
  304. }
  305. b.Write(idx)
  306. return w.r.Storer.SetIndex(idx)
  307. }
  308. func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *indexBuilder) error {
  309. a, err := ch.Action()
  310. if err != nil {
  311. return err
  312. }
  313. var e *object.TreeEntry
  314. var name string
  315. var isSubmodule bool
  316. switch a {
  317. case merkletrie.Modify, merkletrie.Insert:
  318. name = ch.To.String()
  319. e, err = t.FindEntry(name)
  320. if err != nil {
  321. return err
  322. }
  323. isSubmodule = e.Mode == filemode.Submodule
  324. case merkletrie.Delete:
  325. return rmFileAndDirIfEmpty(w.Filesystem, ch.From.String())
  326. }
  327. if isSubmodule {
  328. return w.checkoutChangeSubmodule(name, a, e, idx)
  329. }
  330. return w.checkoutChangeRegularFile(name, a, t, e, idx)
  331. }
  332. func (w *Worktree) containsUnstagedChanges() (bool, error) {
  333. ch, err := w.diffStagingWithWorktree(false)
  334. if err != nil {
  335. return false, err
  336. }
  337. for _, c := range ch {
  338. a, err := c.Action()
  339. if err != nil {
  340. return false, err
  341. }
  342. if a == merkletrie.Insert {
  343. continue
  344. }
  345. return true, nil
  346. }
  347. return false, nil
  348. }
  349. func (w *Worktree) setHEADCommit(commit plumbing.Hash) error {
  350. head, err := w.r.Reference(plumbing.HEAD, false)
  351. if err != nil {
  352. return err
  353. }
  354. if head.Type() == plumbing.HashReference {
  355. head = plumbing.NewHashReference(plumbing.HEAD, commit)
  356. return w.r.Storer.SetReference(head)
  357. }
  358. branch, err := w.r.Reference(head.Target(), false)
  359. if err != nil {
  360. return err
  361. }
  362. if !branch.Name().IsBranch() {
  363. return fmt.Errorf("invalid HEAD target should be a branch, found %s", branch.Type())
  364. }
  365. branch = plumbing.NewHashReference(branch.Name(), commit)
  366. return w.r.Storer.SetReference(branch)
  367. }
  368. func (w *Worktree) checkoutChangeSubmodule(name string,
  369. a merkletrie.Action,
  370. e *object.TreeEntry,
  371. idx *indexBuilder,
  372. ) error {
  373. switch a {
  374. case merkletrie.Modify:
  375. sub, err := w.Submodule(name)
  376. if err != nil {
  377. return err
  378. }
  379. if !sub.initialized {
  380. return nil
  381. }
  382. return w.addIndexFromTreeEntry(name, e, idx)
  383. case merkletrie.Insert:
  384. mode, err := e.Mode.ToOSFileMode()
  385. if err != nil {
  386. return err
  387. }
  388. if err := w.Filesystem.MkdirAll(name, mode); err != nil {
  389. return err
  390. }
  391. return w.addIndexFromTreeEntry(name, e, idx)
  392. }
  393. return nil
  394. }
  395. func (w *Worktree) checkoutChangeRegularFile(name string,
  396. a merkletrie.Action,
  397. t *object.Tree,
  398. e *object.TreeEntry,
  399. idx *indexBuilder,
  400. ) error {
  401. switch a {
  402. case merkletrie.Modify:
  403. idx.Remove(name)
  404. // to apply perm changes the file is deleted, billy doesn't implement
  405. // chmod
  406. if err := w.Filesystem.Remove(name); err != nil {
  407. return err
  408. }
  409. fallthrough
  410. case merkletrie.Insert:
  411. f, err := t.File(name)
  412. if err != nil {
  413. return err
  414. }
  415. if err := w.checkoutFile(f); err != nil {
  416. return err
  417. }
  418. return w.addIndexFromFile(name, e.Hash, idx)
  419. }
  420. return nil
  421. }
  422. var copyBufferPool = sync.Pool{
  423. New: func() interface{} {
  424. return make([]byte, 32*1024)
  425. },
  426. }
  427. func (w *Worktree) checkoutFile(f *object.File) (err error) {
  428. mode, err := f.Mode.ToOSFileMode()
  429. if err != nil {
  430. return
  431. }
  432. if mode&os.ModeSymlink != 0 {
  433. return w.checkoutFileSymlink(f)
  434. }
  435. from, err := f.Reader()
  436. if err != nil {
  437. return
  438. }
  439. defer ioutil.CheckClose(from, &err)
  440. to, err := w.Filesystem.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm())
  441. if err != nil {
  442. return
  443. }
  444. defer ioutil.CheckClose(to, &err)
  445. buf := copyBufferPool.Get().([]byte)
  446. _, err = io.CopyBuffer(to, from, buf)
  447. copyBufferPool.Put(buf)
  448. return
  449. }
  450. func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) {
  451. from, err := f.Reader()
  452. if err != nil {
  453. return
  454. }
  455. defer ioutil.CheckClose(from, &err)
  456. bytes, err := stdioutil.ReadAll(from)
  457. if err != nil {
  458. return
  459. }
  460. err = w.Filesystem.Symlink(string(bytes), f.Name)
  461. // On windows, this might fail.
  462. // Follow Git on Windows behavior by writing the link as it is.
  463. if err != nil && isSymlinkWindowsNonAdmin(err) {
  464. mode, _ := f.Mode.ToOSFileMode()
  465. to, err := w.Filesystem.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm())
  466. if err != nil {
  467. return err
  468. }
  469. defer ioutil.CheckClose(to, &err)
  470. _, err = to.Write(bytes)
  471. return err
  472. }
  473. return
  474. }
  475. func (w *Worktree) addIndexFromTreeEntry(name string, f *object.TreeEntry, idx *indexBuilder) error {
  476. idx.Remove(name)
  477. idx.Add(&index.Entry{
  478. Hash: f.Hash,
  479. Name: name,
  480. Mode: filemode.Submodule,
  481. })
  482. return nil
  483. }
  484. func (w *Worktree) addIndexFromFile(name string, h plumbing.Hash, idx *indexBuilder) error {
  485. idx.Remove(name)
  486. fi, err := w.Filesystem.Lstat(name)
  487. if err != nil {
  488. return err
  489. }
  490. mode, err := filemode.NewFromOSFileMode(fi.Mode())
  491. if err != nil {
  492. return err
  493. }
  494. e := &index.Entry{
  495. Hash: h,
  496. Name: name,
  497. Mode: mode,
  498. ModifiedAt: fi.ModTime(),
  499. Size: uint32(fi.Size()),
  500. }
  501. // if the FileInfo.Sys() comes from os the ctime, dev, inode, uid and gid
  502. // can be retrieved, otherwise this doesn't apply
  503. if fillSystemInfo != nil {
  504. fillSystemInfo(e, fi.Sys())
  505. }
  506. idx.Add(e)
  507. return nil
  508. }
  509. func (w *Worktree) getTreeFromCommitHash(commit plumbing.Hash) (*object.Tree, error) {
  510. c, err := w.r.CommitObject(commit)
  511. if err != nil {
  512. return nil, err
  513. }
  514. return c.Tree()
  515. }
  516. var fillSystemInfo func(e *index.Entry, sys interface{})
  517. const gitmodulesFile = ".gitmodules"
  518. // Submodule returns the submodule with the given name
  519. func (w *Worktree) Submodule(name string) (*Submodule, error) {
  520. l, err := w.Submodules()
  521. if err != nil {
  522. return nil, err
  523. }
  524. for _, m := range l {
  525. if m.Config().Name == name {
  526. return m, nil
  527. }
  528. }
  529. return nil, ErrSubmoduleNotFound
  530. }
  531. // Submodules returns all the available submodules
  532. func (w *Worktree) Submodules() (Submodules, error) {
  533. l := make(Submodules, 0)
  534. m, err := w.readGitmodulesFile()
  535. if err != nil || m == nil {
  536. return l, err
  537. }
  538. c, err := w.r.Config()
  539. if err != nil {
  540. return nil, err
  541. }
  542. for _, s := range m.Submodules {
  543. l = append(l, w.newSubmodule(s, c.Submodules[s.Name]))
  544. }
  545. return l, nil
  546. }
  547. func (w *Worktree) newSubmodule(fromModules, fromConfig *config.Submodule) *Submodule {
  548. m := &Submodule{w: w}
  549. m.initialized = fromConfig != nil
  550. if !m.initialized {
  551. m.c = fromModules
  552. return m
  553. }
  554. m.c = fromConfig
  555. m.c.Path = fromModules.Path
  556. return m
  557. }
  558. func (w *Worktree) isSymlink(path string) bool {
  559. if s, err := w.Filesystem.Lstat(path); err == nil {
  560. return s.Mode()&os.ModeSymlink != 0
  561. }
  562. return false
  563. }
  564. func (w *Worktree) readGitmodulesFile() (*config.Modules, error) {
  565. if w.isSymlink(gitmodulesFile) {
  566. return nil, ErrGitModulesSymlink
  567. }
  568. f, err := w.Filesystem.Open(gitmodulesFile)
  569. if err != nil {
  570. if os.IsNotExist(err) {
  571. return nil, nil
  572. }
  573. return nil, err
  574. }
  575. defer f.Close()
  576. input, err := stdioutil.ReadAll(f)
  577. if err != nil {
  578. return nil, err
  579. }
  580. m := config.NewModules()
  581. return m, m.Unmarshal(input)
  582. }
  583. // Clean the worktree by removing untracked files.
  584. // An empty dir could be removed - this is what `git clean -f -d .` does.
  585. func (w *Worktree) Clean(opts *CleanOptions) error {
  586. s, err := w.Status()
  587. if err != nil {
  588. return err
  589. }
  590. root := ""
  591. files, err := w.Filesystem.ReadDir(root)
  592. if err != nil {
  593. return err
  594. }
  595. return w.doClean(s, opts, root, files)
  596. }
  597. func (w *Worktree) doClean(status Status, opts *CleanOptions, dir string, files []os.FileInfo) error {
  598. for _, fi := range files {
  599. if fi.Name() == GitDirName {
  600. continue
  601. }
  602. // relative path under the root
  603. path := filepath.Join(dir, fi.Name())
  604. if fi.IsDir() {
  605. if !opts.Dir {
  606. continue
  607. }
  608. subfiles, err := w.Filesystem.ReadDir(path)
  609. if err != nil {
  610. return err
  611. }
  612. err = w.doClean(status, opts, path, subfiles)
  613. if err != nil {
  614. return err
  615. }
  616. } else {
  617. if status.IsUntracked(path) {
  618. if err := w.Filesystem.Remove(path); err != nil {
  619. return err
  620. }
  621. }
  622. }
  623. }
  624. if opts.Dir {
  625. return doCleanDirectories(w.Filesystem, dir)
  626. }
  627. return nil
  628. }
  629. // GrepResult is structure of a grep result.
  630. type GrepResult struct {
  631. // FileName is the name of file which contains match.
  632. FileName string
  633. // LineNumber is the line number of a file at which a match was found.
  634. LineNumber int
  635. // Content is the content of the file at the matching line.
  636. Content string
  637. // TreeName is the name of the tree (reference name/commit hash) at
  638. // which the match was performed.
  639. TreeName string
  640. }
  641. func (gr GrepResult) String() string {
  642. return fmt.Sprintf("%s:%s:%d:%s", gr.TreeName, gr.FileName, gr.LineNumber, gr.Content)
  643. }
  644. // Grep performs grep on a worktree.
  645. func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) {
  646. if err := opts.Validate(w); err != nil {
  647. return nil, err
  648. }
  649. // Obtain commit hash from options (CommitHash or ReferenceName).
  650. var commitHash plumbing.Hash
  651. // treeName contains the value of TreeName in GrepResult.
  652. var treeName string
  653. if opts.ReferenceName != "" {
  654. ref, err := w.r.Reference(opts.ReferenceName, true)
  655. if err != nil {
  656. return nil, err
  657. }
  658. commitHash = ref.Hash()
  659. treeName = opts.ReferenceName.String()
  660. } else if !opts.CommitHash.IsZero() {
  661. commitHash = opts.CommitHash
  662. treeName = opts.CommitHash.String()
  663. }
  664. // Obtain a tree from the commit hash and get a tracked files iterator from
  665. // the tree.
  666. tree, err := w.getTreeFromCommitHash(commitHash)
  667. if err != nil {
  668. return nil, err
  669. }
  670. fileiter := tree.Files()
  671. return findMatchInFiles(fileiter, treeName, opts)
  672. }
  673. // findMatchInFiles takes a FileIter, worktree name and GrepOptions, and
  674. // returns a slice of GrepResult containing the result of regex pattern matching
  675. // in content of all the files.
  676. func findMatchInFiles(fileiter *object.FileIter, treeName string, opts *GrepOptions) ([]GrepResult, error) {
  677. var results []GrepResult
  678. err := fileiter.ForEach(func(file *object.File) error {
  679. var fileInPathSpec bool
  680. // When no pathspecs are provided, search all the files.
  681. if len(opts.PathSpecs) == 0 {
  682. fileInPathSpec = true
  683. }
  684. // Check if the file name matches with the pathspec. Break out of the
  685. // loop once a match is found.
  686. for _, pathSpec := range opts.PathSpecs {
  687. if pathSpec != nil && pathSpec.MatchString(file.Name) {
  688. fileInPathSpec = true
  689. break
  690. }
  691. }
  692. // If the file does not match with any of the pathspec, skip it.
  693. if !fileInPathSpec {
  694. return nil
  695. }
  696. grepResults, err := findMatchInFile(file, treeName, opts)
  697. if err != nil {
  698. return err
  699. }
  700. results = append(results, grepResults...)
  701. return nil
  702. })
  703. return results, err
  704. }
  705. // findMatchInFile takes a single File, worktree name and GrepOptions,
  706. // and returns a slice of GrepResult containing the result of regex pattern
  707. // matching in the given file.
  708. func findMatchInFile(file *object.File, treeName string, opts *GrepOptions) ([]GrepResult, error) {
  709. var grepResults []GrepResult
  710. content, err := file.Contents()
  711. if err != nil {
  712. return grepResults, err
  713. }
  714. // Split the file content and parse line-by-line.
  715. contentByLine := strings.Split(content, "\n")
  716. for lineNum, cnt := range contentByLine {
  717. addToResult := false
  718. // Match the patterns and content. Break out of the loop once a
  719. // match is found.
  720. for _, pattern := range opts.Patterns {
  721. if pattern != nil && pattern.MatchString(cnt) {
  722. // Add to result only if invert match is not enabled.
  723. if !opts.InvertMatch {
  724. addToResult = true
  725. break
  726. }
  727. } else if opts.InvertMatch {
  728. // If matching fails, and invert match is enabled, add to
  729. // results.
  730. addToResult = true
  731. break
  732. }
  733. }
  734. if addToResult {
  735. grepResults = append(grepResults, GrepResult{
  736. FileName: file.Name,
  737. LineNumber: lineNum + 1,
  738. Content: cnt,
  739. TreeName: treeName,
  740. })
  741. }
  742. }
  743. return grepResults, nil
  744. }
  745. func rmFileAndDirIfEmpty(fs billy.Filesystem, name string) error {
  746. if err := util.RemoveAll(fs, name); err != nil {
  747. return err
  748. }
  749. dir := filepath.Dir(name)
  750. return doCleanDirectories(fs, dir)
  751. }
  752. // doCleanDirectories removes empty subdirs (without files)
  753. func doCleanDirectories(fs billy.Filesystem, dir string) error {
  754. files, err := fs.ReadDir(dir)
  755. if err != nil {
  756. return err
  757. }
  758. if len(files) == 0 {
  759. return fs.Remove(dir)
  760. }
  761. return nil
  762. }
  763. type indexBuilder struct {
  764. entries map[string]*index.Entry
  765. }
  766. func newIndexBuilder(idx *index.Index) *indexBuilder {
  767. entries := make(map[string]*index.Entry, len(idx.Entries))
  768. for _, e := range idx.Entries {
  769. entries[e.Name] = e
  770. }
  771. return &indexBuilder{
  772. entries: entries,
  773. }
  774. }
  775. func (b *indexBuilder) Write(idx *index.Index) {
  776. idx.Entries = idx.Entries[:0]
  777. for _, e := range b.entries {
  778. idx.Entries = append(idx.Entries, e)
  779. }
  780. }
  781. func (b *indexBuilder) Add(e *index.Entry) {
  782. b.entries[e.Name] = e
  783. }
  784. func (b *indexBuilder) Remove(name string) {
  785. delete(b.entries, filepath.ToSlash(name))
  786. }
上海开阖软件有限公司 沪ICP备12045867号-1