aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--integrations/api_repo_edit_test.go106
-rw-r--r--models/repo.go28
-rw-r--r--modules/structs/repo.go60
-rw-r--r--routers/api/v1/repo/repo.go100
-rw-r--r--templates/swagger/v1_json.tmpl74
5 files changed, 327 insertions, 41 deletions
diff --git a/integrations/api_repo_edit_test.go b/integrations/api_repo_edit_test.go
index 1231201b97..c1b513d075 100644
--- a/integrations/api_repo_edit_test.go
+++ b/integrations/api_repo_edit_test.go
@@ -23,12 +23,35 @@ func getRepoEditOptionFromRepo(repo *models.Repository) *api.EditRepoOption {
website := repo.Website
private := repo.IsPrivate
hasIssues := false
- if _, err := repo.GetUnit(models.UnitTypeIssues); err == nil {
+ var internalTracker *api.InternalTracker
+ var externalTracker *api.ExternalTracker
+ if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil {
+ config := unit.IssuesConfig()
hasIssues = true
+ internalTracker = &api.InternalTracker{
+ EnableTimeTracker: config.EnableTimetracker,
+ AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime,
+ EnableIssueDependencies: config.EnableDependencies,
+ }
+ } else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil {
+ config := unit.ExternalTrackerConfig()
+ hasIssues = true
+ externalTracker = &api.ExternalTracker{
+ ExternalTrackerURL: config.ExternalTrackerURL,
+ ExternalTrackerFormat: config.ExternalTrackerFormat,
+ ExternalTrackerStyle: config.ExternalTrackerStyle,
+ }
}
hasWiki := false
+ var externalWiki *api.ExternalWiki
if _, err := repo.GetUnit(models.UnitTypeWiki); err == nil {
hasWiki = true
+ } else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil {
+ hasWiki = true
+ config := unit.ExternalWikiConfig()
+ externalWiki = &api.ExternalWiki{
+ ExternalWikiURL: config.ExternalWikiURL,
+ }
}
defaultBranch := repo.DefaultBranch
hasPullRequests := false
@@ -53,7 +76,10 @@ func getRepoEditOptionFromRepo(repo *models.Repository) *api.EditRepoOption {
Website: &website,
Private: &private,
HasIssues: &hasIssues,
+ ExternalTracker: externalTracker,
+ InternalTracker: internalTracker,
HasWiki: &hasWiki,
+ ExternalWiki: externalWiki,
DefaultBranch: &defaultBranch,
HasPullRequests: &hasPullRequests,
IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts,
@@ -143,6 +169,84 @@ func TestAPIRepoEdit(t *testing.T) {
assert.Equal(t, *repoEditOption.Archived, *repo1editedOption.Archived)
assert.Equal(t, *repoEditOption.Private, *repo1editedOption.Private)
assert.Equal(t, *repoEditOption.HasWiki, *repo1editedOption.HasWiki)
+
+ //Test editing repo1 to use internal issue and wiki (default)
+ *repoEditOption.HasIssues = true
+ repoEditOption.ExternalTracker = nil
+ repoEditOption.InternalTracker = &api.InternalTracker{
+ EnableTimeTracker: false,
+ AllowOnlyContributorsToTrackTime: false,
+ EnableIssueDependencies: false,
+ }
+ *repoEditOption.HasWiki = true
+ repoEditOption.ExternalWiki = nil
+ url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2)
+ req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ DecodeJSON(t, resp, &repo)
+ assert.NotNil(t, repo)
+ // check repo1 was written to database
+ repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+ repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
+ assert.Equal(t, *repo1editedOption.HasIssues, true)
+ assert.Nil(t, repo1editedOption.ExternalTracker)
+ assert.Equal(t, *repo1editedOption.InternalTracker, *repoEditOption.InternalTracker)
+ assert.Equal(t, *repo1editedOption.HasWiki, true)
+ assert.Nil(t, repo1editedOption.ExternalWiki)
+
+ //Test editing repo1 to use external issue and wiki
+ repoEditOption.ExternalTracker = &api.ExternalTracker{
+ ExternalTrackerURL: "http://www.somewebsite.com",
+ ExternalTrackerFormat: "http://www.somewebsite.com/{user}/{repo}?issue={index}",
+ ExternalTrackerStyle: "alphanumeric",
+ }
+ repoEditOption.ExternalWiki = &api.ExternalWiki{
+ ExternalWikiURL: "http://www.somewebsite.com",
+ }
+ req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ DecodeJSON(t, resp, &repo)
+ assert.NotNil(t, repo)
+ // check repo1 was written to database
+ repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+ repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
+ assert.Equal(t, *repo1editedOption.HasIssues, true)
+ assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker)
+ assert.Equal(t, *repo1editedOption.HasWiki, true)
+ assert.Equal(t, *repo1editedOption.ExternalWiki, *repoEditOption.ExternalWiki)
+
+ // Do some tests with invalid URL for external tracker and wiki
+ repoEditOption.ExternalTracker.ExternalTrackerURL = "htp://www.somewebsite.com"
+ req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
+ resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ repoEditOption.ExternalTracker.ExternalTrackerURL = "http://www.somewebsite.com"
+ repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user/{repo}?issue={index}"
+ req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
+ resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity)
+ repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user}/{repo}?issue={index}"
+ repoEditOption.ExternalWiki.ExternalWikiURL = "htp://www.somewebsite.com"
+ req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
+ resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity)
+
+ //Test small repo change through API with issue and wiki option not set; They shall not be touched.
+ *repoEditOption.Description = "small change"
+ repoEditOption.HasIssues = nil
+ repoEditOption.ExternalTracker = nil
+ repoEditOption.HasWiki = nil
+ repoEditOption.ExternalWiki = nil
+ req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ DecodeJSON(t, resp, &repo)
+ assert.NotNil(t, repo)
+ // check repo1 was written to database
+ repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+ repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
+ assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description)
+ assert.Equal(t, *repo1editedOption.HasIssues, true)
+ assert.NotNil(t, *repo1editedOption.ExternalTracker)
+ assert.Equal(t, *repo1editedOption.HasWiki, true)
+ assert.NotNil(t, *repo1editedOption.ExternalWiki)
+
// reset repo in db
url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2)
req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption)
diff --git a/models/repo.go b/models/repo.go
index eb7f286fec..69f32ae4a5 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -275,12 +275,35 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
}
}
hasIssues := false
- if _, err := repo.getUnit(e, UnitTypeIssues); err == nil {
+ var externalTracker *api.ExternalTracker
+ var internalTracker *api.InternalTracker
+ if unit, err := repo.getUnit(e, UnitTypeIssues); err == nil {
+ config := unit.IssuesConfig()
hasIssues = true
+ internalTracker = &api.InternalTracker{
+ EnableTimeTracker: config.EnableTimetracker,
+ AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime,
+ EnableIssueDependencies: config.EnableDependencies,
+ }
+ } else if unit, err := repo.getUnit(e, UnitTypeExternalTracker); err == nil {
+ config := unit.ExternalTrackerConfig()
+ hasIssues = true
+ externalTracker = &api.ExternalTracker{
+ ExternalTrackerURL: config.ExternalTrackerURL,
+ ExternalTrackerFormat: config.ExternalTrackerFormat,
+ ExternalTrackerStyle: config.ExternalTrackerStyle,
+ }
}
hasWiki := false
+ var externalWiki *api.ExternalWiki
if _, err := repo.getUnit(e, UnitTypeWiki); err == nil {
hasWiki = true
+ } else if unit, err := repo.getUnit(e, UnitTypeExternalWiki); err == nil {
+ hasWiki = true
+ config := unit.ExternalWikiConfig()
+ externalWiki = &api.ExternalWiki{
+ ExternalWikiURL: config.ExternalWikiURL,
+ }
}
hasPullRequests := false
ignoreWhitespaceConflicts := false
@@ -324,7 +347,10 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
Updated: repo.UpdatedUnix.AsTime(),
Permissions: permission,
HasIssues: hasIssues,
+ ExternalTracker: externalTracker,
+ InternalTracker: internalTracker,
HasWiki: hasWiki,
+ ExternalWiki: externalWiki,
HasPullRequests: hasPullRequests,
IgnoreWhitespaceConflicts: ignoreWhitespaceConflicts,
AllowMerge: allowMerge,
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index d94980fca4..87396d6ce9 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -15,6 +15,35 @@ type Permission struct {
Pull bool `json:"pull"`
}
+// InternalTracker represents settings for internal tracker
+// swagger:model
+type InternalTracker struct {
+ // Enable time tracking (Built-in issue tracker)
+ EnableTimeTracker bool `json:"enable_time_tracker"`
+ // Let only contributors track time (Built-in issue tracker)
+ AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"`
+ // Enable dependencies for issues and pull requests (Built-in issue tracker)
+ EnableIssueDependencies bool `json:"enable_issue_dependencies"`
+}
+
+// ExternalTracker represents settings for external tracker
+// swagger:model
+type ExternalTracker struct {
+ // URL of external issue tracker.
+ ExternalTrackerURL string `json:"external_tracker_url"`
+ // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
+ ExternalTrackerFormat string `json:"external_tracker_format"`
+ // External Issue Tracker Number Format, either `numeric` or `alphanumeric`
+ ExternalTrackerStyle string `json:"external_tracker_style"`
+}
+
+// ExternalWiki represents setting for external wiki
+// swagger:model
+type ExternalWiki struct {
+ // URL of external wiki.
+ ExternalWikiURL string `json:"external_wiki_url"`
+}
+
// Repository represents a repository
type Repository struct {
ID int64 `json:"id"`
@@ -42,17 +71,20 @@ type Repository struct {
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
- Updated time.Time `json:"updated_at"`
- Permissions *Permission `json:"permissions,omitempty"`
- HasIssues bool `json:"has_issues"`
- HasWiki bool `json:"has_wiki"`
- HasPullRequests bool `json:"has_pull_requests"`
- IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
- AllowMerge bool `json:"allow_merge_commits"`
- AllowRebase bool `json:"allow_rebase"`
- AllowRebaseMerge bool `json:"allow_rebase_explicit"`
- AllowSquash bool `json:"allow_squash_merge"`
- AvatarURL string `json:"avatar_url"`
+ Updated time.Time `json:"updated_at"`
+ Permissions *Permission `json:"permissions,omitempty"`
+ HasIssues bool `json:"has_issues"`
+ InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
+ ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
+ HasWiki bool `json:"has_wiki"`
+ ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
+ HasPullRequests bool `json:"has_pull_requests"`
+ IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
+ AllowMerge bool `json:"allow_merge_commits"`
+ AllowRebase bool `json:"allow_rebase"`
+ AllowRebaseMerge bool `json:"allow_rebase_explicit"`
+ AllowSquash bool `json:"allow_squash_merge"`
+ AvatarURL string `json:"avatar_url"`
}
// CreateRepoOption options when creating repository
@@ -95,8 +127,14 @@ type EditRepoOption struct {
Private *bool `json:"private,omitempty"`
// either `true` to enable issues for this repository or `false` to disable them.
HasIssues *bool `json:"has_issues,omitempty"`
+ // set this structure to configure internal issue tracker (requires has_issues)
+ InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
+ // set this structure to use external issue tracker (requires has_issues)
+ ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
// either `true` to enable the wiki for this repository or `false` to disable it.
HasWiki *bool `json:"has_wiki,omitempty"`
+ // set this structure to use external wiki instead of internal (requires has_wiki)
+ ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
// sets the default branch for this repository.
DefaultBranch *string `json:"default_branch,omitempty"`
// either `true` to allow pull requests, or `false` to prevent pull request.
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 513e7a37b3..d8b06862a5 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/routers/api/v1/convert"
mirror_service "code.gitea.io/gitea/services/mirror"
)
@@ -669,27 +670,56 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
units = append(units, *unit)
}
} else if *opts.HasIssues {
- // We don't currently allow setting individual issue settings through the API,
- // only can enable/disable issues, so when enabling issues,
- // we either get the existing config which means it was already enabled,
- // or create a new config since it doesn't exist.
- unit, err := repo.GetUnit(models.UnitTypeIssues)
- var config *models.IssuesConfig
- if err != nil {
- // Unit type doesn't exist so we make a new config file with default values
- config = &models.IssuesConfig{
- EnableTimetracker: true,
- AllowOnlyContributorsToTrackTime: true,
- EnableDependencies: true,
+ if opts.ExternalTracker != nil {
+
+ // Check that values are valid
+ if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
+ err := fmt.Errorf("External tracker URL not valid")
+ ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
+ return err
}
+ if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
+ err := fmt.Errorf("External tracker URL format not valid")
+ ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
+ return err
+ }
+
+ units = append(units, models.RepoUnit{
+ RepoID: repo.ID,
+ Type: models.UnitTypeExternalTracker,
+ Config: &models.ExternalTrackerConfig{
+ ExternalTrackerURL: opts.ExternalTracker.ExternalTrackerURL,
+ ExternalTrackerFormat: opts.ExternalTracker.ExternalTrackerFormat,
+ ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
+ },
+ })
} else {
- config = unit.IssuesConfig()
+ // Default to built-in tracker
+ var config *models.IssuesConfig
+
+ if opts.InternalTracker != nil {
+ config = &models.IssuesConfig{
+ EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
+ AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
+ EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
+ }
+ } else if unit, err := repo.GetUnit(models.UnitTypeIssues); err != nil {
+ // Unit type doesn't exist so we make a new config file with default values
+ config = &models.IssuesConfig{
+ EnableTimetracker: true,
+ AllowOnlyContributorsToTrackTime: true,
+ EnableDependencies: true,
+ }
+ } else {
+ config = unit.IssuesConfig()
+ }
+
+ units = append(units, models.RepoUnit{
+ RepoID: repo.ID,
+ Type: models.UnitTypeIssues,
+ Config: config,
+ })
}
- units = append(units, models.RepoUnit{
- RepoID: repo.ID,
- Type: models.UnitTypeIssues,
- Config: config,
- })
}
if opts.HasWiki == nil {
@@ -700,16 +730,30 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
units = append(units, *unit)
}
} else if *opts.HasWiki {
- // We don't currently allow setting individual wiki settings through the API,
- // only can enable/disable the wiki, so when enabling the wiki,
- // we either get the existing config which means it was already enabled,
- // or create a new config since it doesn't exist.
- config := &models.UnitConfig{}
- units = append(units, models.RepoUnit{
- RepoID: repo.ID,
- Type: models.UnitTypeWiki,
- Config: config,
- })
+ if opts.ExternalWiki != nil {
+
+ // Check that values are valid
+ if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
+ err := fmt.Errorf("External wiki URL not valid")
+ ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
+ return err
+ }
+
+ units = append(units, models.RepoUnit{
+ RepoID: repo.ID,
+ Type: models.UnitTypeExternalWiki,
+ Config: &models.ExternalWikiConfig{
+ ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
+ },
+ })
+ } else {
+ config := &models.UnitConfig{}
+ units = append(units, models.RepoUnit{
+ RepoID: repo.ID,
+ Type: models.UnitTypeWiki,
+ Config: config,
+ })
+ }
}
if opts.HasPullRequests == nil {
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index fcc26f5c54..d8750d8bcc 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -8469,6 +8469,12 @@
"type": "string",
"x-go-name": "Description"
},
+ "external_tracker": {
+ "$ref": "#/definitions/ExternalTracker"
+ },
+ "external_wiki": {
+ "$ref": "#/definitions/ExternalWiki"
+ },
"has_issues": {
"description": "either `true` to enable issues for this repository or `false` to disable them.",
"type": "boolean",
@@ -8489,6 +8495,9 @@
"type": "boolean",
"x-go-name": "IgnoreWhitespaceConflicts"
},
+ "internal_tracker": {
+ "$ref": "#/definitions/InternalTracker"
+ },
"name": {
"description": "name of the repository",
"type": "string",
@@ -8644,6 +8653,40 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "ExternalTracker": {
+ "description": "ExternalTracker represents settings for external tracker",
+ "type": "object",
+ "properties": {
+ "external_tracker_format": {
+ "description": "External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.",
+ "type": "string",
+ "x-go-name": "ExternalTrackerFormat"
+ },
+ "external_tracker_style": {
+ "description": "External Issue Tracker Number Format, either `numeric` or `alphanumeric`",
+ "type": "string",
+ "x-go-name": "ExternalTrackerStyle"
+ },
+ "external_tracker_url": {
+ "description": "URL of external issue tracker.",
+ "type": "string",
+ "x-go-name": "ExternalTrackerURL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
+ "ExternalWiki": {
+ "description": "ExternalWiki represents setting for external wiki",
+ "type": "object",
+ "properties": {
+ "external_wiki_url": {
+ "description": "URL of external wiki.",
+ "type": "string",
+ "x-go-name": "ExternalWikiURL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"FileCommitResponse": {
"type": "object",
"title": "FileCommitResponse contains information generated from a Git commit for a repo's file.",
@@ -9008,6 +9051,28 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "InternalTracker": {
+ "description": "InternalTracker represents settings for internal tracker",
+ "type": "object",
+ "properties": {
+ "allow_only_contributors_to_track_time": {
+ "description": "Let only contributors track time (Built-in issue tracker)",
+ "type": "boolean",
+ "x-go-name": "AllowOnlyContributorsToTrackTime"
+ },
+ "enable_issue_dependencies": {
+ "description": "Enable dependencies for issues and pull requests (Built-in issue tracker)",
+ "type": "boolean",
+ "x-go-name": "EnableIssueDependencies"
+ },
+ "enable_time_tracker": {
+ "description": "Enable time tracking (Built-in issue tracker)",
+ "type": "boolean",
+ "x-go-name": "EnableTimeTracker"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"Issue": {
"description": "Issue represents an issue in a repository",
"type": "object",
@@ -9863,6 +9928,12 @@
"type": "boolean",
"x-go-name": "Empty"
},
+ "external_tracker": {
+ "$ref": "#/definitions/ExternalTracker"
+ },
+ "external_wiki": {
+ "$ref": "#/definitions/ExternalWiki"
+ },
"fork": {
"type": "boolean",
"x-go-name": "Fork"
@@ -9901,6 +9972,9 @@
"type": "boolean",
"x-go-name": "IgnoreWhitespaceConflicts"
},
+ "internal_tracker": {
+ "$ref": "#/definitions/InternalTracker"
+ },
"mirror": {
"type": "boolean",
"x-go-name": "Mirror"