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>
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>
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
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>
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>
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>
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
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 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>
5 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. }