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 }