func()

in backend/plugins/gitextractor/parser/repo_libgit2.go [457:610]


func (r *Libgit2RepoCollector) CollectDiffLine(subtaskCtx plugin.SubTaskContext) error {
	//Using this subtask,we can get every line change in every commit.
	//We maintain a snapshot structure to get which commit each deleted line belongs to
	snapshot := make(map[string] /*file path*/ *models.FileBlame)
	repo := r.repo
	//step 1. get the reverse commit list
	commitList := make([]git.Commit, 0)
	// get current head commit sha, default is master branch
	// check branch, if not master, checkout to branch's head
	commitOid, err1 := repo.Head()
	if err1 != nil && err1.Error() != TypeNotMatchError {
		return errors.Convert(err1)
	}
	//get head commit object and add into commitList
	commit, err1 := repo.LookupCommit(commitOid.Target())
	if err1 != nil && err1.Error() != TypeNotMatchError {
		return errors.Convert(err1)
	}
	commitList = append(commitList, *commit)
	// if current head has parents, get parent commitsha
	for commit != nil && commit.ParentCount() > 0 {
		pid := commit.ParentId(0)
		commit, err1 = repo.LookupCommit(pid)
		if err1 != nil && err1.Error() != TypeNotMatchError {
			return errors.Convert(err1)
		}
		commitList = append(commitList, *commit)
	}
	// reverse commitList
	for i, j := 0, len(commitList)-1; i < j; i, j = i+1, j-1 {
		commitList[i], commitList[j] = commitList[j], commitList[i]
	}
	//step 2. get the diff of each commit
	// for each commit, get the diff
	for _, commitsha := range commitList {
		curcommit, err := repo.LookupCommit(commitsha.Id())
		if err != nil {
			return errors.Convert(err)
		}
		if curcommit.ParentCount() == 0 || curcommit.ParentCount() > 0 {
			var parentTree, tree *git.Tree
			tree, err = curcommit.Tree()
			if err != nil {
				return errors.Convert(err)
			}
			var diff *git.Diff
			//FIXME error type convert
			opts, err := git.DefaultDiffOptions()
			opts.NotifyCallback = func(diffSoFar *git.Diff, delta git.DiffDelta, matchedPathSpec string) error {
				return nil
			}
			if err != nil {
				return errors.Convert(err)
			}
			if curcommit.ParentCount() > 0 {
				parent := curcommit.Parent(0)
				parentTree, err = parent.Tree()
			}
			diff, err = repo.DiffTreeToTree(parentTree, tree, &opts)
			if err != nil {
				return errors.Convert(err)
			}
			deleted := make(models.DiffLines, 0)
			added := make(models.DiffLines, 0)
			var lastFile string
			lastFile = ""
			err = diff.ForEach(func(file git.DiffDelta, progress float64) (git.DiffForEachHunkCallback, error) {
				// if it doesn't exist in snapshot, create a new one
				if _, ok := snapshot[file.OldFile.Path]; !ok {
					fileBlame, err := models.NewFileBlame()
					if err != nil {
						r.logger.Info("Create FileBlame Error")
						return nil, err
					}
					snapshot[file.OldFile.Path] = (*models.FileBlame)(fileBlame)
				}
				if lastFile == "" {
					lastFile = file.NewFile.Path
				} else if lastFile != file.NewFile.Path {
					updateSnapshotFileBlame(curcommit, deleted, added, lastFile, snapshot)
					// reset the deleted and added,last_file now is current file
					deleted = make([]git.DiffLine, 0)
					added = make([]git.DiffLine, 0)
					lastFile = file.NewFile.Path
				}
				hunkNum := 0
				return func(hunk git.DiffHunk) (git.DiffForEachLineCallback, error) {
					hunkNum++
					return func(line git.DiffLine) error {
						commitLineChange := &code.CommitLineChange{}
						commitLineChange.CommitSha = curcommit.Id().String()
						commitLineChange.ChangedType = line.Origin.String()
						commitLineChange.LineNoNew = line.NewLineno
						commitLineChange.LineNoOld = line.OldLineno
						commitLineChange.OldFilePath = file.OldFile.Path
						commitLineChange.NewFilePath = file.NewFile.Path
						commitLineChange.HunkNum = hunkNum
						commitLineChange.Id = curcommit.Id().String() + ":" + file.NewFile.Path + ":" + strconv.Itoa(line.OldLineno) + ":" + strconv.Itoa(line.NewLineno)
						if line.Origin == git.DiffLineAddition {
							added = append(added, line)
						} else if line.Origin == git.DiffLineDeletion {
							fb := snapshot[file.OldFile.Path]
							l := fb.Find(line.OldLineno)
							if l != nil && l.Value != nil {
								temp := snapshot[file.OldFile.Path].Find(line.OldLineno)
								commitLineChange.PrevCommit = temp.Value.(string)
							} else {
								r.logger.Info("err", file.OldFile.Path, line.OldLineno, curcommit.Id().String())
							}
							deleted = append(deleted, line)
						}
						err = r.store.CommitLineChange(commitLineChange)
						if err != nil {
							return errors.Convert(err)
						}
						return nil
					}, nil
				}, nil
			}, git.DiffDetailLines)
			if err != nil {
				return errors.Convert(err)
			}
			//finally,process the last file in diff
			updateSnapshotFileBlame(curcommit, deleted, added, lastFile, snapshot)
		}
	}
	r.logger.Info("line change collect success")
	db := subtaskCtx.GetDal()
	err := db.Delete(&code.RepoSnapshot{}, dal.Where("repo_id= ?", r.id))
	if err != nil {
		return errors.Convert(err)
	}
	for fp := range snapshot {
		temp := snapshot[fp]
		count := 0
		for e := temp.Lines.Front(); e != nil; e = e.Next() {
			count++
			snapshotLine := &code.RepoSnapshot{}
			snapshotLine.RepoId = r.id
			snapshotLine.LineNo = count
			snapshotLine.CommitSha = e.Value.(string)
			snapshotLine.FilePath = fp
			err := r.store.RepoSnapshot(snapshotLine)
			if err != nil {
				r.logger.Info("error")
				return err
			}
		}

	}

	r.logger.Info("collect snapshot finished")
	return nil
}