WIP for #3. Creation of entirely new tasks is possible and works as expected. The only remaining issue is the merging of existing tasks.
224 lines
6.4 KiB
Go
224 lines
6.4 KiB
Go
package gitw
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gitea.yves-biener.de/yves-biener/gitwarrior/internal/gitea"
|
|
"gitea.yves-biener.de/yves-biener/gitwarrior/internal/taskwarrior"
|
|
)
|
|
|
|
type Project struct {
|
|
server gitea.Gitea
|
|
repository gitea.Repository
|
|
issues []Issue
|
|
milestones []gitea.Milestone
|
|
}
|
|
|
|
func NewProject(server gitea.Gitea, repository gitea.Repository) Project {
|
|
return Project{
|
|
server: server,
|
|
repository: repository,
|
|
}
|
|
}
|
|
|
|
func (project *Project) Pull() error {
|
|
if err := project.Fetch(); err != nil {
|
|
return err
|
|
}
|
|
if err := project.merge(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (project *Project) Fetch() error {
|
|
comments, err := project.server.GetComments(project.repository)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
issues, err := project.server.GetIssues(project.repository)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
project.milestones, err = project.server.GetMilestones(project.repository)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, issue := range issues {
|
|
var issue_comments []gitea.Comment
|
|
for _, comment := range comments {
|
|
if comment.Issue_url == issue.Html_url {
|
|
issue_comments = append(issue_comments, comment)
|
|
}
|
|
}
|
|
project.issues = append(project.issues, Issue{
|
|
git_issue: issue,
|
|
comments: issue_comments,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TODO: tasks should include the corresponding time's of the git related issues and milestones
|
|
func (project *Project) merge() error {
|
|
dry_run := true // make this a parameter / cli flag
|
|
var tasks []taskwarrior.Task
|
|
var task taskwarrior.Task
|
|
var filter taskwarrior.Filter
|
|
// NOTE: merge tasks
|
|
for _, issue := range project.issues {
|
|
filter.Reset()
|
|
filter.IncludeGitId(issue.git_issue.Id)
|
|
filter.IncludeGitType(taskwarrior.ISSUE)
|
|
git_tasks, err := taskwarrior.GetTasks(filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(git_tasks) > 1 {
|
|
return errors.New("Git issue id was at least used twice in taskwarrior tasks.")
|
|
} else if len(git_tasks) == 0 {
|
|
// NOTE: ignore closed issues which do not have a taskwarrior task
|
|
if issue.git_issue.State == string(gitea.CLOSED) {
|
|
continue
|
|
}
|
|
// NOTE: this task does not yet exist
|
|
task, err = issue.IntoTask(project.repository)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("\tCreated task: '%s'\n", task.Description)
|
|
tasks = append(tasks, task)
|
|
} else {
|
|
// NOTE: there is excactly one git_task
|
|
task, err = issue.MergeTask(git_tasks[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("\tUpdated task: '%s'\n", task.Description)
|
|
tasks = append(tasks, task)
|
|
}
|
|
}
|
|
if !dry_run {
|
|
if err := taskwarrior.UpdateTasks(tasks); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// TODO: reset tasks after successfully updating the issues
|
|
tasks = nil
|
|
// TODO: merge milestones
|
|
for _, milestone := range project.milestones {
|
|
filter.Reset()
|
|
filter.IncludeGitId(milestone.Id)
|
|
filter.IncludeGitType(taskwarrior.MILESTONE)
|
|
git_tasks, err := taskwarrior.GetTasks(filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(git_tasks) > 1 {
|
|
return errors.New("Git milestone id was at least used twice in taskwarrior tasks.")
|
|
} else if len(git_tasks) == 0 {
|
|
// NOTE: ignore closed milestones which do not have a taskwarrior task
|
|
if milestone.State == string(gitea.CLOSED) {
|
|
continue
|
|
}
|
|
// NOTE: this milestone does not yet exist
|
|
task = milestone.IntoTask(project.repository)
|
|
for _, issue := range project.issues {
|
|
if issue.git_issue.State == "closed" || issue.git_issue.Milestone.Id != milestone.Id {
|
|
continue
|
|
}
|
|
// link to the corresponding task
|
|
filter.Reset()
|
|
filter.IncludeGitId(issue.git_issue.Id)
|
|
filter.IncludeGitType(taskwarrior.ISSUE)
|
|
tasks, err := taskwarrior.GetTasks(filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(tasks) != 1 {
|
|
return errors.New("Git issue id used for this milestone does not exist")
|
|
}
|
|
task.Depends = append(task.Depends, tasks[0].Uuid)
|
|
}
|
|
fmt.Printf("\tCreated milestone: '%s'\n", task.Description)
|
|
tasks = append(tasks, task)
|
|
} else {
|
|
// NOTE: there is exactly one git_task
|
|
task = milestone.MergeTask(git_tasks[0])
|
|
fmt.Printf("\tUpdated milestone: '%s'\n", task.Description)
|
|
tasks = append(tasks, task)
|
|
}
|
|
}
|
|
if !dry_run {
|
|
return taskwarrior.UpdateTasks(tasks)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Issue struct {
|
|
git_issue gitea.Issue
|
|
comments []gitea.Comment
|
|
}
|
|
|
|
func (issue *Issue) IntoTask(repository gitea.Repository) (task taskwarrior.Task, err error) {
|
|
// transform current issue into new taskwarrior.Task
|
|
task = taskwarrior.NewTask(
|
|
issue.git_issue.Title,
|
|
repository.Name,
|
|
issue.git_issue.Id,
|
|
taskwarrior.ISSUE,
|
|
issue.git_issue.Labels...,
|
|
)
|
|
task.Entry = taskwarrior.GoTimeToTaskTime(issue.git_issue.Created_at)
|
|
task.Modified = taskwarrior.GoTimeToTaskTime(issue.git_issue.Updated_at)
|
|
for _, comment := range issue.comments {
|
|
task.AppendComment(comment.Body, comment.Updated_at)
|
|
}
|
|
return
|
|
}
|
|
|
|
// TODO: implement merging of git issue or milestone into a taskwarrior task
|
|
func (issue *Issue) MergeTask(task taskwarrior.Task) (taskwarrior.Task, error) {
|
|
last_update := taskwarrior.TaskTimeToGoTime(task.Last_gitw_update)
|
|
if issue.git_issue.Updated_at.After(last_update) {
|
|
// there are changes we need to merge
|
|
if taskwarrior.TaskTimeToGoTime(task.Modified).After(last_update) {
|
|
// TODO: this means that there are local modifications which are not yet pushed
|
|
} else {
|
|
task.Description = issue.git_issue.Title
|
|
task.Tags = issue.git_issue.Labels
|
|
// NOTE: otherwise do not update the value
|
|
if issue.git_issue.State == "closed" {
|
|
task.Status = "completed"
|
|
task.End = taskwarrior.GoTimeToTaskTime(issue.git_issue.Closed_at)
|
|
}
|
|
}
|
|
task.Last_gitw_update = taskwarrior.GoTimeToTaskTime(time.Now())
|
|
} else {
|
|
// there are changes after the previous update here may be merge conflicts
|
|
// TODO: implement merge conflict resolution
|
|
}
|
|
annotations := len(task.Annotations)
|
|
for i, comment := range issue.comments {
|
|
if comment.Updated_at.After(last_update) {
|
|
if i < annotations {
|
|
task.Annotations[i].Description = comment.Body
|
|
task.Annotations[i].Entry = taskwarrior.GoTimeToTaskTime(comment.Updated_at)
|
|
task.Last_gitw_update = taskwarrior.GoTimeToTaskTime(time.Now())
|
|
} else {
|
|
task.AppendComment(comment.Body, comment.Updated_at)
|
|
}
|
|
}
|
|
}
|
|
// TODO: issue values into task:
|
|
// - is the issue more recent than the task?
|
|
// - apply changes into task
|
|
// - in case of merge conflicts ask user for corresponding action:
|
|
// 1. use theirs
|
|
// 2. use mine
|
|
// 3. use provided value
|
|
return task, nil
|
|
}
|