本站源代码
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.

661 lines
14KB

  1. package git
  2. import (
  3. "bytes"
  4. "errors"
  5. "io"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "gopkg.in/src-d/go-billy.v4/util"
  10. "gopkg.in/src-d/go-git.v4/plumbing"
  11. "gopkg.in/src-d/go-git.v4/plumbing/filemode"
  12. "gopkg.in/src-d/go-git.v4/plumbing/format/gitignore"
  13. "gopkg.in/src-d/go-git.v4/plumbing/format/index"
  14. "gopkg.in/src-d/go-git.v4/plumbing/object"
  15. "gopkg.in/src-d/go-git.v4/utils/ioutil"
  16. "gopkg.in/src-d/go-git.v4/utils/merkletrie"
  17. "gopkg.in/src-d/go-git.v4/utils/merkletrie/filesystem"
  18. mindex "gopkg.in/src-d/go-git.v4/utils/merkletrie/index"
  19. "gopkg.in/src-d/go-git.v4/utils/merkletrie/noder"
  20. )
  21. var (
  22. // ErrDestinationExists in an Move operation means that the target exists on
  23. // the worktree.
  24. ErrDestinationExists = errors.New("destination exists")
  25. // ErrGlobNoMatches in an AddGlob if the glob pattern does not match any
  26. // files in the worktree.
  27. ErrGlobNoMatches = errors.New("glob pattern did not match any files")
  28. )
  29. // Status returns the working tree status.
  30. func (w *Worktree) Status() (Status, error) {
  31. var hash plumbing.Hash
  32. ref, err := w.r.Head()
  33. if err != nil && err != plumbing.ErrReferenceNotFound {
  34. return nil, err
  35. }
  36. if err == nil {
  37. hash = ref.Hash()
  38. }
  39. return w.status(hash)
  40. }
  41. func (w *Worktree) status(commit plumbing.Hash) (Status, error) {
  42. s := make(Status)
  43. left, err := w.diffCommitWithStaging(commit, false)
  44. if err != nil {
  45. return nil, err
  46. }
  47. for _, ch := range left {
  48. a, err := ch.Action()
  49. if err != nil {
  50. return nil, err
  51. }
  52. fs := s.File(nameFromAction(&ch))
  53. fs.Worktree = Unmodified
  54. switch a {
  55. case merkletrie.Delete:
  56. s.File(ch.From.String()).Staging = Deleted
  57. case merkletrie.Insert:
  58. s.File(ch.To.String()).Staging = Added
  59. case merkletrie.Modify:
  60. s.File(ch.To.String()).Staging = Modified
  61. }
  62. }
  63. right, err := w.diffStagingWithWorktree(false)
  64. if err != nil {
  65. return nil, err
  66. }
  67. for _, ch := range right {
  68. a, err := ch.Action()
  69. if err != nil {
  70. return nil, err
  71. }
  72. fs := s.File(nameFromAction(&ch))
  73. if fs.Staging == Untracked {
  74. fs.Staging = Unmodified
  75. }
  76. switch a {
  77. case merkletrie.Delete:
  78. fs.Worktree = Deleted
  79. case merkletrie.Insert:
  80. fs.Worktree = Untracked
  81. fs.Staging = Untracked
  82. case merkletrie.Modify:
  83. fs.Worktree = Modified
  84. }
  85. }
  86. return s, nil
  87. }
  88. func nameFromAction(ch *merkletrie.Change) string {
  89. name := ch.To.String()
  90. if name == "" {
  91. return ch.From.String()
  92. }
  93. return name
  94. }
  95. func (w *Worktree) diffStagingWithWorktree(reverse bool) (merkletrie.Changes, error) {
  96. idx, err := w.r.Storer.Index()
  97. if err != nil {
  98. return nil, err
  99. }
  100. from := mindex.NewRootNode(idx)
  101. submodules, err := w.getSubmodulesStatus()
  102. if err != nil {
  103. return nil, err
  104. }
  105. to := filesystem.NewRootNode(w.Filesystem, submodules)
  106. var c merkletrie.Changes
  107. if reverse {
  108. c, err = merkletrie.DiffTree(to, from, diffTreeIsEquals)
  109. } else {
  110. c, err = merkletrie.DiffTree(from, to, diffTreeIsEquals)
  111. }
  112. if err != nil {
  113. return nil, err
  114. }
  115. return w.excludeIgnoredChanges(c), nil
  116. }
  117. func (w *Worktree) excludeIgnoredChanges(changes merkletrie.Changes) merkletrie.Changes {
  118. patterns, err := gitignore.ReadPatterns(w.Filesystem, nil)
  119. if err != nil {
  120. return changes
  121. }
  122. patterns = append(patterns, w.Excludes...)
  123. if len(patterns) == 0 {
  124. return changes
  125. }
  126. m := gitignore.NewMatcher(patterns)
  127. var res merkletrie.Changes
  128. for _, ch := range changes {
  129. var path []string
  130. for _, n := range ch.To {
  131. path = append(path, n.Name())
  132. }
  133. if len(path) == 0 {
  134. for _, n := range ch.From {
  135. path = append(path, n.Name())
  136. }
  137. }
  138. if len(path) != 0 {
  139. isDir := (len(ch.To) > 0 && ch.To.IsDir()) || (len(ch.From) > 0 && ch.From.IsDir())
  140. if m.Match(path, isDir) {
  141. continue
  142. }
  143. }
  144. res = append(res, ch)
  145. }
  146. return res
  147. }
  148. func (w *Worktree) getSubmodulesStatus() (map[string]plumbing.Hash, error) {
  149. o := map[string]plumbing.Hash{}
  150. sub, err := w.Submodules()
  151. if err != nil {
  152. return nil, err
  153. }
  154. status, err := sub.Status()
  155. if err != nil {
  156. return nil, err
  157. }
  158. for _, s := range status {
  159. if s.Current.IsZero() {
  160. o[s.Path] = s.Expected
  161. continue
  162. }
  163. o[s.Path] = s.Current
  164. }
  165. return o, nil
  166. }
  167. func (w *Worktree) diffCommitWithStaging(commit plumbing.Hash, reverse bool) (merkletrie.Changes, error) {
  168. var t *object.Tree
  169. if !commit.IsZero() {
  170. c, err := w.r.CommitObject(commit)
  171. if err != nil {
  172. return nil, err
  173. }
  174. t, err = c.Tree()
  175. if err != nil {
  176. return nil, err
  177. }
  178. }
  179. return w.diffTreeWithStaging(t, reverse)
  180. }
  181. func (w *Worktree) diffTreeWithStaging(t *object.Tree, reverse bool) (merkletrie.Changes, error) {
  182. var from noder.Noder
  183. if t != nil {
  184. from = object.NewTreeRootNode(t)
  185. }
  186. idx, err := w.r.Storer.Index()
  187. if err != nil {
  188. return nil, err
  189. }
  190. to := mindex.NewRootNode(idx)
  191. if reverse {
  192. return merkletrie.DiffTree(to, from, diffTreeIsEquals)
  193. }
  194. return merkletrie.DiffTree(from, to, diffTreeIsEquals)
  195. }
  196. var emptyNoderHash = make([]byte, 24)
  197. // diffTreeIsEquals is a implementation of noder.Equals, used to compare
  198. // noder.Noder, it compare the content and the length of the hashes.
  199. //
  200. // Since some of the noder.Noder implementations doesn't compute a hash for
  201. // some directories, if any of the hashes is a 24-byte slice of zero values
  202. // the comparison is not done and the hashes are take as different.
  203. func diffTreeIsEquals(a, b noder.Hasher) bool {
  204. hashA := a.Hash()
  205. hashB := b.Hash()
  206. if bytes.Equal(hashA, emptyNoderHash) || bytes.Equal(hashB, emptyNoderHash) {
  207. return false
  208. }
  209. return bytes.Equal(hashA, hashB)
  210. }
  211. // Add adds the file contents of a file in the worktree to the index. if the
  212. // file is already staged in the index no error is returned. If a file deleted
  213. // from the Workspace is given, the file is removed from the index. If a
  214. // directory given, adds the files and all his sub-directories recursively in
  215. // the worktree to the index. If any of the files is already staged in the index
  216. // no error is returned. When path is a file, the blob.Hash is returned.
  217. func (w *Worktree) Add(path string) (plumbing.Hash, error) {
  218. // TODO(mcuadros): remove plumbing.Hash from signature at v5.
  219. s, err := w.Status()
  220. if err != nil {
  221. return plumbing.ZeroHash, err
  222. }
  223. idx, err := w.r.Storer.Index()
  224. if err != nil {
  225. return plumbing.ZeroHash, err
  226. }
  227. var h plumbing.Hash
  228. var added bool
  229. fi, err := w.Filesystem.Lstat(path)
  230. if err != nil || !fi.IsDir() {
  231. added, h, err = w.doAddFile(idx, s, path)
  232. } else {
  233. added, err = w.doAddDirectory(idx, s, path)
  234. }
  235. if err != nil {
  236. return h, err
  237. }
  238. if !added {
  239. return h, nil
  240. }
  241. return h, w.r.Storer.SetIndex(idx)
  242. }
  243. func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string) (added bool, err error) {
  244. files, err := w.Filesystem.ReadDir(directory)
  245. if err != nil {
  246. return false, err
  247. }
  248. for _, file := range files {
  249. name := path.Join(directory, file.Name())
  250. var a bool
  251. if file.IsDir() {
  252. if file.Name() == GitDirName {
  253. // ignore special git directory
  254. continue
  255. }
  256. a, err = w.doAddDirectory(idx, s, name)
  257. } else {
  258. a, _, err = w.doAddFile(idx, s, name)
  259. }
  260. if err != nil {
  261. return
  262. }
  263. if !added && a {
  264. added = true
  265. }
  266. }
  267. return
  268. }
  269. // AddGlob adds all paths, matching pattern, to the index. If pattern matches a
  270. // directory path, all directory contents are added to the index recursively. No
  271. // error is returned if all matching paths are already staged in index.
  272. func (w *Worktree) AddGlob(pattern string) error {
  273. files, err := util.Glob(w.Filesystem, pattern)
  274. if err != nil {
  275. return err
  276. }
  277. if len(files) == 0 {
  278. return ErrGlobNoMatches
  279. }
  280. s, err := w.Status()
  281. if err != nil {
  282. return err
  283. }
  284. idx, err := w.r.Storer.Index()
  285. if err != nil {
  286. return err
  287. }
  288. var saveIndex bool
  289. for _, file := range files {
  290. fi, err := w.Filesystem.Lstat(file)
  291. if err != nil {
  292. return err
  293. }
  294. var added bool
  295. if fi.IsDir() {
  296. added, err = w.doAddDirectory(idx, s, file)
  297. } else {
  298. added, _, err = w.doAddFile(idx, s, file)
  299. }
  300. if err != nil {
  301. return err
  302. }
  303. if !saveIndex && added {
  304. saveIndex = true
  305. }
  306. }
  307. if saveIndex {
  308. return w.r.Storer.SetIndex(idx)
  309. }
  310. return nil
  311. }
  312. // doAddFile create a new blob from path and update the index, added is true if
  313. // the file added is different from the index.
  314. func (w *Worktree) doAddFile(idx *index.Index, s Status, path string) (added bool, h plumbing.Hash, err error) {
  315. if s.File(path).Worktree == Unmodified {
  316. return false, h, nil
  317. }
  318. h, err = w.copyFileToStorage(path)
  319. if err != nil {
  320. if os.IsNotExist(err) {
  321. added = true
  322. h, err = w.deleteFromIndex(idx, path)
  323. }
  324. return
  325. }
  326. if err := w.addOrUpdateFileToIndex(idx, path, h); err != nil {
  327. return false, h, err
  328. }
  329. return true, h, err
  330. }
  331. func (w *Worktree) copyFileToStorage(path string) (hash plumbing.Hash, err error) {
  332. fi, err := w.Filesystem.Lstat(path)
  333. if err != nil {
  334. return plumbing.ZeroHash, err
  335. }
  336. obj := w.r.Storer.NewEncodedObject()
  337. obj.SetType(plumbing.BlobObject)
  338. obj.SetSize(fi.Size())
  339. writer, err := obj.Writer()
  340. if err != nil {
  341. return plumbing.ZeroHash, err
  342. }
  343. defer ioutil.CheckClose(writer, &err)
  344. if fi.Mode()&os.ModeSymlink != 0 {
  345. err = w.fillEncodedObjectFromSymlink(writer, path, fi)
  346. } else {
  347. err = w.fillEncodedObjectFromFile(writer, path, fi)
  348. }
  349. if err != nil {
  350. return plumbing.ZeroHash, err
  351. }
  352. return w.r.Storer.SetEncodedObject(obj)
  353. }
  354. func (w *Worktree) fillEncodedObjectFromFile(dst io.Writer, path string, fi os.FileInfo) (err error) {
  355. src, err := w.Filesystem.Open(path)
  356. if err != nil {
  357. return err
  358. }
  359. defer ioutil.CheckClose(src, &err)
  360. if _, err := io.Copy(dst, src); err != nil {
  361. return err
  362. }
  363. return err
  364. }
  365. func (w *Worktree) fillEncodedObjectFromSymlink(dst io.Writer, path string, fi os.FileInfo) error {
  366. target, err := w.Filesystem.Readlink(path)
  367. if err != nil {
  368. return err
  369. }
  370. _, err = dst.Write([]byte(target))
  371. return err
  372. }
  373. func (w *Worktree) addOrUpdateFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error {
  374. e, err := idx.Entry(filename)
  375. if err != nil && err != index.ErrEntryNotFound {
  376. return err
  377. }
  378. if err == index.ErrEntryNotFound {
  379. return w.doAddFileToIndex(idx, filename, h)
  380. }
  381. return w.doUpdateFileToIndex(e, filename, h)
  382. }
  383. func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error {
  384. return w.doUpdateFileToIndex(idx.Add(filename), filename, h)
  385. }
  386. func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbing.Hash) error {
  387. info, err := w.Filesystem.Lstat(filename)
  388. if err != nil {
  389. return err
  390. }
  391. e.Hash = h
  392. e.ModifiedAt = info.ModTime()
  393. e.Mode, err = filemode.NewFromOSFileMode(info.Mode())
  394. if err != nil {
  395. return err
  396. }
  397. if e.Mode.IsRegular() {
  398. e.Size = uint32(info.Size())
  399. }
  400. fillSystemInfo(e, info.Sys())
  401. return nil
  402. }
  403. // Remove removes files from the working tree and from the index.
  404. func (w *Worktree) Remove(path string) (plumbing.Hash, error) {
  405. // TODO(mcuadros): remove plumbing.Hash from signature at v5.
  406. idx, err := w.r.Storer.Index()
  407. if err != nil {
  408. return plumbing.ZeroHash, err
  409. }
  410. var h plumbing.Hash
  411. fi, err := w.Filesystem.Lstat(path)
  412. if err != nil || !fi.IsDir() {
  413. h, err = w.doRemoveFile(idx, path)
  414. } else {
  415. _, err = w.doRemoveDirectory(idx, path)
  416. }
  417. if err != nil {
  418. return h, err
  419. }
  420. return h, w.r.Storer.SetIndex(idx)
  421. }
  422. func (w *Worktree) doRemoveDirectory(idx *index.Index, directory string) (removed bool, err error) {
  423. files, err := w.Filesystem.ReadDir(directory)
  424. if err != nil {
  425. return false, err
  426. }
  427. for _, file := range files {
  428. name := path.Join(directory, file.Name())
  429. var r bool
  430. if file.IsDir() {
  431. r, err = w.doRemoveDirectory(idx, name)
  432. } else {
  433. _, err = w.doRemoveFile(idx, name)
  434. if err == index.ErrEntryNotFound {
  435. err = nil
  436. }
  437. }
  438. if err != nil {
  439. return
  440. }
  441. if !removed && r {
  442. removed = true
  443. }
  444. }
  445. err = w.removeEmptyDirectory(directory)
  446. return
  447. }
  448. func (w *Worktree) removeEmptyDirectory(path string) error {
  449. files, err := w.Filesystem.ReadDir(path)
  450. if err != nil {
  451. return err
  452. }
  453. if len(files) != 0 {
  454. return nil
  455. }
  456. return w.Filesystem.Remove(path)
  457. }
  458. func (w *Worktree) doRemoveFile(idx *index.Index, path string) (plumbing.Hash, error) {
  459. hash, err := w.deleteFromIndex(idx, path)
  460. if err != nil {
  461. return plumbing.ZeroHash, err
  462. }
  463. return hash, w.deleteFromFilesystem(path)
  464. }
  465. func (w *Worktree) deleteFromIndex(idx *index.Index, path string) (plumbing.Hash, error) {
  466. e, err := idx.Remove(path)
  467. if err != nil {
  468. return plumbing.ZeroHash, err
  469. }
  470. return e.Hash, nil
  471. }
  472. func (w *Worktree) deleteFromFilesystem(path string) error {
  473. err := w.Filesystem.Remove(path)
  474. if os.IsNotExist(err) {
  475. return nil
  476. }
  477. return err
  478. }
  479. // RemoveGlob removes all paths, matching pattern, from the index. If pattern
  480. // matches a directory path, all directory contents are removed from the index
  481. // recursively.
  482. func (w *Worktree) RemoveGlob(pattern string) error {
  483. idx, err := w.r.Storer.Index()
  484. if err != nil {
  485. return err
  486. }
  487. entries, err := idx.Glob(pattern)
  488. if err != nil {
  489. return err
  490. }
  491. for _, e := range entries {
  492. file := filepath.FromSlash(e.Name)
  493. if _, err := w.Filesystem.Lstat(file); err != nil && !os.IsNotExist(err) {
  494. return err
  495. }
  496. if _, err := w.doRemoveFile(idx, file); err != nil {
  497. return err
  498. }
  499. dir, _ := filepath.Split(file)
  500. if err := w.removeEmptyDirectory(dir); err != nil {
  501. return err
  502. }
  503. }
  504. return w.r.Storer.SetIndex(idx)
  505. }
  506. // Move moves or rename a file in the worktree and the index, directories are
  507. // not supported.
  508. func (w *Worktree) Move(from, to string) (plumbing.Hash, error) {
  509. // TODO(mcuadros): support directories and/or implement support for glob
  510. if _, err := w.Filesystem.Lstat(from); err != nil {
  511. return plumbing.ZeroHash, err
  512. }
  513. if _, err := w.Filesystem.Lstat(to); err == nil {
  514. return plumbing.ZeroHash, ErrDestinationExists
  515. }
  516. idx, err := w.r.Storer.Index()
  517. if err != nil {
  518. return plumbing.ZeroHash, err
  519. }
  520. hash, err := w.deleteFromIndex(idx, from)
  521. if err != nil {
  522. return plumbing.ZeroHash, err
  523. }
  524. if err := w.Filesystem.Rename(from, to); err != nil {
  525. return hash, err
  526. }
  527. if err := w.addOrUpdateFileToIndex(idx, to, hash); err != nil {
  528. return hash, err
  529. }
  530. return hash, w.r.Storer.SetIndex(idx)
  531. }
上海开阖软件有限公司 沪ICP备12045867号-1