diff --git a/cmd/gitw/main.go b/cmd/gitw/main.go index 13c57b1..3d699d2 100644 --- a/cmd/gitw/main.go +++ b/cmd/gitw/main.go @@ -6,13 +6,11 @@ import ( "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/taskwarrior" ) func main() { // TODO: server url may be also be derived from the git configuration? server := gitea.NewGitea("https://gitea.yves-biener.de") - repository, err := gitw.Discover() if err != nil { fmt.Fprintln(os.Stderr, err) @@ -23,61 +21,13 @@ func main() { fmt.Fprintln(os.Stderr, err) os.Exit(-1) } - fmt.Printf("%#v\n", repository) - fmt.Println("---") + project := gitw.NewProject(server, repository) - issue, err := server.GetIssue(repository, 1) - if err != nil { + if err = project.Fetch(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(-1) } - milestone, err := server.GetMilestone(repository, 1) - 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("---") + fmt.Printf("%#v\n", project) // NOTE: this can be used to add / modify tasks // var update_tasks []taskwarrior.Task diff --git a/internal/gitw/project.go b/internal/gitw/project.go new file mode 100644 index 0000000..c91f13b --- /dev/null +++ b/internal/gitw/project.go @@ -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 +} diff --git a/internal/taskwarrior/filter.go b/internal/taskwarrior/filter.go index abb7e5a..d6d8f92 100644 --- a/internal/taskwarrior/filter.go +++ b/internal/taskwarrior/filter.go @@ -6,6 +6,11 @@ type Filter struct { filter []string } +type Type string + +const MILESTONE Type = "milestone" +const ISSUE Type = "issue" + func (f *Filter) Reset() { f.filter = nil } @@ -33,3 +38,11 @@ func (f *Filter) IncludeIds(ids ...uint) { 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)) +} diff --git a/internal/taskwarrior/task.go b/internal/taskwarrior/task.go index e1c6671..665895a 100644 --- a/internal/taskwarrior/task.go +++ b/internal/taskwarrior/task.go @@ -3,7 +3,11 @@ package taskwarrior import ( "bytes" "encoding/json" + "fmt" + "os" "os/exec" + "strings" + "time" ) /// A task with an Id of 0 is either `completed` or `deleted` which is also @@ -11,6 +15,8 @@ import ( 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"` @@ -29,14 +35,55 @@ type Annotation struct { 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{ Project: project, - Tags: tags, 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) { filter.filter = append(filter.filter, "export") cmd := exec.Command("task", filter.filter...)