add(taskwarrior): integrate uda for git integration

This implementation adds the necessary fields for the tasks of
taskwarrior to be synchronized with git. It provides the necessary
conversion of the taskwarrior times and the standard time.Time go
format. WIP for #3.
This commit is contained in:
2023-10-21 11:03:43 +02:00
parent 11c57d3485
commit 3ed3c53854
4 changed files with 171 additions and 55 deletions

View File

@@ -6,13 +6,11 @@ import (
"gitea.yves-biener.de/yves-biener/gitwarrior/internal/gitea" "gitea.yves-biener.de/yves-biener/gitwarrior/internal/gitea"
"gitea.yves-biener.de/yves-biener/gitwarrior/internal/gitw" "gitea.yves-biener.de/yves-biener/gitwarrior/internal/gitw"
"gitea.yves-biener.de/yves-biener/gitwarrior/internal/taskwarrior"
) )
func main() { func main() {
// TODO: server url may be also be derived from the git configuration? // TODO: server url may be also be derived from the git configuration?
server := gitea.NewGitea("https://gitea.yves-biener.de") server := gitea.NewGitea("https://gitea.yves-biener.de")
repository, err := gitw.Discover() repository, err := gitw.Discover()
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
@@ -23,61 +21,13 @@ func main() {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(-1) os.Exit(-1)
} }
fmt.Printf("%#v\n", repository) project := gitw.NewProject(server, repository)
fmt.Println("---")
issue, err := server.GetIssue(repository, 1) if err = project.Fetch(); err != nil {
if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(-1) os.Exit(-1)
} }
milestone, err := server.GetMilestone(repository, 1) fmt.Printf("%#v\n", project)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}
comments, err := server.GetComments(repository)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}
fmt.Printf("%#v\n", issue)
fmt.Println("---")
fmt.Printf("%#v\n", milestone)
fmt.Println("---")
for _, comment := range comments {
// NOTE: filter comments to only include comments which are related to the current issue
// Order them by their Id's as that's the order they are in the issue (most likely)
if comment.Issue_url == issue.Html_url {
fmt.Printf("%#v\n", comment)
}
}
var filter taskwarrior.Filter
filter.IncludeProjects("notes")
tasks, err := taskwarrior.GetTasks(filter)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}
for _, task := range tasks {
fmt.Printf("%#v\n", task)
}
fmt.Println("---")
filter.Reset()
filter.IncludeIds(1, 2, 3)
tasks, err = taskwarrior.GetTasks(filter)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}
for _, task := range tasks {
fmt.Printf("%#v\n", task)
}
fmt.Println("---")
// NOTE: this can be used to add / modify tasks // NOTE: this can be used to add / modify tasks
// var update_tasks []taskwarrior.Task // var update_tasks []taskwarrior.Task

106
internal/gitw/project.go Normal file
View File

@@ -0,0 +1,106 @@
package gitw
import (
"errors"
"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{
issue: issue,
comments: issue_comments,
})
}
return nil
}
func (project *Project) merge() error {
// TODO: run merge to update the local taskwarrior tasks into the state of the
// issues and milestones
return nil
}
type Issue struct {
issue gitea.Issue
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.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.issue.Title,
i.issue.Repository.Name,
i.issue.Id,
taskwarrior.ISSUE,
i.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 (i *Issue) merge(task taskwarrior.Task) taskwarrior.Task {
// 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
}

View File

@@ -6,6 +6,11 @@ type Filter struct {
filter []string filter []string
} }
type Type string
const MILESTONE Type = "milestone"
const ISSUE Type = "issue"
func (f *Filter) Reset() { func (f *Filter) Reset() {
f.filter = nil f.filter = nil
} }
@@ -33,3 +38,11 @@ func (f *Filter) IncludeIds(ids ...uint) {
f.filter = append(f.filter, fmt.Sprintf("%d", id)) f.filter = append(f.filter, fmt.Sprintf("%d", id))
} }
} }
func (f *Filter) IncludeGitId(id uint) {
f.filter = append(f.filter, fmt.Sprintf("git_id=%d", id))
}
func (f *Filter) IncludeGitType(value Type) {
f.filter = append(f.filter, fmt.Sprintf("git_type=%s", value))
}

View File

@@ -3,7 +3,11 @@ package taskwarrior
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"os"
"os/exec" "os/exec"
"strings"
"time"
) )
/// A task with an Id of 0 is either `completed` or `deleted` which is also /// A task with an Id of 0 is either `completed` or `deleted` which is also
@@ -11,6 +15,8 @@ import (
type Task struct { type Task struct {
Id uint `json:"id,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"` Project string `json:"project,omitempty"`
Tags []string `json:"tags,omitempty"` Tags []string `json:"tags,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
@@ -29,14 +35,55 @@ type Annotation struct {
Entry string `json:"entry,omitempty"` Entry string `json:"entry,omitempty"`
} }
func NewTask(description string, project string, tags ...string) Task { func NewTask(description string, project string, git_id uint, git_type Type, tags ...string) Task {
// TODO: update task struct to include the new user defined value, which shall
// also be provided as an argument
return Task{ return Task{
Project: project, Project: project,
Tags: tags,
Description: description, Description: description,
Git_id: float32(git_id),
Git_type: string(git_type),
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(splits) != 2 {
fmt.Fprintf(os.Stderr, "Expected exactly 2 splits")
os.Exit(-1)
}
date := splits[0]
first := date[0:4]
second := date[4:6]
third := date[6:]
date = strings.Join([]string{first, second, third}, "-")
timestamp := splits[1]
first = timestamp[0:2]
second = timestamp[2:4]
third = timestamp[4 : len(timestamp)-1]
timestamp = strings.Join([]string{first, second, third}, ":")
value := fmt.Sprintf("%sT%s+02:00", date, timestamp)
result, err := time.Parse(time.RFC3339, value)
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(-1)
}
return result
}
func GoTimeToTaskTime(t time.Time) string {
result := t.Format(time.RFC3339)
// TODO: apply changes to the result
// go: 2023-10-10T19:57:22+02:00
// task: 20231010T195722Z
result = strings.Replace(result, "-", "", 2)
result = strings.Replace(result, ":", "", 2)
result = strings.ReplaceAll(result, "+02:00", "Z")
return result
}
func GetTasks(filter Filter) ([]Task, error) { func GetTasks(filter Filter) ([]Task, error) {
filter.filter = append(filter.filter, "export") filter.filter = append(filter.filter, "export")
cmd := exec.Command("task", filter.filter...) cmd := exec.Command("task", filter.filter...)