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
}