You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo.go 32 kB

API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
5 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
10 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "bytes"
  8. "errors"
  9. "fmt"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/modules/auth"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/graceful"
  18. "code.gitea.io/gitea/modules/log"
  19. "code.gitea.io/gitea/modules/migrations"
  20. "code.gitea.io/gitea/modules/notification"
  21. repo_module "code.gitea.io/gitea/modules/repository"
  22. "code.gitea.io/gitea/modules/setting"
  23. api "code.gitea.io/gitea/modules/structs"
  24. "code.gitea.io/gitea/modules/util"
  25. "code.gitea.io/gitea/modules/validation"
  26. "code.gitea.io/gitea/routers/api/v1/utils"
  27. mirror_service "code.gitea.io/gitea/services/mirror"
  28. repo_service "code.gitea.io/gitea/services/repository"
  29. )
  30. var searchOrderByMap = map[string]map[string]models.SearchOrderBy{
  31. "asc": {
  32. "alpha": models.SearchOrderByAlphabetically,
  33. "created": models.SearchOrderByOldest,
  34. "updated": models.SearchOrderByLeastUpdated,
  35. "size": models.SearchOrderBySize,
  36. "id": models.SearchOrderByID,
  37. },
  38. "desc": {
  39. "alpha": models.SearchOrderByAlphabeticallyReverse,
  40. "created": models.SearchOrderByNewest,
  41. "updated": models.SearchOrderByRecentUpdated,
  42. "size": models.SearchOrderBySizeReverse,
  43. "id": models.SearchOrderByIDReverse,
  44. },
  45. }
  46. // Search repositories via options
  47. func Search(ctx *context.APIContext) {
  48. // swagger:operation GET /repos/search repository repoSearch
  49. // ---
  50. // summary: Search for repositories
  51. // produces:
  52. // - application/json
  53. // parameters:
  54. // - name: q
  55. // in: query
  56. // description: keyword
  57. // type: string
  58. // - name: topic
  59. // in: query
  60. // description: Limit search to repositories with keyword as topic
  61. // type: boolean
  62. // - name: includeDesc
  63. // in: query
  64. // description: include search of keyword within repository description
  65. // type: boolean
  66. // - name: uid
  67. // in: query
  68. // description: search only for repos that the user with the given id owns or contributes to
  69. // type: integer
  70. // format: int64
  71. // - name: priority_owner_id
  72. // in: query
  73. // description: repo owner to prioritize in the results
  74. // type: integer
  75. // format: int64
  76. // - name: starredBy
  77. // in: query
  78. // description: search only for repos that the user with the given id has starred
  79. // type: integer
  80. // format: int64
  81. // - name: private
  82. // in: query
  83. // description: include private repositories this user has access to (defaults to true)
  84. // type: boolean
  85. // - name: template
  86. // in: query
  87. // description: include template repositories this user has access to (defaults to true)
  88. // type: boolean
  89. // - name: mode
  90. // in: query
  91. // description: type of repository to search for. Supported values are
  92. // "fork", "source", "mirror" and "collaborative"
  93. // type: string
  94. // - name: exclusive
  95. // in: query
  96. // description: if `uid` is given, search only for repos that the user owns
  97. // type: boolean
  98. // - name: sort
  99. // in: query
  100. // description: sort repos by attribute. Supported values are
  101. // "alpha", "created", "updated", "size", and "id".
  102. // Default is "alpha"
  103. // type: string
  104. // - name: order
  105. // in: query
  106. // description: sort order, either "asc" (ascending) or "desc" (descending).
  107. // Default is "asc", ignored if "sort" is not specified.
  108. // type: string
  109. // - name: page
  110. // in: query
  111. // description: page number of results to return (1-based)
  112. // type: integer
  113. // - name: limit
  114. // in: query
  115. // description: page size of results, maximum page size is 50
  116. // type: integer
  117. // responses:
  118. // "200":
  119. // "$ref": "#/responses/SearchResults"
  120. // "422":
  121. // "$ref": "#/responses/validationError"
  122. opts := &models.SearchRepoOptions{
  123. ListOptions: utils.GetListOptions(ctx),
  124. Actor: ctx.User,
  125. Keyword: strings.Trim(ctx.Query("q"), " "),
  126. OwnerID: ctx.QueryInt64("uid"),
  127. PriorityOwnerID: ctx.QueryInt64("priority_owner_id"),
  128. TopicOnly: ctx.QueryBool("topic"),
  129. Collaborate: util.OptionalBoolNone,
  130. Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")),
  131. Template: util.OptionalBoolNone,
  132. StarredByID: ctx.QueryInt64("starredBy"),
  133. IncludeDescription: ctx.QueryBool("includeDesc"),
  134. }
  135. if ctx.Query("template") != "" {
  136. opts.Template = util.OptionalBoolOf(ctx.QueryBool("template"))
  137. }
  138. if ctx.QueryBool("exclusive") {
  139. opts.Collaborate = util.OptionalBoolFalse
  140. }
  141. var mode = ctx.Query("mode")
  142. switch mode {
  143. case "source":
  144. opts.Fork = util.OptionalBoolFalse
  145. opts.Mirror = util.OptionalBoolFalse
  146. case "fork":
  147. opts.Fork = util.OptionalBoolTrue
  148. case "mirror":
  149. opts.Mirror = util.OptionalBoolTrue
  150. case "collaborative":
  151. opts.Mirror = util.OptionalBoolFalse
  152. opts.Collaborate = util.OptionalBoolTrue
  153. case "":
  154. default:
  155. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
  156. return
  157. }
  158. var sortMode = ctx.Query("sort")
  159. if len(sortMode) > 0 {
  160. var sortOrder = ctx.Query("order")
  161. if len(sortOrder) == 0 {
  162. sortOrder = "asc"
  163. }
  164. if searchModeMap, ok := searchOrderByMap[sortOrder]; ok {
  165. if orderBy, ok := searchModeMap[sortMode]; ok {
  166. opts.OrderBy = orderBy
  167. } else {
  168. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
  169. return
  170. }
  171. } else {
  172. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
  173. return
  174. }
  175. }
  176. var err error
  177. repos, count, err := models.SearchRepository(opts)
  178. if err != nil {
  179. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  180. OK: false,
  181. Error: err.Error(),
  182. })
  183. return
  184. }
  185. results := make([]*api.Repository, len(repos))
  186. for i, repo := range repos {
  187. if err = repo.GetOwner(); err != nil {
  188. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  189. OK: false,
  190. Error: err.Error(),
  191. })
  192. return
  193. }
  194. accessMode, err := models.AccessLevel(ctx.User, repo)
  195. if err != nil {
  196. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  197. OK: false,
  198. Error: err.Error(),
  199. })
  200. }
  201. results[i] = repo.APIFormat(accessMode)
  202. }
  203. ctx.SetLinkHeader(int(count), opts.PageSize)
  204. ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
  205. ctx.JSON(http.StatusOK, api.SearchResults{
  206. OK: true,
  207. Data: results,
  208. })
  209. }
  210. // CreateUserRepo create a repository for a user
  211. func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateRepoOption) {
  212. if opt.AutoInit && opt.Readme == "" {
  213. opt.Readme = "Default"
  214. }
  215. repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{
  216. Name: opt.Name,
  217. Description: opt.Description,
  218. IssueLabels: opt.IssueLabels,
  219. Gitignores: opt.Gitignores,
  220. License: opt.License,
  221. Readme: opt.Readme,
  222. IsPrivate: opt.Private,
  223. AutoInit: opt.AutoInit,
  224. })
  225. if err != nil {
  226. if models.IsErrRepoAlreadyExist(err) {
  227. ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
  228. } else if models.IsErrNameReserved(err) ||
  229. models.IsErrNamePatternNotAllowed(err) {
  230. ctx.Error(http.StatusUnprocessableEntity, "", err)
  231. } else {
  232. ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
  233. }
  234. return
  235. }
  236. ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeOwner))
  237. }
  238. // Create one repository of mine
  239. func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
  240. // swagger:operation POST /user/repos repository user createCurrentUserRepo
  241. // ---
  242. // summary: Create a repository
  243. // consumes:
  244. // - application/json
  245. // produces:
  246. // - application/json
  247. // parameters:
  248. // - name: body
  249. // in: body
  250. // schema:
  251. // "$ref": "#/definitions/CreateRepoOption"
  252. // responses:
  253. // "201":
  254. // "$ref": "#/responses/Repository"
  255. // "409":
  256. // description: The repository with the same name already exists.
  257. // "422":
  258. // "$ref": "#/responses/validationError"
  259. if ctx.User.IsOrganization() {
  260. // Shouldn't reach this condition, but just in case.
  261. ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
  262. return
  263. }
  264. CreateUserRepo(ctx, ctx.User, opt)
  265. }
  266. // CreateOrgRepoDeprecated create one repository of the organization
  267. func CreateOrgRepoDeprecated(ctx *context.APIContext, opt api.CreateRepoOption) {
  268. // swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
  269. // ---
  270. // summary: Create a repository in an organization
  271. // deprecated: true
  272. // consumes:
  273. // - application/json
  274. // produces:
  275. // - application/json
  276. // parameters:
  277. // - name: org
  278. // in: path
  279. // description: name of organization
  280. // type: string
  281. // required: true
  282. // - name: body
  283. // in: body
  284. // schema:
  285. // "$ref": "#/definitions/CreateRepoOption"
  286. // responses:
  287. // "201":
  288. // "$ref": "#/responses/Repository"
  289. // "422":
  290. // "$ref": "#/responses/validationError"
  291. // "403":
  292. // "$ref": "#/responses/forbidden"
  293. CreateOrgRepo(ctx, opt)
  294. }
  295. // CreateOrgRepo create one repository of the organization
  296. func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
  297. // swagger:operation POST /orgs/{org}/repos organization createOrgRepo
  298. // ---
  299. // summary: Create a repository in an organization
  300. // consumes:
  301. // - application/json
  302. // produces:
  303. // - application/json
  304. // parameters:
  305. // - name: org
  306. // in: path
  307. // description: name of organization
  308. // type: string
  309. // required: true
  310. // - name: body
  311. // in: body
  312. // schema:
  313. // "$ref": "#/definitions/CreateRepoOption"
  314. // responses:
  315. // "201":
  316. // "$ref": "#/responses/Repository"
  317. // "404":
  318. // "$ref": "#/responses/notFound"
  319. // "403":
  320. // "$ref": "#/responses/forbidden"
  321. org, err := models.GetOrgByName(ctx.Params(":org"))
  322. if err != nil {
  323. if models.IsErrOrgNotExist(err) {
  324. ctx.Error(http.StatusUnprocessableEntity, "", err)
  325. } else {
  326. ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
  327. }
  328. return
  329. }
  330. if !models.HasOrgVisible(org, ctx.User) {
  331. ctx.NotFound("HasOrgVisible", nil)
  332. return
  333. }
  334. if !ctx.User.IsAdmin {
  335. canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
  336. if err != nil {
  337. ctx.ServerError("CanCreateOrgRepo", err)
  338. return
  339. } else if !canCreate {
  340. ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
  341. return
  342. }
  343. }
  344. CreateUserRepo(ctx, org, opt)
  345. }
  346. // Migrate migrate remote git repository to gitea
  347. func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
  348. // swagger:operation POST /repos/migrate repository repoMigrate
  349. // ---
  350. // summary: Migrate a remote git repository
  351. // consumes:
  352. // - application/json
  353. // produces:
  354. // - application/json
  355. // parameters:
  356. // - name: body
  357. // in: body
  358. // schema:
  359. // "$ref": "#/definitions/MigrateRepoForm"
  360. // responses:
  361. // "201":
  362. // "$ref": "#/responses/Repository"
  363. // "403":
  364. // "$ref": "#/responses/forbidden"
  365. // "422":
  366. // "$ref": "#/responses/validationError"
  367. ctxUser := ctx.User
  368. // Not equal means context user is an organization,
  369. // or is another user/organization if current user is admin.
  370. if form.UID != ctxUser.ID {
  371. org, err := models.GetUserByID(form.UID)
  372. if err != nil {
  373. if models.IsErrUserNotExist(err) {
  374. ctx.Error(http.StatusUnprocessableEntity, "", err)
  375. } else {
  376. ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
  377. }
  378. return
  379. }
  380. ctxUser = org
  381. }
  382. if ctx.HasError() {
  383. ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
  384. return
  385. }
  386. if !ctx.User.IsAdmin {
  387. if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
  388. ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
  389. return
  390. }
  391. if ctxUser.IsOrganization() {
  392. // Check ownership of organization.
  393. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  394. if err != nil {
  395. ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
  396. return
  397. } else if !isOwner {
  398. ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
  399. return
  400. }
  401. }
  402. }
  403. remoteAddr, err := form.ParseRemoteAddr(ctx.User)
  404. if err != nil {
  405. if models.IsErrInvalidCloneAddr(err) {
  406. addrErr := err.(models.ErrInvalidCloneAddr)
  407. switch {
  408. case addrErr.IsURLError:
  409. ctx.Error(http.StatusUnprocessableEntity, "", err)
  410. case addrErr.IsPermissionDenied:
  411. ctx.Error(http.StatusUnprocessableEntity, "", "You are not allowed to import local repositories.")
  412. case addrErr.IsInvalidPath:
  413. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid local path, it does not exist or not a directory.")
  414. default:
  415. ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
  416. }
  417. } else {
  418. ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err)
  419. }
  420. return
  421. }
  422. var gitServiceType = api.PlainGitService
  423. u, err := url.Parse(remoteAddr)
  424. if err == nil && strings.EqualFold(u.Host, "github.com") {
  425. gitServiceType = api.GithubService
  426. }
  427. var opts = migrations.MigrateOptions{
  428. CloneAddr: remoteAddr,
  429. RepoName: form.RepoName,
  430. Description: form.Description,
  431. Private: form.Private || setting.Repository.ForcePrivate,
  432. Mirror: form.Mirror,
  433. AuthUsername: form.AuthUsername,
  434. AuthPassword: form.AuthPassword,
  435. Wiki: form.Wiki,
  436. Issues: form.Issues,
  437. Milestones: form.Milestones,
  438. Labels: form.Labels,
  439. Comments: true,
  440. PullRequests: form.PullRequests,
  441. Releases: form.Releases,
  442. GitServiceType: gitServiceType,
  443. }
  444. if opts.Mirror {
  445. opts.Issues = false
  446. opts.Milestones = false
  447. opts.Labels = false
  448. opts.Comments = false
  449. opts.PullRequests = false
  450. opts.Releases = false
  451. }
  452. repo, err := repo_module.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
  453. Name: opts.RepoName,
  454. Description: opts.Description,
  455. OriginalURL: form.CloneAddr,
  456. GitServiceType: gitServiceType,
  457. IsPrivate: opts.Private,
  458. IsMirror: opts.Mirror,
  459. Status: models.RepositoryBeingMigrated,
  460. })
  461. if err != nil {
  462. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  463. return
  464. }
  465. opts.MigrateToRepoID = repo.ID
  466. defer func() {
  467. if e := recover(); e != nil {
  468. var buf bytes.Buffer
  469. fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
  470. err = errors.New(buf.String())
  471. }
  472. if err == nil {
  473. repo.Status = models.RepositoryReady
  474. if err := models.UpdateRepositoryCols(repo, "status"); err == nil {
  475. notification.NotifyMigrateRepository(ctx.User, ctxUser, repo)
  476. return
  477. }
  478. }
  479. if repo != nil {
  480. if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
  481. log.Error("DeleteRepository: %v", errDelete)
  482. }
  483. }
  484. }()
  485. if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, ctxUser.Name, opts); err != nil {
  486. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  487. return
  488. }
  489. log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
  490. ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeAdmin))
  491. }
  492. func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
  493. switch {
  494. case models.IsErrRepoAlreadyExist(err):
  495. ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
  496. case migrations.IsRateLimitError(err):
  497. ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
  498. case migrations.IsTwoFactorAuthError(err):
  499. ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.")
  500. case models.IsErrReachLimitOfRepo(err):
  501. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
  502. case models.IsErrNameReserved(err):
  503. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
  504. case models.IsErrNamePatternNotAllowed(err):
  505. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
  506. default:
  507. err = util.URLSanitizedError(err, remoteAddr)
  508. if strings.Contains(err.Error(), "Authentication failed") ||
  509. strings.Contains(err.Error(), "Bad credentials") ||
  510. strings.Contains(err.Error(), "could not read Username") {
  511. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Authentication failed: %v.", err))
  512. } else if strings.Contains(err.Error(), "fatal:") {
  513. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Migration failed: %v.", err))
  514. } else {
  515. ctx.Error(http.StatusInternalServerError, "MigrateRepository", err)
  516. }
  517. }
  518. }
  519. // Get one repository
  520. func Get(ctx *context.APIContext) {
  521. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  522. // ---
  523. // summary: Get a repository
  524. // produces:
  525. // - application/json
  526. // parameters:
  527. // - name: owner
  528. // in: path
  529. // description: owner of the repo
  530. // type: string
  531. // required: true
  532. // - name: repo
  533. // in: path
  534. // description: name of the repo
  535. // type: string
  536. // required: true
  537. // responses:
  538. // "200":
  539. // "$ref": "#/responses/Repository"
  540. ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  541. }
  542. // GetByID returns a single Repository
  543. func GetByID(ctx *context.APIContext) {
  544. // swagger:operation GET /repositories/{id} repository repoGetByID
  545. // ---
  546. // summary: Get a repository by id
  547. // produces:
  548. // - application/json
  549. // parameters:
  550. // - name: id
  551. // in: path
  552. // description: id of the repo to get
  553. // type: integer
  554. // format: int64
  555. // required: true
  556. // responses:
  557. // "200":
  558. // "$ref": "#/responses/Repository"
  559. repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
  560. if err != nil {
  561. if models.IsErrRepoNotExist(err) {
  562. ctx.NotFound()
  563. } else {
  564. ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
  565. }
  566. return
  567. }
  568. perm, err := models.GetUserRepoPermission(repo, ctx.User)
  569. if err != nil {
  570. ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
  571. return
  572. } else if !perm.HasAccess() {
  573. ctx.NotFound()
  574. return
  575. }
  576. ctx.JSON(http.StatusOK, repo.APIFormat(perm.AccessMode))
  577. }
  578. // Edit edit repository properties
  579. func Edit(ctx *context.APIContext, opts api.EditRepoOption) {
  580. // swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
  581. // ---
  582. // summary: Edit a repository's properties. Only fields that are set will be changed.
  583. // produces:
  584. // - application/json
  585. // parameters:
  586. // - name: owner
  587. // in: path
  588. // description: owner of the repo to edit
  589. // type: string
  590. // required: true
  591. // - name: repo
  592. // in: path
  593. // description: name of the repo to edit
  594. // type: string
  595. // required: true
  596. // required: true
  597. // - name: body
  598. // in: body
  599. // description: "Properties of a repo that you can edit"
  600. // schema:
  601. // "$ref": "#/definitions/EditRepoOption"
  602. // responses:
  603. // "200":
  604. // "$ref": "#/responses/Repository"
  605. // "403":
  606. // "$ref": "#/responses/forbidden"
  607. // "422":
  608. // "$ref": "#/responses/validationError"
  609. if err := updateBasicProperties(ctx, opts); err != nil {
  610. return
  611. }
  612. if err := updateRepoUnits(ctx, opts); err != nil {
  613. return
  614. }
  615. if opts.Archived != nil {
  616. if err := updateRepoArchivedState(ctx, opts); err != nil {
  617. return
  618. }
  619. }
  620. ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  621. }
  622. // updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
  623. func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error {
  624. owner := ctx.Repo.Owner
  625. repo := ctx.Repo.Repository
  626. newRepoName := repo.Name
  627. if opts.Name != nil {
  628. newRepoName = *opts.Name
  629. }
  630. // Check if repository name has been changed and not just a case change
  631. if repo.LowerName != strings.ToLower(newRepoName) {
  632. if err := repo_service.ChangeRepositoryName(ctx.User, repo, newRepoName); err != nil {
  633. switch {
  634. case models.IsErrRepoAlreadyExist(err):
  635. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
  636. case models.IsErrNameReserved(err):
  637. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
  638. case models.IsErrNamePatternNotAllowed(err):
  639. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(models.ErrNamePatternNotAllowed).Pattern), err)
  640. default:
  641. ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
  642. }
  643. return err
  644. }
  645. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  646. }
  647. // Update the name in the repo object for the response
  648. repo.Name = newRepoName
  649. repo.LowerName = strings.ToLower(newRepoName)
  650. if opts.Description != nil {
  651. repo.Description = *opts.Description
  652. }
  653. if opts.Website != nil {
  654. repo.Website = *opts.Website
  655. }
  656. visibilityChanged := false
  657. if opts.Private != nil {
  658. // Visibility of forked repository is forced sync with base repository.
  659. if repo.IsFork {
  660. *opts.Private = repo.BaseRepo.IsPrivate
  661. }
  662. visibilityChanged = repo.IsPrivate != *opts.Private
  663. // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
  664. if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.User.IsAdmin {
  665. err := fmt.Errorf("cannot change private repository to public")
  666. ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
  667. return err
  668. }
  669. repo.IsPrivate = *opts.Private
  670. }
  671. if opts.Template != nil {
  672. repo.IsTemplate = *opts.Template
  673. }
  674. // Default branch only updated if changed and exist
  675. if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch) {
  676. if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
  677. if !git.IsErrUnsupportedVersion(err) {
  678. ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
  679. return err
  680. }
  681. }
  682. repo.DefaultBranch = *opts.DefaultBranch
  683. }
  684. if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
  685. ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
  686. return err
  687. }
  688. log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
  689. return nil
  690. }
  691. // updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings
  692. func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
  693. owner := ctx.Repo.Owner
  694. repo := ctx.Repo.Repository
  695. var units []models.RepoUnit
  696. var deleteUnitTypes []models.UnitType
  697. if opts.HasIssues != nil {
  698. if *opts.HasIssues && opts.ExternalTracker != nil && !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
  699. // Check that values are valid
  700. if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
  701. err := fmt.Errorf("External tracker URL not valid")
  702. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
  703. return err
  704. }
  705. if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
  706. err := fmt.Errorf("External tracker URL format not valid")
  707. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
  708. return err
  709. }
  710. units = append(units, models.RepoUnit{
  711. RepoID: repo.ID,
  712. Type: models.UnitTypeExternalTracker,
  713. Config: &models.ExternalTrackerConfig{
  714. ExternalTrackerURL: opts.ExternalTracker.ExternalTrackerURL,
  715. ExternalTrackerFormat: opts.ExternalTracker.ExternalTrackerFormat,
  716. ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
  717. },
  718. })
  719. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
  720. } else if *opts.HasIssues && opts.ExternalTracker == nil && !models.UnitTypeIssues.UnitGlobalDisabled() {
  721. // Default to built-in tracker
  722. var config *models.IssuesConfig
  723. if opts.InternalTracker != nil {
  724. config = &models.IssuesConfig{
  725. EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
  726. AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
  727. EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
  728. }
  729. } else if unit, err := repo.GetUnit(models.UnitTypeIssues); err != nil {
  730. // Unit type doesn't exist so we make a new config file with default values
  731. config = &models.IssuesConfig{
  732. EnableTimetracker: true,
  733. AllowOnlyContributorsToTrackTime: true,
  734. EnableDependencies: true,
  735. }
  736. } else {
  737. config = unit.IssuesConfig()
  738. }
  739. units = append(units, models.RepoUnit{
  740. RepoID: repo.ID,
  741. Type: models.UnitTypeIssues,
  742. Config: config,
  743. })
  744. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
  745. } else if !*opts.HasIssues {
  746. if !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
  747. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
  748. }
  749. if !models.UnitTypeIssues.UnitGlobalDisabled() {
  750. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
  751. }
  752. }
  753. }
  754. if opts.HasWiki != nil {
  755. if *opts.HasWiki && opts.ExternalWiki != nil && !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
  756. // Check that values are valid
  757. if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
  758. err := fmt.Errorf("External wiki URL not valid")
  759. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
  760. return err
  761. }
  762. units = append(units, models.RepoUnit{
  763. RepoID: repo.ID,
  764. Type: models.UnitTypeExternalWiki,
  765. Config: &models.ExternalWikiConfig{
  766. ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
  767. },
  768. })
  769. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
  770. } else if *opts.HasWiki && opts.ExternalWiki == nil && !models.UnitTypeWiki.UnitGlobalDisabled() {
  771. config := &models.UnitConfig{}
  772. units = append(units, models.RepoUnit{
  773. RepoID: repo.ID,
  774. Type: models.UnitTypeWiki,
  775. Config: config,
  776. })
  777. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
  778. } else if !*opts.HasWiki {
  779. if !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
  780. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
  781. }
  782. if !models.UnitTypeWiki.UnitGlobalDisabled() {
  783. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
  784. }
  785. }
  786. }
  787. if opts.HasPullRequests != nil {
  788. if *opts.HasPullRequests && !models.UnitTypePullRequests.UnitGlobalDisabled() {
  789. // We do allow setting individual PR settings through the API, so
  790. // we get the config settings and then set them
  791. // if those settings were provided in the opts.
  792. unit, err := repo.GetUnit(models.UnitTypePullRequests)
  793. var config *models.PullRequestsConfig
  794. if err != nil {
  795. // Unit type doesn't exist so we make a new config file with default values
  796. config = &models.PullRequestsConfig{
  797. IgnoreWhitespaceConflicts: false,
  798. AllowMerge: true,
  799. AllowRebase: true,
  800. AllowRebaseMerge: true,
  801. AllowSquash: true,
  802. }
  803. } else {
  804. config = unit.PullRequestsConfig()
  805. }
  806. if opts.IgnoreWhitespaceConflicts != nil {
  807. config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
  808. }
  809. if opts.AllowMerge != nil {
  810. config.AllowMerge = *opts.AllowMerge
  811. }
  812. if opts.AllowRebase != nil {
  813. config.AllowRebase = *opts.AllowRebase
  814. }
  815. if opts.AllowRebaseMerge != nil {
  816. config.AllowRebaseMerge = *opts.AllowRebaseMerge
  817. }
  818. if opts.AllowSquash != nil {
  819. config.AllowSquash = *opts.AllowSquash
  820. }
  821. units = append(units, models.RepoUnit{
  822. RepoID: repo.ID,
  823. Type: models.UnitTypePullRequests,
  824. Config: config,
  825. })
  826. } else if !*opts.HasPullRequests && !models.UnitTypePullRequests.UnitGlobalDisabled() {
  827. deleteUnitTypes = append(deleteUnitTypes, models.UnitTypePullRequests)
  828. }
  829. }
  830. if err := models.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil {
  831. ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
  832. return err
  833. }
  834. log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name)
  835. return nil
  836. }
  837. // updateRepoArchivedState updates repo's archive state
  838. func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error {
  839. repo := ctx.Repo.Repository
  840. // archive / un-archive
  841. if opts.Archived != nil {
  842. if repo.IsMirror {
  843. err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
  844. ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
  845. return err
  846. }
  847. if *opts.Archived {
  848. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  849. log.Error("Tried to archive a repo: %s", err)
  850. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  851. return err
  852. }
  853. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  854. } else {
  855. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  856. log.Error("Tried to un-archive a repo: %s", err)
  857. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  858. return err
  859. }
  860. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  861. }
  862. }
  863. return nil
  864. }
  865. // Delete one repository
  866. func Delete(ctx *context.APIContext) {
  867. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  868. // ---
  869. // summary: Delete a repository
  870. // produces:
  871. // - application/json
  872. // parameters:
  873. // - name: owner
  874. // in: path
  875. // description: owner of the repo to delete
  876. // type: string
  877. // required: true
  878. // - name: repo
  879. // in: path
  880. // description: name of the repo to delete
  881. // type: string
  882. // required: true
  883. // responses:
  884. // "204":
  885. // "$ref": "#/responses/empty"
  886. // "403":
  887. // "$ref": "#/responses/forbidden"
  888. owner := ctx.Repo.Owner
  889. repo := ctx.Repo.Repository
  890. canDelete, err := repo.CanUserDelete(ctx.User)
  891. if err != nil {
  892. ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
  893. return
  894. } else if !canDelete {
  895. ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
  896. return
  897. }
  898. if err := repo_service.DeleteRepository(ctx.User, repo); err != nil {
  899. ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
  900. return
  901. }
  902. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  903. ctx.Status(http.StatusNoContent)
  904. }
  905. // MirrorSync adds a mirrored repository to the sync queue
  906. func MirrorSync(ctx *context.APIContext) {
  907. // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
  908. // ---
  909. // summary: Sync a mirrored repository
  910. // produces:
  911. // - application/json
  912. // parameters:
  913. // - name: owner
  914. // in: path
  915. // description: owner of the repo to sync
  916. // type: string
  917. // required: true
  918. // - name: repo
  919. // in: path
  920. // description: name of the repo to sync
  921. // type: string
  922. // required: true
  923. // responses:
  924. // "200":
  925. // "$ref": "#/responses/empty"
  926. // "403":
  927. // "$ref": "#/responses/forbidden"
  928. repo := ctx.Repo.Repository
  929. if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  930. ctx.Error(http.StatusForbidden, "MirrorSync", "Must have write access")
  931. }
  932. mirror_service.StartToMirror(repo.ID)
  933. ctx.Status(http.StatusOK)
  934. }