From ce2a4769c9cd6b5b49fce9d93a17069f43a789bd Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Sun, 22 Oct 2023 00:02:47 +0200 Subject: [PATCH] add(taskwarrior): uda for last_gitw_update with initial merge WIP for #3. Creation of entirely new tasks is possible and works as expected. The only remaining issue is the merging of existing tasks. --- internal/gitea/milestone.go | 16 ++- internal/gitw/project.go | 187 ++++++++++++++++++++--------------- internal/taskwarrior/task.go | 44 +++++---- 3 files changed, 150 insertions(+), 97 deletions(-) diff --git a/internal/gitea/milestone.go b/internal/gitea/milestone.go index bb85a18..bad864d 100644 --- a/internal/gitea/milestone.go +++ b/internal/gitea/milestone.go @@ -23,8 +23,22 @@ type Milestone struct { Closed_at time.Time `json:"closed_at"` } +func (milestone *Milestone) IntoTask(repository Repository) (task taskwarrior.Task) { + task = taskwarrior.NewTask( + milestone.Title, + repository.Name, + milestone.Id, + taskwarrior.MILESTONE, + ) + task.Due = taskwarrior.GoTimeToTaskTime(milestone.Due_on) + task.Entry = taskwarrior.GoTimeToTaskTime(milestone.Created_at) + task.Modified = taskwarrior.GoTimeToTaskTime(milestone.Updated_at) + task.AppendComment(milestone.Description, milestone.Updated_at) + return +} + // TODO: implement merge for milestone tasks -func (milestone *Milestone) Merge(task taskwarrior.Task) taskwarrior.Task { +func (milestone *Milestone) MergeTask(task taskwarrior.Task) taskwarrior.Task { return task } diff --git a/internal/gitw/project.go b/internal/gitw/project.go index cb015ba..cd6bf99 100644 --- a/internal/gitw/project.go +++ b/internal/gitw/project.go @@ -3,6 +3,7 @@ package gitw import ( "errors" "fmt" + "time" "gitea.yves-biener.de/yves-biener/gitwarrior/internal/gitea" "gitea.yves-biener.de/yves-biener/gitwarrior/internal/taskwarrior" @@ -62,8 +63,50 @@ func (project *Project) Fetch() error { // 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() @@ -73,7 +116,6 @@ func (project *Project) merge() error { if err != nil { return err } - var task taskwarrior.Task 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 { @@ -82,67 +124,34 @@ func (project *Project) merge() error { continue } // NOTE: this milestone does not yet exist - task = taskwarrior.NewTask( - milestone.Title, - project.repository.Name, - milestone.Id, - taskwarrior.MILESTONE, - ) - task.Due = taskwarrior.GoTimeToTaskTime(milestone.Due_on) - task.Entry = taskwarrior.GoTimeToTaskTime(milestone.Created_at) - task.Modified = taskwarrior.GoTimeToTaskTime(milestone.Updated_at) - task.AppendComment(milestone.Description, milestone.Updated_at) + 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.Merge(git_tasks[0]) + task = milestone.MergeTask(git_tasks[0]) fmt.Printf("\tUpdated milestone: '%s'\n", task.Description) tasks = append(tasks, task) } } - // NOTE: merge tasks - // TODO: link milestones into 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 - } - var task taskwarrior.Task - 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 = taskwarrior.NewTask( - issue.git_issue.Title, - project.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) - } - fmt.Printf("\tCreated task: '%s'\n", task.Description) - tasks = append(tasks, task) - } else { - // NOTE: there is excactly one git_task - task = issue.merge(git_tasks[0]) - fmt.Printf("\tUpdated task: '%s'\n", task.Description) - tasks = append(tasks, task) - } - } - // TODO: dry-run switch - if false { + if !dry_run { return taskwarrior.UpdateTasks(tasks) } return nil @@ -153,32 +162,56 @@ type Issue struct { comments []gitea.Comment } -func (i *Issue) into() (taskwarrior.Task, error) { - // TODO: identify if issue is already an existing task - var filter taskwarrior.Filter - filter.IncludeGitId(i.git_issue.Id) - filter.IncludeGitType(taskwarrior.ISSUE) - tasks, err := taskwarrior.GetTasks(filter) - if err != nil { - // this means that a task for this issue does not exist yet - return taskwarrior.NewTask( - i.git_issue.Title, - i.git_issue.Repository.Name, - i.git_issue.Id, - taskwarrior.ISSUE, - i.git_issue.Labels..., - ), nil - } else { - // this means that a task exists and it needs to be merged - if len(tasks) != 1 { - return taskwarrior.Task{}, errors.New("Did not find exactly one task for a given issue.Id") - } - return i.merge(tasks[0]), nil +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 (i *Issue) merge(task taskwarrior.Task) 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 @@ -186,5 +219,5 @@ func (i *Issue) merge(task taskwarrior.Task) taskwarrior.Task { // 1. use theirs // 2. use mine // 3. use provided value - return task + return task, nil } diff --git a/internal/taskwarrior/task.go b/internal/taskwarrior/task.go index ed9ab58..96d6616 100644 --- a/internal/taskwarrior/task.go +++ b/internal/taskwarrior/task.go @@ -14,20 +14,22 @@ import ( /// mentioned in the Status field type Task struct { - Id uint `json:"id,omitempty"` - Git_id float32 `json:"git_id,omitempty"` // uda - Git_type string `json:"git_type,omitempty"` // uda - Project string `json:"project,omitempty"` - Tags []string `json:"tags,omitempty"` - Description string `json:"description,omitempty"` - Annotations []Annotation `json:"annotations,omitempty"` - Status string `json:"status,omitempty"` - Due string `json:"due,omitempty"` - Entry string `json:"entry,omitempty"` - Modified string `json:"modified,omitempty"` - End string `json:"end,omitempty"` - Uuid string `json:"uuid,omitempty"` - Urgency float32 `json:"urgency,omitempty"` + Id uint `json:"id,omitempty"` + Git_id float32 `json:"git_id,omitempty"` // uda + Git_type string `json:"git_type,omitempty"` // uda + Project string `json:"project,omitempty"` + Tags []string `json:"tags,omitempty"` + Description string `json:"description,omitempty"` + Annotations []Annotation `json:"annotations,omitempty"` + Status string `json:"status,omitempty"` + Depends []string `json:"depends,omitempty"` + Due string `json:"due,omitempty"` + Entry string `json:"entry,omitempty"` + Modified string `json:"modified,omitempty"` + End string `json:"end,omitempty"` + Last_gitw_update string `json:"last_gitw_update,omitempty"` // uda + Uuid string `json:"uuid,omitempty"` + Urgency float32 `json:"urgency,omitempty"` } type Annotation struct { @@ -47,17 +49,21 @@ func NewTask(description string, project string, git_id uint, git_type Type, tag // TODO: update task struct to include the new user defined value, which shall // also be provided as an argument return Task{ - Project: project, - Description: description, - Git_id: float32(git_id), - Git_type: string(git_type), - Tags: tags, + Project: project, + Description: description, + Git_id: float32(git_id), + Git_type: string(git_type), + Last_gitw_update: GoTimeToTaskTime(time.Now()), + Tags: tags, } } func TaskTimeToGoTime(t string) time.Time { // TODO: apply required changes to the string for correct parsing splits := strings.Split(t, "T") + if len(t) == 0 { + return time.UnixMicro(0) + } if len(splits) != 2 { fmt.Fprintf(os.Stderr, "Expected exactly 2 splits") os.Exit(-1)