add: push changes to gitea server after merging local changes
This implementation has no configuration yet to authorize accordingly. WIP for #3 and #4.
This commit is contained in:
@@ -29,6 +29,12 @@ func main() {
|
|||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Push changes to %s\n", repository.Full_name)
|
||||||
|
if err = project.Push(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
// task := taskwarrior.NewTask(
|
// task := taskwarrior.NewTask(
|
||||||
|
|||||||
@@ -1,11 +1,72 @@
|
|||||||
package diff
|
package diff
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
func Prompt(value, theirs, mine string) string {
|
func Prompt(value, theirs, mine string) string {
|
||||||
// TODO: create tmp file with the corresponding contents
|
// TODO: create tmp file with the corresponding contents
|
||||||
// open tmp file using the $EDITOR environment variable
|
// open tmp file using the $EDITOR environment variable
|
||||||
// parse and return output of tmp file after $EDITOR execution has been completed
|
// parse and return output of tmp file after $EDITOR execution has been completed
|
||||||
fmt.Printf("\tPrompting for value: '%s'\n", value)
|
value = strings.ReplaceAll(value, "\r\n", "\n")
|
||||||
|
theirs = strings.ReplaceAll(theirs, "\r\n", "\n")
|
||||||
|
mine = strings.ReplaceAll(mine, "\r\n", "\n")
|
||||||
|
file, err := os.CreateTemp("", "gitw-*")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
// prepare file contents
|
||||||
|
if _, err := file.WriteString(value); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
if _, err := file.WriteString("\n\n--- theirs ---\n\n"); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
if _, err := file.WriteString(theirs); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
if _, err := file.WriteString("\n\n--- mine ---\n\n"); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
if _, err := file.WriteString(mine); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
cmd := exec.Command(os.Getenv("EDITOR"), file.Name())
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
contents, err := os.ReadFile(file.Name())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
// TODO: what should I do with the '\r' characters? Who and when should they be handled?
|
||||||
|
re := regexp.MustCompile("(?s)^(.*)--- theirs ---")
|
||||||
|
matches := re.FindAllStringSubmatch(string(contents), -1)
|
||||||
|
if len(matches) > 0 && len(matches[0]) == 2 {
|
||||||
|
return matches[0][1]
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, errors.New("No matches found, could not read merged value. Aborting."))
|
||||||
|
os.Exit(-1)
|
||||||
|
return "" // just to make compiler happy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package gitea
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -61,6 +62,7 @@ func (gitea *Gitea) GetComment(repo Repository, id uint) (Comment, error) {
|
|||||||
return comment, err
|
return comment, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: how to do basic authorization?
|
||||||
func (gitea *Gitea) UpdateComment(repo Repository, comment Comment) error {
|
func (gitea *Gitea) UpdateComment(repo Repository, comment Comment) error {
|
||||||
url := fmt.Sprintf("%s/repos/%s/issues/comments/%d", gitea.Url(), repo.Full_name, comment.Id)
|
url := fmt.Sprintf("%s/repos/%s/issues/comments/%d", gitea.Url(), repo.Full_name, comment.Id)
|
||||||
json, err := json.Marshal(&map[string]interface{}{
|
json, err := json.Marshal(&map[string]interface{}{
|
||||||
@@ -69,10 +71,20 @@ func (gitea *Gitea) UpdateComment(repo Repository, comment Comment) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(json))
|
client := &http.Client{}
|
||||||
|
request, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(json))
|
||||||
|
request.Header.Set("content-type", "application/json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
result, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer result.Body.Close()
|
||||||
|
if result.StatusCode != 201 {
|
||||||
|
return errors.New(fmt.Sprintf("\tRequest returned status: %s\n", result.Status))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package gitea
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -80,15 +81,16 @@ func (gitea *Gitea) GetIssue(repo Repository, id uint) (Issue, error) {
|
|||||||
return issue, nil
|
return issue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: how to do basic authorization?
|
||||||
func (gitea *Gitea) UpdateIssue(repo Repository, issue Issue) error {
|
func (gitea *Gitea) UpdateIssue(repo Repository, issue Issue) error {
|
||||||
url := fmt.Sprintf("%s/repos/%s/issues/%d", gitea.Url(), repo.Full_name, issue.Id)
|
url := fmt.Sprintf("%s/repos/%s/issues/%d", gitea.Url(), repo.Full_name, issue.Id)
|
||||||
var payload map[string]interface{}
|
payload := make(map[string]interface{})
|
||||||
payload["assignee"] = issue.Assignee
|
// payload["assignee"] = issue.Assignee
|
||||||
payload["assignees"] = issue.Assignees
|
// payload["assignees"] = issue.Assignees
|
||||||
payload["body"] = issue.Body
|
payload["body"] = issue.Body
|
||||||
payload["due_date"] = issue.Due_date
|
payload["due_date"] = issue.Due_date
|
||||||
payload["milestone"] = issue.Milestone
|
payload["milestone"] = issue.Milestone.Id
|
||||||
payload["ref"] = issue.Ref
|
// payload["ref"] = issue.Ref
|
||||||
payload["state"] = issue.State
|
payload["state"] = issue.State
|
||||||
payload["title"] = issue.Title
|
payload["title"] = issue.Title
|
||||||
payload["unset_due_date"] = issue.Due_date.Unix() == 0
|
payload["unset_due_date"] = issue.Due_date.Unix() == 0
|
||||||
@@ -96,24 +98,34 @@ func (gitea *Gitea) UpdateIssue(repo Repository, issue Issue) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(json))
|
client := &http.Client{}
|
||||||
|
request, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(json))
|
||||||
|
request.Header.Set("content-type", "application/json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
result, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer result.Body.Close()
|
||||||
|
if result.StatusCode != 201 {
|
||||||
|
return errors.New(fmt.Sprintf("\tRequest returned status: %s\n", result.Status))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gitea *Gitea) NewIssue(repo Repository, issue Issue) (Issue, error) {
|
func (gitea *Gitea) NewIssue(repo Repository, issue Issue) (Issue, error) {
|
||||||
url := fmt.Sprintf("%s/repos/%s/issues", gitea.Url(), repo.Full_name)
|
url := fmt.Sprintf("%s/repos/%s/issues", gitea.Url(), repo.Full_name)
|
||||||
var payload map[string]interface{}
|
payload := make(map[string]interface{})
|
||||||
payload["assignee"] = issue.Assignee
|
// payload["assignee"] = issue.Assignee
|
||||||
payload["assignees"] = issue.Assignees
|
// payload["assignees"] = issue.Assignees
|
||||||
payload["body"] = issue.Body
|
payload["body"] = issue.Body
|
||||||
payload["closed"] = issue.State == string(CLOSED)
|
payload["closed"] = issue.State == string(CLOSED)
|
||||||
payload["due_date"] = issue.Due_date
|
payload["due_date"] = issue.Due_date
|
||||||
payload["lables"] = issue.Labels
|
payload["lables"] = issue.Labels
|
||||||
payload["milestone"] = issue.Milestone.Title != ""
|
payload["milestone"] = issue.Milestone.Id
|
||||||
payload["ref"] = issue.Ref
|
// payload["ref"] = issue.Ref
|
||||||
payload["title"] = issue.Title
|
payload["title"] = issue.Title
|
||||||
json_payload, err := json.Marshal(&payload)
|
json_payload, err := json.Marshal(&payload)
|
||||||
var res Issue
|
var res Issue
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package gitea
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -113,9 +114,10 @@ func (gitea *Gitea) GetMilestone(repo Repository, id uint) (Milestone, error) {
|
|||||||
return milestone, nil
|
return milestone, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: how to do basic authorization?
|
||||||
func (gitea *Gitea) UpdateMilestone(repo Repository, milestone Milestone) error {
|
func (gitea *Gitea) UpdateMilestone(repo Repository, milestone Milestone) error {
|
||||||
url := fmt.Sprintf("%s/repos/%s/milestones/%d", gitea.Url(), repo.Full_name, milestone.Id)
|
url := fmt.Sprintf("%s/repos/%s/milestones/%d", gitea.Url(), repo.Full_name, milestone.Id)
|
||||||
var payload map[string]interface{}
|
payload := make(map[string]interface{})
|
||||||
payload["description"] = milestone.Description
|
payload["description"] = milestone.Description
|
||||||
payload["due_on"] = milestone.Due_on
|
payload["due_on"] = milestone.Due_on
|
||||||
payload["state"] = milestone.State
|
payload["state"] = milestone.State
|
||||||
@@ -124,10 +126,20 @@ func (gitea *Gitea) UpdateMilestone(repo Repository, milestone Milestone) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(json))
|
client := &http.Client{}
|
||||||
|
request, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(json))
|
||||||
|
request.Header.Set("content-type", "application/json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
result, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer result.Body.Close()
|
||||||
|
if result.StatusCode != 201 {
|
||||||
|
return errors.New(fmt.Sprintf("\tRequest returned status: %s\n", result.Status))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,9 +68,86 @@ func (project *Project) Fetch() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (project *Project) Push() error {
|
||||||
|
// TODO: check if a pull is required before pushing changes
|
||||||
|
var filter taskwarrior.Filter
|
||||||
|
filter.IncludeProjects(project.repository.Name)
|
||||||
|
tasks, err := taskwarrior.GetTasks(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, task := range tasks {
|
||||||
|
if task.Git_type == string(taskwarrior.ISSUE) {
|
||||||
|
var issue gitea.Issue
|
||||||
|
issue.Id = uint(task.Git_id)
|
||||||
|
issue.Title = task.Description
|
||||||
|
issue.Body = task.Annotations[0].Description
|
||||||
|
if len(task.Due) > 0 {
|
||||||
|
issue.Due_date = taskwarrior.TaskTimeToGoTime(task.Due)
|
||||||
|
} else {
|
||||||
|
issue.Due_date = time.Unix(0, 0)
|
||||||
|
}
|
||||||
|
filter.Reset()
|
||||||
|
filter.IncludeGitType(taskwarrior.MILESTONE)
|
||||||
|
filter.IncludeProjects(project.repository.Name)
|
||||||
|
milestones, err := taskwarrior.GetTasks(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, milestone := range milestones {
|
||||||
|
for _, dep := range milestone.Depends {
|
||||||
|
if task.Uuid == dep {
|
||||||
|
issue.Milestone.Id = uint(milestone.Git_id)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if task.Status == "completed" {
|
||||||
|
issue.State = string(gitea.CLOSED)
|
||||||
|
} else {
|
||||||
|
issue.State = string(gitea.OPEN)
|
||||||
|
}
|
||||||
|
if err = project.server.UpdateIssue(project.repository, issue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var milestone gitea.Milestone
|
||||||
|
milestone.Id = uint(task.Git_id)
|
||||||
|
milestone.Title = task.Description
|
||||||
|
milestone.Description = task.Annotations[0].Description
|
||||||
|
milestone.Due_on = taskwarrior.TaskTimeToGoTime(task.Due)
|
||||||
|
if task.Status == "completed" {
|
||||||
|
milestone.State = string(gitea.CLOSED)
|
||||||
|
} else {
|
||||||
|
milestone.State = string(gitea.OPEN)
|
||||||
|
}
|
||||||
|
if err = project.server.UpdateMilestone(project.repository, milestone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, annotation := range task.Annotations {
|
||||||
|
if i == 0 { // that the body of the issue / milestone
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var comment gitea.Comment
|
||||||
|
comment.Id = uint(task.Git_id)
|
||||||
|
comment.Body = annotation.Description
|
||||||
|
if err = project.server.UpdateComment(project.repository, comment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: tasks should include the corresponding time's of the git related issues and milestones
|
// TODO: tasks should include the corresponding time's of the git related issues and milestones
|
||||||
func (project *Project) merge() error {
|
func (project *Project) merge() error {
|
||||||
dry_run := true // make this a parameter / cli flag
|
dry_run := false // make this a parameter / cli flag
|
||||||
var tasks []taskwarrior.Task
|
var tasks []taskwarrior.Task
|
||||||
var task taskwarrior.Task
|
var task taskwarrior.Task
|
||||||
var filter taskwarrior.Filter
|
var filter taskwarrior.Filter
|
||||||
@@ -208,7 +285,7 @@ func (issue *Issue) MergeTask(task taskwarrior.Task) taskwarrior.Task {
|
|||||||
task.Status = diff.Prompt(task.Status, issue.git_issue.State, task.Status)
|
task.Status = diff.Prompt(task.Status, issue.git_issue.State, task.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(task.Annotations) != len(issue.comments) {
|
// TODO: try to automatically derive the correct required changes
|
||||||
var annotations []string
|
var annotations []string
|
||||||
annotations = append(annotations, issue.git_issue.Body)
|
annotations = append(annotations, issue.git_issue.Body)
|
||||||
for _, annotation := range task.Annotations {
|
for _, annotation := range task.Annotations {
|
||||||
@@ -218,22 +295,16 @@ func (issue *Issue) MergeTask(task taskwarrior.Task) taskwarrior.Task {
|
|||||||
for _, comment := range issue.comments {
|
for _, comment := range issue.comments {
|
||||||
comments = append(comments, comment.Body)
|
comments = append(comments, comment.Body)
|
||||||
}
|
}
|
||||||
// TODO: how should the user manually enter the values?
|
|
||||||
// TODO: provide options for theirs, mine and manual edit of the task annotations
|
// TODO: provide options for theirs, mine and manual edit of the task annotations
|
||||||
// annotation_joined := prompt(strings.Join(annotations, "\n\n"), strings.Join(comments, "\n\n"), strings.Join(annotations, "\n\n"))
|
annotation_joined := diff.Prompt(strings.Join(annotations, "\n\n"), strings.Join(comments, "\n\n"), strings.Join(annotations, "\n\n"))
|
||||||
} else {
|
for i, description := range strings.Split(annotation_joined, "\n\n") {
|
||||||
for i := range issue.comments {
|
if len(description) == 0 {
|
||||||
// check the modification times?
|
continue
|
||||||
annotation := task.Annotations[i]
|
|
||||||
comment := issue.comments[i]
|
|
||||||
modification_time := taskwarrior.TaskTimeToGoTime(annotation.Entry)
|
|
||||||
if comment.Updated_at.After(modification_time) {
|
|
||||||
annotation.Description = comment.Body
|
|
||||||
annotation.Entry = taskwarrior.GoTimeToTaskTime(comment.Updated_at)
|
|
||||||
} else {
|
|
||||||
annotation.Description = diff.Prompt(annotation.Description, comment.Body, annotation.Description)
|
|
||||||
}
|
}
|
||||||
task.Annotations[i] = annotation
|
if i < len(task.Annotations) {
|
||||||
|
task.Annotations[i].Description = description
|
||||||
|
} else {
|
||||||
|
task.AppendComment(description, time.Now().In(time.Local))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user