diff --git a/cmd/hook.go b/cmd/hook.go index b741127ca3c17..eb722a415c167 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -181,6 +181,7 @@ Gitea or set your environment appropriately.`, "") // the environment is set by serv command isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) username := os.Getenv(repo_module.EnvRepoUsername) + groupID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvRepoGroupID), 10, 64) reponame := os.Getenv(repo_module.EnvRepoName) userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) @@ -254,7 +255,7 @@ Gitea or set your environment appropriately.`, "") hookOptions.OldCommitIDs = oldCommitIDs hookOptions.NewCommitIDs = newCommitIDs hookOptions.RefFullNames = refFullNames - extra := private.HookPreReceive(ctx, username, reponame, hookOptions) + extra := private.HookPreReceive(ctx, username, reponame, groupID, hookOptions) if extra.HasError() { return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) } @@ -277,7 +278,7 @@ Gitea or set your environment appropriately.`, "") fmt.Fprintf(out, " Checking %d references\n", count) - extra := private.HookPreReceive(ctx, username, reponame, hookOptions) + extra := private.HookPreReceive(ctx, username, reponame, groupID, hookOptions) if extra.HasError() { return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error) } @@ -350,6 +351,7 @@ Gitea or set your environment appropriately.`, "") pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) pusherName := os.Getenv(repo_module.EnvPusherName) + groupID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvRepoGroupID), 10, 64) hookOptions := private.HookOptions{ UserName: pusherName, @@ -399,7 +401,7 @@ Gitea or set your environment appropriately.`, "") hookOptions.OldCommitIDs = oldCommitIDs hookOptions.NewCommitIDs = newCommitIDs hookOptions.RefFullNames = refFullNames - resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) + resp, extra := private.HookPostReceive(ctx, repoUser, repoName, groupID, hookOptions) if extra.HasError() { _ = dWriter.Close() hookPrintResults(results) @@ -414,7 +416,7 @@ Gitea or set your environment appropriately.`, "") if count == 0 { if wasEmpty && masterPushed { // We need to tell the repo to reset the default branch to master - extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master") + extra := private.SetDefaultBranch(ctx, repoUser, repoName, groupID, "master") if extra.HasError() { return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error) } @@ -432,7 +434,7 @@ Gitea or set your environment appropriately.`, "") fmt.Fprintf(out, " Processing %d references\n", count) - resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) + resp, extra := private.HookPostReceive(ctx, repoUser, repoName, groupID, hookOptions) if resp == nil { _ = dWriter.Close() hookPrintResults(results) @@ -445,7 +447,7 @@ Gitea or set your environment appropriately.`, "") if wasEmpty && masterPushed { // We need to tell the repo to reset the default branch to master - extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master") + extra := private.SetDefaultBranch(ctx, repoUser, repoName, groupID, "master") if extra.HasError() { return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error) } @@ -513,6 +515,7 @@ Gitea or set your environment appropriately.`, "") repoName := os.Getenv(repo_module.EnvRepoName) pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) pusherName := os.Getenv(repo_module.EnvPusherName) + groupID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvRepoGroupID), 10, 64) // 1. Version and features negotiation. // S: PKT-LINE(version=1\0push-options atomic...) / PKT-LINE(version=1\n) @@ -626,7 +629,7 @@ Gitea or set your environment appropriately.`, "") } // 3. run hook - resp, extra := private.HookProcReceive(ctx, repoUser, repoName, hookOptions) + resp, extra := private.HookProcReceive(ctx, repoUser, repoName, groupID, hookOptions) if extra.HasError() { return fail(ctx, extra.UserMsg, "HookProcReceive failed: %v", extra.Error) } diff --git a/models/fixtures/repo_group.yml b/models/fixtures/repo_group.yml new file mode 100644 index 0000000000000..2c1f09ff34d74 --- /dev/null +++ b/models/fixtures/repo_group.yml @@ -0,0 +1,12090 @@ +- id: 1 + owner_id: 25 + owner_name: org25 + lower_name: group 1 + name: group 1 + description: | + In his anyway it recently to horror. Company alas stream to the soon host. Out tensely to as spell his contrast. Today afterwards it board shower from are. Had hence whichever few alas man would. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install TomatoInexpensive + ''' + + \#\# Usage + '''python + result = tomatoinexpensive.perform("funny request") + print("tomatoinexpensive result\:", "in progress") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 1 +- id: 2 + owner_id: 25 + owner_name: org25 + lower_name: group 2 + name: group 2 + description: | + These then painting government when each myself. One afterwards friendly upstairs inquire ourselves onto. Brilliance yet union each soon ours sometimes. Group host she you my pout under. Videotape gee under those shall these you. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/TomatoConfusing/MotionlessBlender + ''' + + \#\# Usage + '''go + result \:= MotionlessBlender.execute("playful alert") + fmt.Println("motionlessblender result\:", "completed") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 2 +- id: 3 + owner_id: 25 + owner_name: org25 + lower_name: group 3 + name: group 3 + description: | + Now while them elsewhere congregation accordingly it. Energy around gun promise fact spin utterly. Yours each occur week monthly quarterly anything. He elated theirs American them army brace. How indeed daily some of sharply nobody. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install LegumeEnergetic264 + ''' + + \#\# Usage + '''javascript + const result = legumeenergetic264.process("funny request"); + console.log("legumeenergetic264 result\:", "success"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 1 + sort_order: 1 +- id: 4 + owner_id: 25 + owner_name: org25 + lower_name: group 4 + name: group 4 + description: | + First aha that these finally summation understanding. Their well quarterly posse rainbow dizzying upset. Where substantial victoriously wearily whose eek for. Either about anything Barbadian across about weekly. Several theirs how first monthly later due. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install MysteriousKangaroo99 + ''' + + \#\# Usage + '''javascript + const result = mysteriouskangaroo99.execute("playful alert"); + console.log("mysteriouskangaroo99 result\:", "terminated"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 3 +- id: 5 + owner_id: 25 + owner_name: org25 + lower_name: group 5 + name: group 5 + description: | + Ours either today lot generally tablet finally. Consequently I their little it sari some. Daily boldly yikes Indonesian ourselves the foot. Here could of same page mine include. Everything Chinese catalog through of mine because. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GorgeousRestaurant + ''' + + \#\# Usage + '''python + result = gorgeousrestaurant.handle("quirky message") + print("gorgeousrestaurant result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 1 + sort_order: 2 +- id: 6 + owner_id: 25 + owner_name: org25 + lower_name: group 6 + name: group 6 + description: | + His them Einsteinian this why give himself. To its ourselves nobody safely ouch suddenly. Tonight being bunch link us herself bevy. Had his gossip purely work most still. Later whatever galaxy yourself play ours day. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PagodaShakeer + ''' + + \#\# Usage + '''javascript + const result = pagodashakeer.run("funny request"); + console.log("pagodashakeer result\:", "completed"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 3 + sort_order: 1 +- id: 7 + owner_id: 25 + owner_name: org25 + lower_name: group 7 + name: group 7 + description: | + Head are instance daringly mango want of. That now inside tomorrow clump his eek. Annually well yikes what his Turkmen convert. I who must calm how exaltation before. Ourselves now instance man towards give where. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install YellowPainter524 + ''' + + \#\# Usage + '''javascript + const result = yellowpainter524.execute("funny request"); + console.log("yellowpainter524 result\:", "terminated"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 5 + sort_order: 1 +- id: 8 + owner_id: 25 + owner_name: org25 + lower_name: group 8 + name: group 8 + description: | + Then several out neither he cast yay. Yourselves where Diabolical your nobody brass nest. I how your from because what as. Meanwhile anger dazzle nightly range Shakespearean doctor. As including everybody been as near wrap. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ImportantGorilla7 + ''' + + \#\# Usage + '''javascript + const result = importantgorilla7.run("quirky message"); + console.log("importantgorilla7 result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 1 + sort_order: 3 +- id: 9 + owner_id: 25 + owner_name: org25 + lower_name: group 9 + name: group 9 + description: | + Here nearly did within sometimes inside patrol. Huh that hers mine key videotape her. He softly her muddy yearly london day. Secondly Pacific alas the here last Taiwanese. Its soon when yesterday band at metal. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install AdventurousChest1 + ''' + + \#\# Usage + '''python + result = adventurouschest1.execute("funny request") + print("adventurouschest1 result\:", "completed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 4 +- id: 10 + owner_id: 25 + owner_name: org25 + lower_name: group 10 + name: group 10 + description: | + Had first that that gee gee additionally. Within respond tonight my her ourselves today. Constantly how one German that clap dizzying. Through appear onto warmth this there not. Each everybody these up firstly unless has. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install LemonModern24 + ''' + + \#\# Usage + '''javascript + const result = lemonmodern24.execute("funny request"); + console.log("lemonmodern24 result\:", "unknown"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 5 + sort_order: 2 +- id: 11 + owner_id: 25 + owner_name: org25 + lower_name: group 11 + name: group 11 + description: | + Batch firstly too will these depending them. Of onion father sometimes cackle sternly forest. Shall electricity himself as rarely way climb. Him up very game firstly adventurous huh. Finnish whereas growth before yesterday off behind. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/TaxiReader/OutrageousFarm264 + ''' + + \#\# Usage + '''go + result \:= OutrageousFarm264.execute("whimsical story") + fmt.Println("outrageousfarm264 result\:", "in progress") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 5 + sort_order: 3 +- id: 12 + owner_id: 25 + owner_name: org25 + lower_name: group 12 + name: group 12 + description: | + Of certain since my indoors how stand. Tonight yet by government goodness normally host. Pretty anthology of from some kiss yearly. Number him yikes myself for still shiny. Above shall pack its way some constantly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/DonkeyOpener836/CleverCrow + ''' + + \#\# Usage + '''go + result \:= CleverCrow.run("lighthearted command") + fmt.Println("clevercrow result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 11 + sort_order: 1 +- id: 13 + owner_id: 25 + owner_name: org25 + lower_name: group 13 + name: group 13 + description: | + Group at mine for whose why everybody. Along whose of which I bush rarely. Regularly mob certain wad everybody which to. How jump in deceit belong bread employment. These Indian electricity does that how all. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/GauvaRideer/DistinctCastle + ''' + + \#\# Usage + '''go + result \:= DistinctCastle.run("quirky message") + fmt.Println("distinctcastle result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 4 + sort_order: 1 +- id: 14 + owner_id: 25 + owner_name: org25 + lower_name: group 14 + name: group 14 + description: | + Each meanwhile hand joy love whoever weekly. Which yesterday of lastly furnish being me. Plant earlier few my finally had before. Unless monthly your gee begin by group. Fine in company French frequently give within. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install NeckShakeer0 + ''' + + \#\# Usage + '''python + result = neckshakeer0.run("playful alert") + print("neckshakeer0 result\:", "error") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 11 + sort_order: 2 +- id: 15 + owner_id: 25 + owner_name: org25 + lower_name: group 15 + name: group 15 + description: | + Who yours fight finally his dream back. I regularly follow annually that in bravo. Tibetan problem account regularly lag today scold. An wheat neither sing him anything hey. Had your each first nightly auspicious where. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install MelonImportant + ''' + + \#\# Usage + '''javascript + const result = melonimportant.handle("funny request"); + console.log("melonimportant result\:", "error"); + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 8 + sort_order: 1 +- id: 16 + owner_id: 25 + owner_name: org25 + lower_name: group 16 + name: group 16 + description: | + Near these almost she these without without. For listen of noise with conclude finally. Recklessly itself must highly we kill besides. Who mouse her realistic giraffe Gaussian racism. Pencil data some him hail then stand. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LycheeConfusing + ''' + + \#\# Usage + '''python + result = lycheeconfusing.handle("whimsical story") + print("lycheeconfusing result\:", "terminated") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 15 + sort_order: 1 +- id: 17 + owner_id: 25 + owner_name: org25 + lower_name: group 17 + name: group 17 + description: | + Under stand than designer hail their tough. Wisp being as yourselves labour he all. Salt ourselves government that off whose through. Constantly cautiously owing her then these hey. What in his including box those what. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install RedcurrantDull009 + ''' + + \#\# Usage + '''python + result = redcurrantdull009.handle("playful alert") + print("redcurrantdull009 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 12 + sort_order: 1 +- id: 18 + owner_id: 25 + owner_name: org25 + lower_name: group 18 + name: group 18 + description: | + Both these sternly how finally end by. Anyway from below next of filthy beautiful. How in annually eek to gently myself. Off but everything Thatcherite hedge notebook our. Nothing from than everything recently everybody problem. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install DisgustingMinnow + ''' + + \#\# Usage + '''javascript + const result = disgustingminnow.execute("whimsical story"); + console.log("disgustingminnow result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 2 + sort_order: 1 +- id: 19 + owner_id: 25 + owner_name: org25 + lower_name: group 19 + name: group 19 + description: | + Shall our American across fortnightly ourselves our. Brass in tomorrow itchy straightaway justice every. Summation then that someone that Chinese business. Someone has sink tonight packet inadequately than. That unless school how company busily other. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install DresserKisser + ''' + + \#\# Usage + '''python + result = dresserkisser.handle("quirky message") + print("dresserkisser result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 2 + sort_order: 2 +- id: 20 + owner_id: 25 + owner_name: org25 + lower_name: group 20 + name: group 20 + description: | + Result mob Jungian above nearly bunch there. His light answer last others vacate at. Sit those which tomorrow yearly here annually. Oops sand yearly drink are grammar secondly. Themselves lovely rather involve tomorrow tomorrow what. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install WideShirt + ''' + + \#\# Usage + '''python + result = wideshirt.run("playful alert") + print("wideshirt result\:", "in progress") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 3 + sort_order: 2 +- id: 21 + owner_id: 25 + owner_name: org25 + lower_name: group 21 + name: group 21 + description: | + Often coat me fine huh covey completely. Ouch little whose as heart from theirs. His behind may sometimes could everything occasionally. Will us all whose along those munch. Few wow certain where where weight tonight. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/NectarineNice595/DelightfulWildebeest + ''' + + \#\# Usage + '''go + result \:= DelightfulWildebeest.perform("lighthearted command") + fmt.Println("delightfulwildebeest result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 14 + sort_order: 1 +- id: 22 + owner_id: 25 + owner_name: org25 + lower_name: group 22 + name: group 22 + description: | + Understimate her everything he modern nest without. At problem yearly loss my all determination. He there tonight us herself he life. His Peruvian thoroughly each next as what. Myself quarterly entirely which his my from. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/SwimmingPoolEater27/CondemnedDinosaur + ''' + + \#\# Usage + '''go + result \:= CondemnedDinosaur.run("quirky message") + fmt.Println("condemneddinosaur result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 8 + sort_order: 2 +- id: 23 + owner_id: 25 + owner_name: org25 + lower_name: group 23 + name: group 23 + description: | + Shy I enough myself whose Pacific club. Company equally that you aloof fact generally. Next that lake why which liter other. Somebody buckles themselves in of many firstly. Murder off by absolutely wash town nobody. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install KangarooStacker + ''' + + \#\# Usage + '''python + result = kangaroostacker.process("playful alert") + print("kangaroostacker result\:", "unknown") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 9 + sort_order: 1 +- id: 24 + owner_id: 25 + owner_name: org25 + lower_name: group 24 + name: group 24 + description: | + Shall outside it of than yours these. So be next Mozartian a heavily brace. Yourself there paint hers tonight we pollution. Onto recline would red your hers anywhere. For near same anyone never appear fish. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GrumpyPrairieDog5 + ''' + + \#\# Usage + '''python + result = grumpyprairiedog5.execute("funny request") + print("grumpyprairiedog5 result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 22 + sort_order: 1 +- id: 25 + owner_id: 25 + owner_name: org25 + lower_name: group 25 + name: group 25 + description: | + Including frock where consist Senegalese virtually murder. Bother to its army till some by. Whose Shakespearean did might in her research. That mall murder normally stand Antarctic regularly. Door whoever instead each above secondly had. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CaneCrawler/ToughGrapes13 + ''' + + \#\# Usage + '''go + result \:= ToughGrapes13.handle("quirky message") + fmt.Println("toughgrapes13 result\:", "error") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 7 + sort_order: 1 +- id: 26 + owner_id: 25 + owner_name: org25 + lower_name: group 26 + name: group 26 + description: | + South nobody silence they from cloud transform. These these myself Einsteinian everyone someone therefore. Tribe beauty there sleep who what as. Congregation pack this out enlist monthly our. No lastly grip could hang our I. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PhysalisTense/PhysalisBusy383 + ''' + + \#\# Usage + '''go + result \:= PhysalisBusy383.perform("playful alert") + fmt.Println("physalisbusy383 result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 13 + sort_order: 1 +- id: 27 + owner_id: 25 + owner_name: org25 + lower_name: group 27 + name: group 27 + description: | + No little backwards just before your be. Bra off her should monthly wisdom why. African hmm gain who words itself oops. Fact obesity elsewhere flock these those he. Adorable hmm today insufficient horse generally behind. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PipeListener2 + ''' + + \#\# Usage + '''javascript + const result = pipelistener2.execute("lighthearted command"); + console.log("pipelistener2 result\:", "failed"); + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 2 + sort_order: 3 +- id: 28 + owner_id: 25 + owner_name: org25 + lower_name: group 28 + name: group 28 + description: | + Afterwards any some off meanwhile rapidly enough. By Greek now street sleepy up remove. Carry failure bread fairly troop his answer. A always life clap had why card. Sandals as hourly already deeply aha me. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PerfectCane + ''' + + \#\# Usage + '''python + result = perfectcane.execute("playful alert") + print("perfectcane result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 22 + sort_order: 2 +- id: 29 + owner_id: 25 + owner_name: org25 + lower_name: group 29 + name: group 29 + description: | + Wrack me off today class whose as. Of American theirs those since insert library. Anybody may from lastly quarterly that throughout. For unemployment are whose mob upstairs fortunately. What whom her tomorrow first few ours. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install EmbarrassedSheep + ''' + + \#\# Usage + '''python + result = embarrassedsheep.handle("lighthearted command") + print("embarrassedsheep result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 14 + sort_order: 2 +- id: 30 + owner_id: 25 + owner_name: org25 + lower_name: group 30 + name: group 30 + description: | + Cigarette part line first is few nightly. Where first none example him sock next. Confucian without those Kyrgyz seldom his that. They few us later moreover quarterly blushing. That Kyrgyz couch have am their spit. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install WatermelonImpossible + ''' + + \#\# Usage + '''python + result = watermelonimpossible.run("quirky message") + print("watermelonimpossible result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 1 + sort_order: 4 +- id: 31 + owner_id: 26 + owner_name: org26 + lower_name: group 1 + name: group 1 + description: | + You whom bale where caravan veterinarian that. Weather then that for being outside disgusting. Mine she what party onto untie why. Another tomorrow what she previously him themselves. Monthly yet occasionally some him her daily. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install JoyousMonkey + ''' + + \#\# Usage + '''python + result = joyousmonkey.perform("playful alert") + print("joyousmonkey result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 5 +- id: 32 + owner_id: 26 + owner_name: org26 + lower_name: group 2 + name: group 2 + description: | + Drab which in occasionally apple congregation themselves. You an host from man he shall. To yourselves occasionally since monthly that power. We before late we your have obediently. His thing finally frequently joy dress end. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install KnightlyWoodchuck + ''' + + \#\# Usage + '''python + result = knightlywoodchuck.process("quirky message") + print("knightlywoodchuck result\:", "error") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 31 + sort_order: 1 +- id: 33 + owner_id: 26 + owner_name: org26 + lower_name: group 3 + name: group 3 + description: | + I after several when due remain in. Think any their these this with set. Then frequently sensibly hers hastily woman this. Elsewhere shower theirs above turkey safety horde. That with luxury this Kazakh that it. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/RedcurrantPowerless86/CurrantItchy + ''' + + \#\# Usage + '''go + result \:= CurrantItchy.handle("whimsical story") + fmt.Println("currantitchy result\:", "failed") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 31 + sort_order: 2 +- id: 34 + owner_id: 26 + owner_name: org26 + lower_name: group 4 + name: group 4 + description: | + Hmm key newspaper them rather for their. Cough formerly cut abundant huge back eek. Ourselves whom that your brace every monthly. Handle ours mine previously whenever few previously. Mustering into to I with off decidedly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install HungryToad + ''' + + \#\# Usage + '''javascript + const result = hungrytoad.perform("playful alert"); + console.log("hungrytoad result\:", "success"); + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 31 + sort_order: 3 +- id: 35 + owner_id: 26 + owner_name: org26 + lower_name: group 5 + name: group 5 + description: | + Whenever whose neither anxious generally this neither. Shall of somebody it party worrisome stack. Whichever these furthermore gladly group warmth might. Caravan chair those Barbadian theirs Nepalese knock. Tribe packet these he however those instance. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install GrapeEvil3 + ''' + + \#\# Usage + '''javascript + const result = grapeevil3.handle("funny request"); + console.log("grapeevil3 result\:", "in progress"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 6 +- id: 36 + owner_id: 26 + owner_name: org26 + lower_name: group 6 + name: group 6 + description: | + Inadequately electricity nervously monthly lucky these pair. Cravat these that hourly fortunately later these. For but Darwinian smile must patrol i.e.. Book bottle kuban how day the these. Nightly host phew judge he neither e.g.. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/DullImpala/KidLaugher + ''' + + \#\# Usage + '''go + result \:= KidLaugher.handle("funny request") + fmt.Println("kidlaugher result\:", "terminated") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 35 + sort_order: 1 +- id: 37 + owner_id: 26 + owner_name: org26 + lower_name: group 7 + name: group 7 + description: | + Nevertheless include somebody cooker that now petrify. Can desk down chest monthly which itself. Build virtually that inside everything recline ours. Archipelago regiment Monacan firstly weekly American troubling. They Colombian out what gee whomever neither. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GauvaClimber3 + ''' + + \#\# Usage + '''python + result = gauvaclimber3.handle("playful alert") + print("gauvaclimber3 result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 31 + sort_order: 4 +- id: 38 + owner_id: 26 + owner_name: org26 + lower_name: group 8 + name: group 8 + description: | + Fuel over in part an here he. All a Japanese terribly host why in. Formerly in were tribe it that his. May pouch next whisker whose elegance down. Might his since pronunciation stand really party. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ImportantDonkey + ''' + + \#\# Usage + '''javascript + const result = importantdonkey.perform("funny request"); + console.log("importantdonkey result\:", "failed"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 32 + sort_order: 1 +- id: 39 + owner_id: 26 + owner_name: org26 + lower_name: group 9 + name: group 9 + description: | + Barbadian it I monthly that down chair. These on at mine include who practically. Toothpaste whenever theirs mine wings that it. Today its hers though Sri-Lankan lastly important. Thing my cloud horde finally outcome can. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install JitteryModel + ''' + + \#\# Usage + '''javascript + const result = jitterymodel.execute("lighthearted command"); + console.log("jitterymodel result\:", "failed"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 35 + sort_order: 2 +- id: 40 + owner_id: 26 + owner_name: org26 + lower_name: group 10 + name: group 10 + description: | + Who joy an many generally rhythm time. Nobody alone whomever could where wash congregation. Joyously previously which nest where in woman. Week Congolese will has as full must. Additionally into us therefore whom tender also. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install DefiantNoise + ''' + + \#\# Usage + '''javascript + const result = defiantnoise.handle("funny request"); + console.log("defiantnoise result\:", "failed"); + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 7 +- id: 41 + owner_id: 26 + owner_name: org26 + lower_name: group 11 + name: group 11 + description: | + To seldom here look do you its. Education constantly backwards stack she these some. Which might besides tomorrow behind open indoors. Wow it alas phew careful is tonight. Gun information now did will garlic late. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/TerseTiger/PhysalisQuaint + ''' + + \#\# Usage + '''go + result \:= PhysalisQuaint.perform("playful alert") + fmt.Println("physalisquaint result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 34 + sort_order: 1 +- id: 42 + owner_id: 26 + owner_name: org26 + lower_name: group 12 + name: group 12 + description: | + Congregation it cook which accordingly wisp here. Nest painting staff none each weather it. Highly must bale do eye any hand. Might belong team stand including differs covey. Onion enough certain just nightly book very. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FaithfulCave + ''' + + \#\# Usage + '''python + result = faithfulcave.run("whimsical story") + print("faithfulcave result\:", "in progress") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 38 + sort_order: 1 +- id: 43 + owner_id: 26 + owner_name: org26 + lower_name: group 13 + name: group 13 + description: | + Sprint now now some some Cypriot instance. Far sorrow flock everyone meanwhile group we. Welsh peace frightening these relaxation recently most. I.e. one in either from them our. Him heap each life where about shower. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install RepulsivePotato + ''' + + \#\# Usage + '''javascript + const result = repulsivepotato.process("lighthearted command"); + console.log("repulsivepotato result\:", "terminated"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 31 + sort_order: 5 +- id: 44 + owner_id: 26 + owner_name: org26 + lower_name: group 14 + name: group 14 + description: | + Catch muddy above does yesterday many I. All her unless then other bunch shall. Is brace yearly seldom elsewhere throughout at. Monthly flock this as fortnightly just anything. Other ours scold quietly these for regularly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install EasyWildebeest + ''' + + \#\# Usage + '''javascript + const result = easywildebeest.run("funny request"); + console.log("easywildebeest result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 8 +- id: 45 + owner_id: 26 + owner_name: org26 + lower_name: group 15 + name: group 15 + description: | + Acknowledge away me there soon why for. Hmm as yesterday unless her they they. Corner car line smell toy where should. Differs so his gain ours colorful did. Painfully constantly for ouch few thing over. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/OutrageousDinosaur/BookstoreFighter + ''' + + \#\# Usage + '''go + result \:= BookstoreFighter.perform("playful alert") + fmt.Println("bookstorefighter result\:", "terminated") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 37 + sort_order: 1 +- id: 46 + owner_id: 26 + owner_name: org26 + lower_name: group 16 + name: group 16 + description: | + Am on out way into juice double. Foolishly Confucian time still next each outfit. Neither you most cut tickle then tightly. Him here have its you wow dig. Where several bless highly juicer whom his. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/MelonMuddy/AuntDiveer + ''' + + \#\# Usage + '''go + result \:= AuntDiveer.process("lighthearted command") + fmt.Println("auntdiveer result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 44 + sort_order: 1 +- id: 47 + owner_id: 26 + owner_name: org26 + lower_name: group 17 + name: group 17 + description: | + Whose boxers reel inside significant this thing. Away in finally finally yet my nearby. Hedge sandwich today our yours hurt edge. Far hourly theirs lastly therefore eat currency. Somebody work glamorous monthly accordingly him certain. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/RambutanDisgusting05/KiwiQueer + ''' + + \#\# Usage + '''go + result \:= KiwiQueer.handle("lighthearted command") + fmt.Println("kiwiqueer result\:", "success") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 36 + sort_order: 1 +- id: 48 + owner_id: 26 + owner_name: org26 + lower_name: group 18 + name: group 18 + description: | + Numerous could wisp when of you murder. Last normally crawl rudely seafood head lastly. The my slavery Pacific busily you his. Sometimes below lately poverty these deskpath on. Shoulder silently you live many great most. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PitayaHungry/DisgustingMango36 + ''' + + \#\# Usage + '''go + result \:= DisgustingMango36.perform("funny request") + fmt.Println("disgustingmango36 result\:", "unknown") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 37 + sort_order: 2 +- id: 49 + owner_id: 26 + owner_name: org26 + lower_name: group 19 + name: group 19 + description: | + Whose outcome for monthly widen of first. Previously yet we this in moreover on. Whom just fact tonight hourly up half. Joy Californian should bravely solemnly murder some. Rain your regularly in it read child. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install HilariousBrother + ''' + + \#\# Usage + '''javascript + const result = hilariousbrother.process("funny request"); + console.log("hilariousbrother result\:", "success"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 44 + sort_order: 2 +- id: 50 + owner_id: 26 + owner_name: org26 + lower_name: group 20 + name: group 20 + description: | + Hence cough flock troupe group nap ouch. Off tomorrow hourly sufficient which string any. Quiver auspicious mob inquisitively block tea why. Throughout leave that sometimes hers which drag. Tonight within anyway fade this bale those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/VastJuicer16/AnxiousWombat2 + ''' + + \#\# Usage + '''go + result \:= AnxiousWombat2.handle("funny request") + fmt.Println("anxiouswombat2 result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 31 + sort_order: 6 +- id: 51 + owner_id: 26 + owner_name: org26 + lower_name: group 21 + name: group 21 + description: | + Quarterly upon party pipe early ahead hers. Each e.g. sleep begin late those about. Mushy out today couple her then earlier. Me which this whoever these most fight. We that though weekly many Mozartian those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PlantThinker59 + ''' + + \#\# Usage + '''python + result = plantthinker59.handle("whimsical story") + print("plantthinker59 result\:", "unknown") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 38 + sort_order: 2 +- id: 52 + owner_id: 26 + owner_name: org26 + lower_name: group 22 + name: group 22 + description: | + Example mustering late now i.e. that with. These determination joyously cap weight when yourselves. One powerless that viplate always brace spotted. Tomorrow Putinist Peruvian work yet bill that. Hand which it they it fortnightly these. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install StupidChicken + ''' + + \#\# Usage + '''python + result = stupidchicken.execute("playful alert") + print("stupidchicken result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 48 + sort_order: 1 +- id: 53 + owner_id: 26 + owner_name: org26 + lower_name: group 23 + name: group 23 + description: | + Woman others board recognise today where me. Pod by juice car any pack would. Lastly whichever where his someone medicine consequently. Give between interest his will laugh ream. Last seldom album was to that also. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install EnchantedRabbit + ''' + + \#\# Usage + '''python + result = enchantedrabbit.process("quirky message") + print("enchantedrabbit result\:", "success") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 41 + sort_order: 1 +- id: 54 + owner_id: 26 + owner_name: org26 + lower_name: group 24 + name: group 24 + description: | + Magic e.g. almost frequently itself always who. Empty I stormy was these somebody heavily. Yesterday until these elephant Confucian though which. Whose here aha yay tired grip next. Everybody since pack covey us that which. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/RambutanCrowded/ShortsThrower12 + ''' + + \#\# Usage + '''go + result \:= ShortsThrower12.handle("playful alert") + fmt.Println("shortsthrower12 result\:", "error") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 42 + sort_order: 1 +- id: 55 + owner_id: 26 + owner_name: org26 + lower_name: group 25 + name: group 25 + description: | + Ride when any then begin thought where. Itself i.e. accordingly to example us yourselves. Us our whoever what me though flour. Team our rather rather in can write. Frail themselves cry Iraqi mine shoes lot. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ClumsyGnu055 + ''' + + \#\# Usage + '''python + result = clumsygnu055.process("quirky message") + print("clumsygnu055 result\:", "failed") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 53 + sort_order: 1 +- id: 56 + owner_id: 26 + owner_name: org26 + lower_name: group 26 + name: group 26 + description: | + Which ours Lebanese who set each consequently. Group flock huge beneath care hers to. Other party him madly together everything climb. Ream several pack nightly conclude to panda. Spoon his outside without little anyone their. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ArchitectSnoreer/CheerfulWombat276 + ''' + + \#\# Usage + '''go + result \:= CheerfulWombat276.perform("playful alert") + fmt.Println("cheerfulwombat276 result\:", "terminated") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 43 + sort_order: 1 +- id: 57 + owner_id: 26 + owner_name: org26 + lower_name: group 27 + name: group 27 + description: | + Anyone may annoyance away library whose Somali. That man grieving none which necklace that. Recline it now daughter nose luxuty to. Anxiously then what team tomorrow out wisp. Which in whose its pod eventually impossible. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FeijoaEasy + ''' + + \#\# Usage + '''python + result = feijoaeasy.process("funny request") + print("feijoaeasy result\:", "error") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 36 + sort_order: 2 +- id: 58 + owner_id: 26 + owner_name: org26 + lower_name: group 28 + name: group 28 + description: | + Conditioner her were as anxiously opposite e.g.. Pen eek nevertheless Dutch gather will itself. Soon anywhere arrow she this purely ever. Animal there your might patrol she less. Annually at think judge whose yourself their. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PencilClimber/RockMelonIll + ''' + + \#\# Usage + '''go + result \:= RockMelonIll.run("whimsical story") + fmt.Println("rockmelonill result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 54 + sort_order: 1 +- id: 59 + owner_id: 26 + owner_name: org26 + lower_name: group 29 + name: group 29 + description: | + Who yesterday what why repel building cheerfully. Today had you in to us yourselves. Yourselves she gang whoever e.g. nothing learn. Any where accordingly never even usually bunch. Hmm might childhood this regularly imitate those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install VillaWasher75 + ''' + + \#\# Usage + '''python + result = villawasher75.process("whimsical story") + print("villawasher75 result\:", "success") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 33 + sort_order: 1 +- id: 60 + owner_id: 26 + owner_name: org26 + lower_name: group 30 + name: group 30 + description: | + Talk outcome those badly next enough lastly. Towards sunshine to that whose abundant lately. Somebody he on pronunciation must yourself explode. We these whichever though regiment murder inside. Gee moreover whom thing patience there so. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install DefiantLeg + ''' + + \#\# Usage + '''python + result = defiantleg.perform("whimsical story") + print("defiantleg result\:", "failed") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 31 + sort_order: 7 +- id: 61 + owner_id: 41 + owner_name: org41 + lower_name: group 1 + name: group 1 + description: | + What avoid range range ourselves by enormously. About up should differs every number ankle. Several nest what what besides including jump. Tomorrow lastly then how monthly who east. Off another year upon scold those the. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install TelevisionCooker + ''' + + \#\# Usage + '''python + result = televisioncooker.execute("funny request") + print("televisioncooker result\:", "terminated") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 9 +- id: 62 + owner_id: 41 + owner_name: org41 + lower_name: group 2 + name: group 2 + description: | + On I did do there how in. Differs this heap there Einsteinian far within. Half off open instance then stealthily here. What they straight me instance where no. Trip upstairs purely handsome catalog moreover link. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ApricotObnoxious812/CheerfulVeterinarian35 + ''' + + \#\# Usage + '''go + result \:= CheerfulVeterinarian35.perform("playful alert") + fmt.Println("cheerfulveterinarian35 result\:", "failed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 61 + sort_order: 1 +- id: 63 + owner_id: 41 + owner_name: org41 + lower_name: group 3 + name: group 3 + description: | + Will scarcely provided finally his ever is. German child are school i.e. am from. Here week how day never day smell. On something been wit varied themselves outside. Outside then virtually few crib indeed full. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/MusicCuter07/StormyTomato + ''' + + \#\# Usage + '''go + result \:= StormyTomato.run("whimsical story") + fmt.Println("stormytomato result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 10 +- id: 64 + owner_id: 41 + owner_name: org41 + lower_name: group 4 + name: group 4 + description: | + Contrast harvest of his nightly vacate climb. English talk you behind leave that firstly. Result in us above is tomorrow hug. Why videotape cackle near through quarterly daughter. Company yesterday you sharply sometimes in which. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SoreCane1 + ''' + + \#\# Usage + '''python + result = sorecane1.process("quirky message") + print("sorecane1 result\:", "success") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 11 +- id: 65 + owner_id: 41 + owner_name: org41 + lower_name: group 5 + name: group 5 + description: | + Walk fact sleep shall quite pollution besides. Week whose either kindness earlier yet few. Lately how stress may just up stand. Troop airport there that herself cloud himself. Team of Atlantic tomorrow Dutch everyone conclude. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install HoneydewArrogant + ''' + + \#\# Usage + '''python + result = honeydewarrogant.handle("lighthearted command") + print("honeydewarrogant result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 64 + sort_order: 1 +- id: 66 + owner_id: 41 + owner_name: org41 + lower_name: group 6 + name: group 6 + description: | + I.e. i.e. battle comb here other most. We on faithfully anything him innocently hers. Fatally itself how body those were occasionally. Tie who hers person gun that fiction. Those whose to yay rarely that orange. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/DateHappy413/ToyDiger6 + ''' + + \#\# Usage + '''go + result \:= ToyDiger6.perform("lighthearted command") + fmt.Println("toydiger6 result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 63 + sort_order: 1 +- id: 67 + owner_id: 41 + owner_name: org41 + lower_name: group 7 + name: group 7 + description: | + When powerless Senegalese how hundreds sleep whom. Why we since does finally week hence. Fact how me theirs hourly to freedom. His single murder that Finnish estate ourselves. Therefore occasionally whichever hmm they about horror. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ElatedNewspaper + ''' + + \#\# Usage + '''javascript + const result = elatednewspaper.execute("funny request"); + console.log("elatednewspaper result\:", "in progress"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 66 + sort_order: 1 +- id: 68 + owner_id: 41 + owner_name: org41 + lower_name: group 8 + name: group 8 + description: | + One those this our will substantial upon. Agree our bird finally obediently there violently. Mine drink example it since hey what. Nobody yet father any conclude eek daily. Group of mourn had additionally then conclude. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FrighteningServal874 + ''' + + \#\# Usage + '''python + result = frighteningserval874.process("whimsical story") + print("frighteningserval874 result\:", "failed") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 63 + sort_order: 2 +- id: 69 + owner_id: 41 + owner_name: org41 + lower_name: group 9 + name: group 9 + description: | + Troupe tomorrow regularly why without videotape case. Our gold truthfully that infrequently bow look. Other thing circumstances where example mustering watch. Whom world how down finally case their. Group murder reassure sprint we this earlier. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/WatermelonDelightful/GlassesCryer983 + ''' + + \#\# Usage + '''go + result \:= GlassesCryer983.handle("lighthearted command") + fmt.Println("glassescryer983 result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 62 + sort_order: 1 +- id: 70 + owner_id: 41 + owner_name: org41 + lower_name: group 10 + name: group 10 + description: | + Research publicity climb that eek about muster. Air everyone is yourselves tonight monthly fact. Somebody holiday few that Afghan later his. Read chicken flock dynasty life before opposite. Ring what Philippine then mine many brother. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install BlackcurrantRed83 + ''' + + \#\# Usage + '''python + result = blackcurrantred83.handle("whimsical story") + print("blackcurrantred83 result\:", "completed") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 61 + sort_order: 2 +- id: 71 + owner_id: 41 + owner_name: org41 + lower_name: group 11 + name: group 11 + description: | + Mysteriously anybody up weekly them album pray. Laugh that red to transform whirl a. Nothing whoa poised pack in what because. Greatly whose hail formerly trend today open. Pasta week its them eye some these. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/HilariousGorilla/ClementineMysterious + ''' + + \#\# Usage + '''go + result \:= ClementineMysterious.perform("funny request") + fmt.Println("clementinemysterious result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 64 + sort_order: 2 +- id: 72 + owner_id: 41 + owner_name: org41 + lower_name: group 12 + name: group 12 + description: | + Would up theirs how fine me when. Gallop who those anxiously whatever ski conclude. Troupe fall you vanish vanish number moreover. Over some cost are am yikes another. Wildly you select therefore host yours cast. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FineSheep679 + ''' + + \#\# Usage + '''python + result = finesheep679.execute("funny request") + print("finesheep679 result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 68 + sort_order: 1 +- id: 73 + owner_id: 41 + owner_name: org41 + lower_name: group 13 + name: group 13 + description: | + Monthly greatly next inexpensive whomever what I. Within company whose his what yet deceive. All whichever at hourly there my your. Weekly her one us anyone deliberately luxury. Huge on all line outfit conclude as. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/HostOpener/LemonyGasStation + ''' + + \#\# Usage + '''go + result \:= LemonyGasStation.handle("lighthearted command") + fmt.Println("lemonygasstation result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 67 + sort_order: 1 +- id: 74 + owner_id: 41 + owner_name: org41 + lower_name: group 14 + name: group 14 + description: | + Yours you fine late me viplate eat. Since these then no delightful today lately. Economics her himself Belgian I then themselves. Way execute must roughly aha anybody most. Flock gracefully all sometimes throughout bookstore hence. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install OnionCuter + ''' + + \#\# Usage + '''javascript + const result = onioncuter.perform("lighthearted command"); + console.log("onioncuter result\:", "failed"); + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 70 + sort_order: 1 +- id: 75 + owner_id: 41 + owner_name: org41 + lower_name: group 15 + name: group 15 + description: | + Several day woman being limp fleet this. Are intensely honour Turkish him happiness of. Quarterly someone that which as recently alone. Myself today besides few hers marriage insufficient. How near us sedge a since speedily. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/SkirtDreamer/OrangeSweater + ''' + + \#\# Usage + '''go + result \:= OrangeSweater.perform("playful alert") + fmt.Println("orangesweater result\:", "finished") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 73 + sort_order: 1 +- id: 76 + owner_id: 41 + owner_name: org41 + lower_name: group 16 + name: group 16 + description: | + Promise no grieving reel its yay besides. Lately slide that of in mob several. It with yet ball bill so what. This anyway whom week anybody hmm firstly. Upstairs how constantly whoever will happiness pleasure. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ElephantClimber0/PhysalisWitty + ''' + + \#\# Usage + '''go + result \:= PhysalisWitty.process("lighthearted command") + fmt.Println("physaliswitty result\:", "failed") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 63 + sort_order: 3 +- id: 77 + owner_id: 41 + owner_name: org41 + lower_name: group 17 + name: group 17 + description: | + Yet for whose are Christian yikes as. The swing from in without firstly i.e.. Stay fortnightly Christian of yourselves murder one. Patrol regularly Iranian wisp whose just fortnightly. Straightaway frankly being wad her what vanish. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GrievingTiger + ''' + + \#\# Usage + '''python + result = grievingtiger.perform("whimsical story") + print("grievingtiger result\:", "success") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 62 + sort_order: 2 +- id: 78 + owner_id: 41 + owner_name: org41 + lower_name: group 18 + name: group 18 + description: | + My joy extremely spelling had yours other. Little boat they occasionally these whom string. Shampoo glorious after innocently one none thing. Yours those think vanish an he my. Outside thing paint fact daily that cry. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SmilingSalt985 + ''' + + \#\# Usage + '''python + result = smilingsalt985.process("quirky message") + print("smilingsalt985 result\:", "completed") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 70 + sort_order: 2 +- id: 79 + owner_id: 41 + owner_name: org41 + lower_name: group 19 + name: group 19 + description: | + Foolishly leap cheerful without most by orchard. Kindness their my themselves tonight myself in. Accordingly you be sometimes backwards thankful whichever. Cast boy recently impress i.e. say outside. Annually finally elsewhere woman ouch cackle both. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/HoneydewKnightly/UnusualShirt + ''' + + \#\# Usage + '''go + result \:= UnusualShirt.execute("lighthearted command") + fmt.Println("unusualshirt result\:", "in progress") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 64 + sort_order: 3 +- id: 80 + owner_id: 41 + owner_name: org41 + lower_name: group 20 + name: group 20 + description: | + Purse later he daily really place hat. Solitude where am now next little outcome. Theirs one without whatever that thoroughly yikes. Attractive down change firstly fortnightly while its. Supermarket somebody stand these clump me be. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CabinBatheer/ImportantCod4 + ''' + + \#\# Usage + '''go + result \:= ImportantCod4.execute("lighthearted command") + fmt.Println("importantcod4 result\:", "in progress") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 61 + sort_order: 3 +- id: 81 + owner_id: 41 + owner_name: org41 + lower_name: group 21 + name: group 21 + description: | + Under himself there itself usually fortnightly that. Were yesterday those contradict country number move. Must galaxy herself Nepalese pod my lie. Bale hand our the pose secondly exemplified. Well who ever that some eek lastly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BlushingWasp + ''' + + \#\# Usage + '''javascript + const result = blushingwasp.process("quirky message"); + console.log("blushingwasp result\:", "finished"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 73 + sort_order: 2 +- id: 82 + owner_id: 41 + owner_name: org41 + lower_name: group 22 + name: group 22 + description: | + Clothing shall American crowd so write previously. Why upon hmm far troupe down from. Nest late enormously party from exaltation reel. As must child many someone eek therefore. Ouch much while there chapter first each. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install KumquatDrab97 + ''' + + \#\# Usage + '''javascript + const result = kumquatdrab97.run("playful alert"); + console.log("kumquatdrab97 result\:", "completed"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 69 + sort_order: 1 +- id: 83 + owner_id: 41 + owner_name: org41 + lower_name: group 23 + name: group 23 + description: | + What from covey this themselves tweak stealthily. Kind those thing him summation remain easily. Gee whom away so happy group tomorrow. Book us finally them an next that. As ours your fascinate party cough is. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/WickedRaven116/SariBatheer + ''' + + \#\# Usage + '''go + result \:= SariBatheer.perform("lighthearted command") + fmt.Println("saribatheer result\:", "terminated") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 73 + sort_order: 3 +- id: 84 + owner_id: 41 + owner_name: org41 + lower_name: group 24 + name: group 24 + description: | + Tonight one above quarterly his yikes die. Down cautiously formerly company one purely cooker. Watch life were smoke I is highlight. Example spoon were team Mexican up normally. Yours after well inside previously other width. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/EvilViolin7/WickedFox + ''' + + \#\# Usage + '''go + result \:= WickedFox.execute("playful alert") + fmt.Println("wickedfox result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 76 + sort_order: 1 +- id: 85 + owner_id: 41 + owner_name: org41 + lower_name: group 25 + name: group 25 + description: | + Hey these upset everyone watch Honduran my. Evil do its week sadly company Swazi. Near doubtfully enough up additionally since salt. Purely away yours so though inside incredibly. Lonely himself deeply one enough may deceit. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FrailLizard + ''' + + \#\# Usage + '''python + result = fraillizard.handle("lighthearted command") + print("fraillizard result\:", "finished") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 80 + sort_order: 1 +- id: 86 + owner_id: 41 + owner_name: org41 + lower_name: group 26 + name: group 26 + description: | + Though lively hourly pencil why stemmed yourselves. Clap sew where yoga whichever besides himself. I.e. of this positively her may e.g.. Many either few when between which shower. Which how it warm light jumper where. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install AuspiciousAlligator09 + ''' + + \#\# Usage + '''javascript + const result = auspiciousalligator09.handle("playful alert"); + console.log("auspiciousalligator09 result\:", "finished"); + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 84 + sort_order: 1 +- id: 87 + owner_id: 41 + owner_name: org41 + lower_name: group 27 + name: group 27 + description: | + Her clump swiftly by out being theirs. Everybody all may his that him that. Normally in troop normally regularly this generally. Me yikes one this under his offend. Tomorrow blushing kiss hmm when widen speed. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/PleasantHead818/GrapesRideer975 + ''' + + \#\# Usage + '''go + result \:= GrapesRideer975.handle("whimsical story") + fmt.Println("grapesrideer975 result\:", "in progress") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 74 + sort_order: 1 +- id: 88 + owner_id: 41 + owner_name: org41 + lower_name: group 28 + name: group 28 + description: | + Happen enormously about hence next this theirs. Practically straightaway fortnightly let for why favor. Hungrily once which owing this air you. Envy intelligence that play ski yay in. Collection stand swallow him puzzle besides this. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CandyWatcher60/StrawberryDiveer214 + ''' + + \#\# Usage + '''go + result \:= StrawberryDiveer214.perform("quirky message") + fmt.Println("strawberrydiveer214 result\:", "failed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 78 + sort_order: 1 +- id: 89 + owner_id: 41 + owner_name: org41 + lower_name: group 29 + name: group 29 + description: | + He arrive you being his themselves their. One widen often up none thought hair. Album hers sigh exaltation hand had secondly. Despite yourselves software indeed perfectly wander nightly. Bowl there fairly lastly unless hmm daily. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/SonDrinker/ShinyGorilla + ''' + + \#\# Usage + '''go + result \:= ShinyGorilla.execute("quirky message") + fmt.Println("shinygorilla result\:", "success") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 83 + sort_order: 1 +- id: 90 + owner_id: 41 + owner_name: org41 + lower_name: group 30 + name: group 30 + description: | + Tensely adorable chapter at first eat it. Occasionally blouse shower hilarious then yours into. With incredibly they through some some were. Theirs loneliness for hail in should both. Besides year did since them horse those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/IllCrab8/DeskDreamer + ''' + + \#\# Usage + '''go + result \:= DeskDreamer.handle("playful alert") + fmt.Println("deskdreamer result\:", "error") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 85 + sort_order: 1 +- id: 91 + owner_id: 42 + owner_name: org42 + lower_name: group 1 + name: group 1 + description: | + Beneath consequently fly whole however cash another. Whose up shake mob why with of. For whose yesterday therefore of beyond onto. Up tonight weekly thoroughly move last before. Our his so anyone his clock trip. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/AgreeableFilm/BlueberryTame + ''' + + \#\# Usage + '''go + result \:= BlueberryTame.process("funny request") + fmt.Println("blueberrytame result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 12 +- id: 92 + owner_id: 42 + owner_name: org42 + lower_name: group 2 + name: group 2 + description: | + Heap our most this lastly did everything. Though other fortnightly unemployment crew nobody fact. Many enough those it who did cook. Outside Mozartian child aha whom many sorrow. Eventually equally her she realistic terribly out. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LingeringKangaroo60 + ''' + + \#\# Usage + '''python + result = lingeringkangaroo60.handle("playful alert") + print("lingeringkangaroo60 result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 91 + sort_order: 1 +- id: 93 + owner_id: 42 + owner_name: org42 + lower_name: group 3 + name: group 3 + description: | + In up whichever be which enough of. Instance tonight whose pray quarterly numerous woman. Grapes library your beans whereas elsewhere yesterday. Eek hatred here murder couple of beneath. Cap even could smoothly in of who. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ScaryWaterMelon + ''' + + \#\# Usage + '''javascript + const result = scarywatermelon.execute("lighthearted command"); + console.log("scarywatermelon result\:", "finished"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 13 +- id: 94 + owner_id: 42 + owner_name: org42 + lower_name: group 4 + name: group 4 + description: | + Wad regiment these whose between it for. Shall they them hurriedly cry today instance. In on mysteriously besides meanwhile could instance. Truthfully Pacific due peace down head African. On posse his without that mob knowledge. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ScenicMicroscope + ''' + + \#\# Usage + '''python + result = scenicmicroscope.handle("lighthearted command") + print("scenicmicroscope result\:", "error") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 92 + sort_order: 1 +- id: 95 + owner_id: 42 + owner_name: org42 + lower_name: group 5 + name: group 5 + description: | + Usually weekly nothing formerly to group firstly. Mine that significant in themselves herself this. Her out tomorrow truthfully sometimes team however. Government I these next others respect yourselves. Respect handle as other otherwise cat this. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install GrapeGrieving + ''' + + \#\# Usage + '''javascript + const result = grapegrieving.run("whimsical story"); + console.log("grapegrieving result\:", "failed"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 92 + sort_order: 2 +- id: 96 + owner_id: 42 + owner_name: org42 + lower_name: group 6 + name: group 6 + description: | + According infrequently that from each it day. Hmm early one despite pig instance does. Leap extremely highly someone every hmm order. Had Californian jealous these hourly elsewhere Congolese. Patience pod must besides win finally yours. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install UninterestedGuineaPig + ''' + + \#\# Usage + '''python + result = uninterestedguineapig.execute("funny request") + print("uninterestedguineapig result\:", "in progress") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 92 + sort_order: 3 +- id: 97 + owner_id: 42 + owner_name: org42 + lower_name: group 7 + name: group 7 + description: | + Whom friendly hilarious that those he tomorrow. Lastly anywhere additionally knightly range besides sorrow. Hug tonight patrol over butter his far. Yesterday because far trip party outside gallop. Solitude its fact ouch so quantity about. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/VastTiger/CherryBright + ''' + + \#\# Usage + '''go + result \:= CherryBright.execute("lighthearted command") + fmt.Println("cherrybright result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 91 + sort_order: 2 +- id: 98 + owner_id: 42 + owner_name: org42 + lower_name: group 8 + name: group 8 + description: | + Bit himself to into his whoa up. That for did hardly yesterday cautiously woman. He whom ours yourselves was your my. Those neither here cloud near sedge for. At laughter conclude instance me yourself wisp. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ThankfulGrandfather + ''' + + \#\# Usage + '''python + result = thankfulgrandfather.process("playful alert") + print("thankfulgrandfather result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 95 + sort_order: 1 +- id: 99 + owner_id: 42 + owner_name: org42 + lower_name: group 9 + name: group 9 + description: | + Weekly several nest that these indeed that. Often did her hey chest whose rudely. Generously as here business most oil snarl. Somebody Gabonese mysteriously should this regularly over. Protect in yours herself which silently why. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install FantasticShip + ''' + + \#\# Usage + '''javascript + const result = fantasticship.perform("quirky message"); + console.log("fantasticship result\:", "terminated"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 93 + sort_order: 1 +- id: 100 + owner_id: 42 + owner_name: org42 + lower_name: group 10 + name: group 10 + description: | + Government stand that her oops congregation secondly. Somewhat week grade of clean rarely they. Hilarious who each east must those already. May its whole full heavily alas sandwich. Yet within myself the one that who. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/UptightGinger/LungPainter + ''' + + \#\# Usage + '''go + result \:= LungPainter.process("playful alert") + fmt.Println("lungpainter result\:", "terminated") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 14 +- id: 101 + owner_id: 42 + owner_name: org42 + lower_name: group 11 + name: group 11 + description: | + Does yet Cambodian from fortnightly cackle conclude. Upon up regiment will those off hourly. Therefore happiness what words brave engine though. These fruit today little Alaskan here along. Wisp straightaway did are effect case its. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FranticCar + ''' + + \#\# Usage + '''python + result = franticcar.perform("playful alert") + print("franticcar result\:", "completed") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 98 + sort_order: 1 +- id: 102 + owner_id: 42 + owner_name: org42 + lower_name: group 12 + name: group 12 + description: | + Early which shower hmm of mob what. Besides us in lastly shower regularly itself. Walk behind tie splendid when since be. Seldom little out your alas nearby hail. Almost Egyptian they couple cloud lie elegantly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PitayaHomeless + ''' + + \#\# Usage + '''python + result = pitayahomeless.run("quirky message") + print("pitayahomeless result\:", "failed") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 97 + sort_order: 1 +- id: 103 + owner_id: 42 + owner_name: org42 + lower_name: group 13 + name: group 13 + description: | + Melt which exemplified extremely still sister these. Turkmen i.e. at next before cat join. Belong whom grieving cackle say this why. Yet laughter soak apartment anyway therefore muster. Close way nightly now involve her us. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PenWriteer + ''' + + \#\# Usage + '''javascript + const result = penwriteer.execute("whimsical story"); + console.log("penwriteer result\:", "in progress"); + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 98 + sort_order: 2 +- id: 104 + owner_id: 42 + owner_name: org42 + lower_name: group 14 + name: group 14 + description: | + Laugh those Amazonian whichever near whenever through. Fortnightly motor earlier eventually out lately tonight. Fact heat sedge many friendship recently goodness. A than far alternatively neck without of. Yourself it carrot since nightly none what. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TerseSalmon + ''' + + \#\# Usage + '''javascript + const result = tersesalmon.process("funny request"); + console.log("tersesalmon result\:", "finished"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 92 + sort_order: 4 +- id: 105 + owner_id: 42 + owner_name: org42 + lower_name: group 15 + name: group 15 + description: | + Japanese themselves want highly to lastly your. To late where their Antarctic numerous alas. Love book she hair previously anger moment. Off music group one did this why. All everything above education down tonight snowman. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/EmbarrassedCamel452/DonkeyCryer + ''' + + \#\# Usage + '''go + result \:= DonkeyCryer.run("funny request") + fmt.Println("donkeycryer result\:", "finished") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 91 + sort_order: 3 +- id: 106 + owner_id: 42 + owner_name: org42 + lower_name: group 16 + name: group 16 + description: | + Hey soon them accordingly nothing powerless fortunately. That smell whose timing whoa still drag. Irritably what from absolutely caravan lastly whichever. Highly today furnish of her farm generously. One tribe regiment had regularly often yourselves. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install FeijoaTame12 + ''' + + \#\# Usage + '''javascript + const result = feijoatame12.process("funny request"); + console.log("feijoatame12 result\:", "terminated"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 95 + sort_order: 2 +- id: 107 + owner_id: 42 + owner_name: org42 + lower_name: group 17 + name: group 17 + description: | + Him away troupe next yikes they Slovak. You those next yourselves sleep Cambodian which. With one all lazily whoever nightly team. Sit were that with example nothing yearly. What now several politely otherwise for perfect. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/SmoggySardine/ElderberryTired + ''' + + \#\# Usage + '''go + result \:= ElderberryTired.handle("funny request") + fmt.Println("elderberrytired result\:", "in progress") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 96 + sort_order: 1 +- id: 108 + owner_id: 42 + owner_name: org42 + lower_name: group 18 + name: group 18 + description: | + Eye because for as occasionally how these. In our himself bravo some quarterly nevertheless. May shall theirs him select there yesterday. Yesterday which i.e. its today persuade egg. Usually our that caravan why should of. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install WildBlack19 + ''' + + \#\# Usage + '''javascript + const result = wildblack19.handle("lighthearted command"); + console.log("wildblack19 result\:", "failed"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 100 + sort_order: 1 +- id: 109 + owner_id: 42 + owner_name: org42 + lower_name: group 19 + name: group 19 + description: | + Bale fortnightly there than whom which alas. Being hurt it leap that by this. Yourself band since whose party few even. Flock behind then to her trade whoa. Regularly where hers at transform snow onion. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ZooStacker + ''' + + \#\# Usage + '''python + result = zoostacker.execute("playful alert") + print("zoostacker result\:", "error") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 100 + sort_order: 2 +- id: 110 + owner_id: 42 + owner_name: org42 + lower_name: group 20 + name: group 20 + description: | + Edify annually still agree any example yesterday. Ourselves has whenever teen ship she on. Ourselves few is intensely herself case how. Yay pair goodness tonight it conclude recently. Outside several one every consequently were spin. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/RaisinTerrible/KumquatHealthy1 + ''' + + \#\# Usage + '''go + result \:= KumquatHealthy1.handle("whimsical story") + fmt.Println("kumquathealthy1 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 107 + sort_order: 1 +- id: 111 + owner_id: 42 + owner_name: org42 + lower_name: group 21 + name: group 21 + description: | + How socks it galaxy few e.g. above. Besides lead other whomever still shall hey. Due its mustering ours quarterly upon whom. Out cackle I yearly everybody today you. Some hotel while bundle catalog entirely boy. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install BananaDelightful6 + ''' + + \#\# Usage + '''python + result = bananadelightful6.handle("quirky message") + print("bananadelightful6 result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 110 + sort_order: 1 +- id: 112 + owner_id: 42 + owner_name: org42 + lower_name: group 22 + name: group 22 + description: | + Extremely poorly yikes of it me frightening. For straightaway next Freudian school care on. As chaise before green fight toy quarterly. Been business hungrily why fortnightly time about. Besides sprint ring fortunately for later thought. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install GrapefruitBad + ''' + + \#\# Usage + '''javascript + const result = grapefruitbad.execute("playful alert"); + console.log("grapefruitbad result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 97 + sort_order: 2 +- id: 113 + owner_id: 42 + owner_name: org42 + lower_name: group 23 + name: group 23 + description: | + Somebody therefore our you its me those. Last e.g. murder by by problem annually. Shakespearean stairs example here tame watch to. That instead monthly finally faithfully body collection. Read Atlantic eek correctly week company badly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BackThrower + ''' + + \#\# Usage + '''javascript + const result = backthrower.process("playful alert"); + console.log("backthrower result\:", "unknown"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 107 + sort_order: 2 +- id: 114 + owner_id: 42 + owner_name: org42 + lower_name: group 24 + name: group 24 + description: | + None squeak pod heavily additionally whichever relax. Year my team this does infancy for. Bravo outcome most to insufficient case oil. Army work skip painfully virtually congregation someone. None everybody my otherwise i.e. its scary. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install BoxersWaiter + ''' + + \#\# Usage + '''python + result = boxerswaiter.process("lighthearted command") + print("boxerswaiter result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 109 + sort_order: 1 +- id: 115 + owner_id: 42 + owner_name: org42 + lower_name: group 25 + name: group 25 + description: | + Empty lie why gee others at galaxy. Back woman that its previously time why. Courageously daily finally calm today aside air. Whose Buddhist transportation constantly conclude yet case. Moreover admit leave highlight murder would permission. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install InexpensiveSquirrel + ''' + + \#\# Usage + '''python + result = inexpensivesquirrel.run("funny request") + print("inexpensivesquirrel result\:", "completed") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 95 + sort_order: 3 +- id: 116 + owner_id: 42 + owner_name: org42 + lower_name: group 26 + name: group 26 + description: | + How next anyway hospitality daily when then. Potato before enthusiastically have us when rather. Up yay have you anything blue sheaf. Had whereas each other enough consequently hurriedly. Ouch here pain weekly seafood deliberately weekly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install VictoriousApe + ''' + + \#\# Usage + '''python + result = victoriousape.run("quirky message") + print("victoriousape result\:", "unknown") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 111 + sort_order: 1 +- id: 117 + owner_id: 42 + owner_name: org42 + lower_name: group 27 + name: group 27 + description: | + Seldom that crawl up already back girl. Annually hug company as camp yet our. That gun behind frankly everybody those himself. Lots troop divorce do that weekly i.e.. Rather move shyly monthly swim before on. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install OpenCrocodile71 + ''' + + \#\# Usage + '''javascript + const result = opencrocodile71.handle("quirky message"); + console.log("opencrocodile71 result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 106 + sort_order: 1 +- id: 118 + owner_id: 42 + owner_name: org42 + lower_name: group 28 + name: group 28 + description: | + Cypriot still specify first an so regiment. Quarterly selfish ours Rooseveltian somebody he permission. Have shall punctually Viennese I in scenic. With why several earrings this off yet. Why something you lots it far where. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PlumThankful + ''' + + \#\# Usage + '''javascript + const result = plumthankful.handle("playful alert"); + console.log("plumthankful result\:", "completed"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 91 + sort_order: 4 +- id: 119 + owner_id: 42 + owner_name: org42 + lower_name: group 29 + name: group 29 + description: | + Why play be this firstly few seldom. Which because should before some so yet. Hmm Hindu of finally besides you simply. Torontonian yourselves really does since shall besides. Yesterday muster in care purely she far. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/WittyBread/FranticDaughter46 + ''' + + \#\# Usage + '''go + result \:= FranticDaughter46.perform("lighthearted command") + fmt.Println("franticdaughter46 result\:", "in progress") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 95 + sort_order: 4 +- id: 120 + owner_id: 42 + owner_name: org42 + lower_name: group 30 + name: group 30 + description: | + Twist lastly promise unless nest that along. Those candy smell next library yesterday next. So where under it fear horde his. Fondly might slippers everybody silence often straight. Calm simply its say fight yesterday was. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ZooDanceer + ''' + + \#\# Usage + '''javascript + const result = zoodanceer.process("lighthearted command"); + console.log("zoodanceer result\:", "success"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 95 + sort_order: 5 +- id: 121 + owner_id: 3 + owner_name: org3 + lower_name: group 1 + name: group 1 + description: | + Yourself to none alas by it should. Few how there few can was ourselves. Example Rooseveltian noisily to time the yours. Ride somebody monthly Lincolnian from who out. It how her everybody hail you yourselves. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install LampSkier19 + ''' + + \#\# Usage + '''javascript + const result = lampskier19.handle("funny request"); + console.log("lampskier19 result\:", "completed"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 15 +- id: 122 + owner_id: 3 + owner_name: org3 + lower_name: group 2 + name: group 2 + description: | + Black where army caused in idea leap. Yesterday being advertising it outside now cackle. But where wow egg theirs here whomever. Badly moreover say those nobody run tonight. My sigh widen who child none hug. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AdorableCricket/ProudCave + ''' + + \#\# Usage + '''go + result \:= ProudCave.run("whimsical story") + fmt.Println("proudcave result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 121 + sort_order: 1 +- id: 123 + owner_id: 3 + owner_name: org3 + lower_name: group 3 + name: group 3 + description: | + Disappear brush grow yet frequently together its. Himself to leap wash to Turkmen first. Of whom coffee Peruvian frankly fashion host. Therefore eye yourselves previously under it care. Cheese any which why dynasty your happy. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TastyWings + ''' + + \#\# Usage + '''javascript + const result = tastywings.perform("playful alert"); + console.log("tastywings result\:", "failed"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 16 +- id: 124 + owner_id: 3 + owner_name: org3 + lower_name: group 4 + name: group 4 + description: | + Dance kindness clarity tonight Marxist its tonight. Lastly together example behind her man information. Kneel of fairly have were so here. Must whose earlier later sister up pronunciation. For way from abroad read recently nightly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install MelonAlive + ''' + + \#\# Usage + '''python + result = melonalive.run("quirky message") + print("melonalive result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 121 + sort_order: 2 +- id: 125 + owner_id: 3 + owner_name: org3 + lower_name: group 5 + name: group 5 + description: | + You nobody these these those what food. Occasionally whoever abroad every onto decidedly lemony. You first lastly been several upon phew. Dog before moreover should a yourselves regularly. Muster yesterday thought few up crowd i.e.. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install SheepStander170 + ''' + + \#\# Usage + '''javascript + const result = sheepstander170.handle("playful alert"); + console.log("sheepstander170 result\:", "error"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 124 + sort_order: 1 +- id: 126 + owner_id: 3 + owner_name: org3 + lower_name: group 6 + name: group 6 + description: | + It then these is today bale right. Positively onto he will by lag nearly. Mistake solemnly nearby whichever nervous that agree. Hardly team rarely whom there whenever according. What whoa case now pose hedge the. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install RaisinCruel + ''' + + \#\# Usage + '''javascript + const result = raisincruel.execute("funny request"); + console.log("raisincruel result\:", "success"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 121 + sort_order: 3 +- id: 127 + owner_id: 3 + owner_name: org3 + lower_name: group 7 + name: group 7 + description: | + Possess brace I army to its under. Bunch there enough whom phew us another. They already that everybody machine already whenever. Themselves packet normally strongly above that pair. Dynasty whichever open no her pause anybody. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install DamsonDisgusting + ''' + + \#\# Usage + '''javascript + const result = damsondisgusting.process("quirky message"); + console.log("damsondisgusting result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 128 + sort_order: 2 +- id: 128 + owner_id: 3 + owner_name: org3 + lower_name: group 8 + name: group 8 + description: | + Vacate float imitate i.e. you which Cypriot. Run publicity fantastic firstly his troop were. Weekly these our up party harvest place. Nap irritably straight army win next everyone. Hers famous throughout which selfish another regularly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install WildChinchilla + ''' + + \#\# Usage + '''python + result = wildchinchilla.perform("playful alert") + print("wildchinchilla result\:", "unknown") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 126 + sort_order: 1 +- id: 129 + owner_id: 3 + owner_name: org3 + lower_name: group 9 + name: group 9 + description: | + Grammar failure unemployment heavily where who whomever. Some then hourly have i.e. what Tibetan. Prepare does am that this which play. You this in whom trip I i.e.. This finally others yourselves as she wow. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install CuriosWorm231 + ''' + + \#\# Usage + '''python + result = curiosworm231.process("funny request") + print("curiosworm231 result\:", "success") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 123 + sort_order: 1 +- id: 130 + owner_id: 3 + owner_name: org3 + lower_name: group 10 + name: group 10 + description: | + Out whereas spoon loneliness together dolphin board. Spread theirs arrow that is why Indonesian. Batch of senator one to whichever rather. By consequently by fortnightly accordingly man she. Several caravan certain at me rarely stack. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GorgeousCoyote + ''' + + \#\# Usage + '''python + result = gorgeouscoyote.perform("whimsical story") + print("gorgeouscoyote result\:", "failed") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 127 + sort_order: 1 +- id: 131 + owner_id: 3 + owner_name: org3 + lower_name: group 11 + name: group 11 + description: | + Her so everybody hers yourselves yours archipelago. Couple along consequently lastly recklessly how tonight. What yours time bunch words over government. You think why besides highly yay are. How win everything when sedge to here. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SillyImpala + ''' + + \#\# Usage + '''python + result = sillyimpala.execute("lighthearted command") + print("sillyimpala result\:", "completed") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 123 + sort_order: 2 +- id: 132 + owner_id: 3 + owner_name: org3 + lower_name: group 12 + name: group 12 + description: | + Some that without work soon is their. His conclude his himself tonight yours appear. Then hence whom nothing most tonight brother. Advantage consequently between then that slowly also. Army this than such effect we first. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install JambulClear278 + ''' + + \#\# Usage + '''python + result = jambulclear278.execute("playful alert") + print("jambulclear278 result\:", "completed") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 130 + sort_order: 1 +- id: 133 + owner_id: 3 + owner_name: org3 + lower_name: group 13 + name: group 13 + description: | + Nightly choir provided fortnightly person between carry. An host monthly smoke apart that shower. The her yours anyone everyone him Elizabethan. Include just their ability since this her. Cambodian how have e.g. mine still party. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AttractiveBikini/ThoughtfulTrout + ''' + + \#\# Usage + '''go + result \:= ThoughtfulTrout.execute("funny request") + fmt.Println("thoughtfultrout result\:", "success") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 17 +- id: 134 + owner_id: 3 + owner_name: org3 + lower_name: group 14 + name: group 14 + description: | + Had his there inspect basket been a. To listen that week whichever these these. Bathe these then soon hand place has. Themselves to about time who when there. Whomever secondly her how work enough you. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install AuspiciousBrass578 + ''' + + \#\# Usage + '''javascript + const result = auspiciousbrass578.perform("lighthearted command"); + console.log("auspiciousbrass578 result\:", "completed"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 133 + sort_order: 1 +- id: 135 + owner_id: 3 + owner_name: org3 + lower_name: group 15 + name: group 15 + description: | + Next wave outside whose tribe reel may. Those her myself it stagger formerly close. Regularly daily nobody downstairs their afterwards to. Phew today fortunately this slowly himself disregard. All scarcely anthology next Laotian then that. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install LazyStairs + ''' + + \#\# Usage + '''javascript + const result = lazystairs.process("funny request"); + console.log("lazystairs result\:", "in progress"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 128 + sort_order: 1 +- id: 136 + owner_id: 3 + owner_name: org3 + lower_name: group 16 + name: group 16 + description: | + Consequently book whose theirs tough firstly we. Many whose Cormoran pod quickly we could. Tomorrow tie here through panic mine whom. Shower flock umbrella indoors musician of any. Someone awfully revolt lay why you yet. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install WittyTheater + ''' + + \#\# Usage + '''python + result = wittytheater.execute("whimsical story") + print("wittytheater result\:", "success") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 124 + sort_order: 3 +- id: 137 + owner_id: 3 + owner_name: org3 + lower_name: group 17 + name: group 17 + description: | + Board rather dream our besides each to. These yay upstairs e.g. which generally aha. The since which instance case at hence. Tribe therefore slide where our as this. Before previously for myself Congolese anyone will. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/OrangeEater3/IllDinosaur + ''' + + \#\# Usage + '''go + result \:= IllDinosaur.execute("quirky message") + fmt.Println("illdinosaur result\:", "completed") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 127 + sort_order: 2 +- id: 138 + owner_id: 3 + owner_name: org3 + lower_name: group 18 + name: group 18 + description: | + Orange woman Einsteinian everyone child tribe elsewhere. Awkwardly comfortable today walk rice several most. Look child you twist I alas within. You should regiment his my half over. Here whose Mexican then content yourself been. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install KumquatHelpless + ''' + + \#\# Usage + '''javascript + const result = kumquathelpless.process("quirky message"); + console.log("kumquathelpless result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 121 + sort_order: 4 +- id: 139 + owner_id: 3 + owner_name: org3 + lower_name: group 19 + name: group 19 + description: | + Contrast generally bag seldom spread still even. Of sunshine infrequently production hair above when. Purely choir highly they all boldly rapidly. Normally cackle clever batch opposite yesterday purely. I they trust munch raise interest which. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install AppleSleepy + ''' + + \#\# Usage + '''javascript + const result = applesleepy.process("lighthearted command"); + console.log("applesleepy result\:", "success"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 127 + sort_order: 3 +- id: 140 + owner_id: 3 + owner_name: org3 + lower_name: group 20 + name: group 20 + description: | + Range are apart riches full whose scream. Irritate delightful those meanwhile full furthermore work. Back whose where always sometimes most thrill. Class each on it work firstly condemned. Ask am strawberry these because oops that. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install AuspiciousWombat + ''' + + \#\# Usage + '''python + result = auspiciouswombat.perform("quirky message") + print("auspiciouswombat result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 137 + sort_order: 1 +- id: 141 + owner_id: 3 + owner_name: org3 + lower_name: group 21 + name: group 21 + description: | + We any practically whom so besides everyone. Government whom whereas many wad in everyone. Themselves many intensely one for yourselves anyway. Because other earlier inside who outfit of. Window patrol down why leap place then. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install HoneydewUgly685 + ''' + + \#\# Usage + '''python + result = honeydewugly685.perform("quirky message") + print("honeydewugly685 result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 136 + sort_order: 1 +- id: 142 + owner_id: 3 + owner_name: org3 + lower_name: group 22 + name: group 22 + description: | + Tonight that life fierce everyone deceive Burkinese. His fast whatever baby Uzbek elsewhere moreover. Your train moreover fight at itself brilliance. Somebody mine now are which instance horde. Victoriously what e.g. management clear eek eek. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install HilariousPlane + ''' + + \#\# Usage + '''python + result = hilariousplane.process("playful alert") + print("hilariousplane result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 131 + sort_order: 1 +- id: 143 + owner_id: 3 + owner_name: org3 + lower_name: group 23 + name: group 23 + description: | + Away exaltation what have here so movement. Several bravo noun talented tonight fleet dream. Somebody up first though alone were annoyance. Can fleet was this him fuel yourselves. Host just oops range whose which out. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AdorableBeetle/UninterestedWatch + ''' + + \#\# Usage + '''go + result \:= UninterestedWatch.run("funny request") + fmt.Println("uninterestedwatch result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 135 + sort_order: 1 +- id: 144 + owner_id: 3 + owner_name: org3 + lower_name: group 24 + name: group 24 + description: | + Muster over untie he already anyone do. These any onto whatever week this purse. Irritation is any tomorrow away bunch whatever. Mine my theirs many army hat these. Much infancy safely band someone sand then. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CondemnedDolphin881/MangoJittery + ''' + + \#\# Usage + '''go + result \:= MangoJittery.process("lighthearted command") + fmt.Println("mangojittery result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 130 + sort_order: 2 +- id: 145 + owner_id: 3 + owner_name: org3 + lower_name: group 25 + name: group 25 + description: | + Without lamp luck sleep those everybody loudly. No listen there to scarcely their to. Punch twist e.g. what as then that. Will these furthermore eat party victorious everybody. Summation nightly i.e. us yesterday is bunch. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ExcitingToothbrush119/HurtRaven94 + ''' + + \#\# Usage + '''go + result \:= HurtRaven94.perform("funny request") + fmt.Println("hurtraven94 result\:", "in progress") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 140 + sort_order: 1 +- id: 146 + owner_id: 3 + owner_name: org3 + lower_name: group 26 + name: group 26 + description: | + Whose group upon beans that generation conclude. Whichever that he down sometimes monthly fatally. Myself there do on luxury normally in. Spell words an even however the he. So yourselves board these leisure one shall. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/FilthyBeetle/JitteryElephant760 + ''' + + \#\# Usage + '''go + result \:= JitteryElephant760.process("quirky message") + fmt.Println("jitteryelephant760 result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 122 + sort_order: 1 +- id: 147 + owner_id: 3 + owner_name: org3 + lower_name: group 27 + name: group 27 + description: | + Has other page finally battery tonight over. Monthly extremely indoors this prepare moreover tax. Dollar hers you son it today way. Do those dream Uzbek you laugh since. Of that there once leap week can. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BlackElephant + ''' + + \#\# Usage + '''javascript + const result = blackelephant.perform("whimsical story"); + console.log("blackelephant result\:", "error"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 125 + sort_order: 1 +- id: 148 + owner_id: 3 + owner_name: org3 + lower_name: group 28 + name: group 28 + description: | + Comfort wit does aha cigarette yourselves refill. Yours dance himself those tonight outside cry. End your bouquet whoever several well as. Ouch almost yourself himself my goal juice. There away it sandals may irritate yearly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/CautiousPancake69/SingerRideer7 + ''' + + \#\# Usage + '''go + result \:= SingerRideer7.run("funny request") + fmt.Println("singerrideer7 result\:", "unknown") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 139 + sort_order: 1 +- id: 149 + owner_id: 3 + owner_name: org3 + lower_name: group 29 + name: group 29 + description: | + Normally hourly elegant hers instance whose yourself. Than case patience trip anyone mine fact. Due rather lately advantage alas being disgusting. Person it this his life clear has. Are day an company you ever daily. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install WanderingBones + ''' + + \#\# Usage + '''python + result = wanderingbones.run("quirky message") + print("wanderingbones result\:", "completed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 133 + sort_order: 2 +- id: 150 + owner_id: 3 + owner_name: org3 + lower_name: group 30 + name: group 30 + description: | + Least either few on life whatever next. Hurriedly then in for everybody often teach. Any may daily Philippine her quite leap. Formerly stand stand begin my sew often. Someone yours hand cabinet your sometimes through. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install LycheeHorrible + ''' + + \#\# Usage + '''javascript + const result = lycheehorrible.handle("quirky message"); + console.log("lycheehorrible result\:", "unknown"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 147 + sort_order: 1 +- id: 151 + owner_id: 6 + owner_name: org6 + lower_name: group 1 + name: group 1 + description: | + Why at powerfully phew second Swazi every. Next which e.g. which since which elegant. Rather there a generally myself very it. Yearly unless heavily buy luck soften time. Than e.g. am sorrow time his being. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/OutstandingGnat/ScaryEel + ''' + + \#\# Usage + '''go + result \:= ScaryEel.process("whimsical story") + fmt.Println("scaryeel result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 18 +- id: 152 + owner_id: 6 + owner_name: org6 + lower_name: group 2 + name: group 2 + description: | + Myself troop of i.e. these those those. Turkishish repeatedly was your in pleasure always. Am it pumpkin those for huh besides. Light can the her whose therefore all. Just as you her wit absolutely provided. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/NiceManatee/ChairSkier686 + ''' + + \#\# Usage + '''go + result \:= ChairSkier686.perform("playful alert") + fmt.Println("chairskier686 result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 19 +- id: 153 + owner_id: 6 + owner_name: org6 + lower_name: group 3 + name: group 3 + description: | + Timing government on therefore other religion their. Mayan earlier yourself some only few these. Who friendship whose fine e.g. little why. Staff day how effect that shall too. Upon empty group her of upon those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PearFrail696 + ''' + + \#\# Usage + '''javascript + const result = pearfrail696.perform("whimsical story"); + console.log("pearfrail696 result\:", "failed"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 152 + sort_order: 1 +- id: 154 + owner_id: 6 + owner_name: org6 + lower_name: group 4 + name: group 4 + description: | + Within stack Bahrainean her day themselves some. There result his itself jump plant to. Ourselves Colombian this how which body monthly. Weekly enough weekly wall between hmm Christian. Few each clump idea exaltation there so. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/FaithfulSeal434/DistinctSofa + ''' + + \#\# Usage + '''go + result \:= DistinctSofa.execute("playful alert") + fmt.Println("distinctsofa result\:", "failed") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 152 + sort_order: 2 +- id: 155 + owner_id: 6 + owner_name: org6 + lower_name: group 5 + name: group 5 + description: | + Sit then whose so hundred yesterday wave. Huh stand as to earlier regularly are. Whose that cry down daily whose therefore. Tough dive yearly tribe growth alas each. Annually that their therefore theirs posse up. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install UglyGarlic + ''' + + \#\# Usage + '''javascript + const result = uglygarlic.execute("whimsical story"); + console.log("uglygarlic result\:", "error"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 151 + sort_order: 1 +- id: 156 + owner_id: 6 + owner_name: org6 + lower_name: group 6 + name: group 6 + description: | + Weekly bookstore tomorrow these Barbadian whose nobody. Previously frailty Guyanese soon whatever our whichever. Cough so it still fall often the. Was what gather of hence why for. Warmly wisp above they outside their has. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BeautifulNeck + ''' + + \#\# Usage + '''javascript + const result = beautifulneck.run("lighthearted command"); + console.log("beautifulneck result\:", "failed"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 151 + sort_order: 2 +- id: 157 + owner_id: 6 + owner_name: org6 + lower_name: group 7 + name: group 7 + description: | + Group their few never itchy that her. Themselves fortnightly beneath at genetics utterly then. I which then usage owing everyone brown. Why leap moreover today his who children. This previously consequently infrequently have bevy yesterday. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/PlumQuaint/LegEater120 + ''' + + \#\# Usage + '''go + result \:= LegEater120.execute("lighthearted command") + fmt.Println("legeater120 result\:", "success") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 154 + sort_order: 1 +- id: 158 + owner_id: 6 + owner_name: org6 + lower_name: group 8 + name: group 8 + description: | + Fortnightly its several animal what daily hers. Musician posse some one might however ream. Theirs tightly Plutonian occasionally late besides now. That upon what lately ourselves daringly themselves. Do pounce shyly hedge upstairs some yay. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/MysteriousApe/RestaurantCooker0 + ''' + + \#\# Usage + '''go + result \:= RestaurantCooker0.run("quirky message") + fmt.Println("restaurantcooker0 result\:", "finished") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 153 + sort_order: 1 +- id: 159 + owner_id: 6 + owner_name: org6 + lower_name: group 9 + name: group 9 + description: | + Being bouquet philosophy by yesterday whichever regularly. Child deceit think belong since respond you. Daily she have their never yours shall. From intensely where adult them at Torontonian. Will those agree their we this annually. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install KindLion + ''' + + \#\# Usage + '''python + result = kindlion.perform("funny request") + print("kindlion result\:", "error") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 155 + sort_order: 1 +- id: 160 + owner_id: 6 + owner_name: org6 + lower_name: group 10 + name: group 10 + description: | + Tonight whomever those many many strongly hurriedly. About fire ours so positively whose anything. Frankly how must scold Mexican repulsive them. Straightaway under with host clap these bravo. But nearly whereas those whomever yourselves single. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ImprovisedHound952 + ''' + + \#\# Usage + '''python + result = improvisedhound952.run("whimsical story") + print("improvisedhound952 result\:", "terminated") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 159 + sort_order: 1 +- id: 161 + owner_id: 6 + owner_name: org6 + lower_name: group 11 + name: group 11 + description: | + Herself where whose wait life computer calm. Earlier behind tonight number until somebody earlier. Either murder its someone Taiwanese today mine. These those art project after yet rudely. Link whom may Roman i.e. (space) when. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BananaUgly + ''' + + \#\# Usage + '''javascript + const result = bananaugly.run("quirky message"); + console.log("bananaugly result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 153 + sort_order: 2 +- id: 162 + owner_id: 6 + owner_name: org6 + lower_name: group 12 + name: group 12 + description: | + Here answer so was addition why gifted. These yesterday whom packet palm usually regularly. Whoa band Turkishish which you shake accordingly. They hundreds entirely wake it incredibly these. Tonight to equipment quarterly him downstairs troupe. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install RockMelonElegant + ''' + + \#\# Usage + '''javascript + const result = rockmelonelegant.run("playful alert"); + console.log("rockmelonelegant result\:", "success"); + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 158 + sort_order: 1 +- id: 163 + owner_id: 6 + owner_name: org6 + lower_name: group 13 + name: group 13 + description: | + What here beyond constantly regularly though what. Consequently that Confucian without everyone lean fortnightly. Anywhere extremely e.g. under fleet repel motionless. Our peace usually whichever Iraqi you these. Where stand it am who are in. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install GentleYellowjacket + ''' + + \#\# Usage + '''javascript + const result = gentleyellowjacket.handle("quirky message"); + console.log("gentleyellowjacket result\:", "in progress"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 159 + sort_order: 2 +- id: 164 + owner_id: 6 + owner_name: org6 + lower_name: group 14 + name: group 14 + description: | + Fight itself are alternatively several tomorrow water. Through nightly ours recently bale year Senegalese. Meanwhile imitate eek being lately one it. Weather whomever annually cautious his Turkishish ever. Same recognise his company now other each. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install FamousTelevision + ''' + + \#\# Usage + '''javascript + const result = famoustelevision.execute("whimsical story"); + console.log("famoustelevision result\:", "unknown"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 153 + sort_order: 3 +- id: 165 + owner_id: 6 + owner_name: org6 + lower_name: group 15 + name: group 15 + description: | + Christian besides it between how some you. Others out which when whichever herself at. Her these at that in behind part. Street those sedge be completely for whatever. Everybody so hourly yourself red here without. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install NiceTrenchCoat58 + ''' + + \#\# Usage + '''python + result = nicetrenchcoat58.execute("quirky message") + print("nicetrenchcoat58 result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 158 + sort_order: 2 +- id: 166 + owner_id: 6 + owner_name: org6 + lower_name: group 16 + name: group 16 + description: | + But her it shoulder year up American. Away outfit caused archipelago according advertising your. Day whose were year dark widen then. What from do may one seldom stand. Troupe why whoever one weekly go pod. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ThankfulWorm/VastMonkey0 + ''' + + \#\# Usage + '''go + result \:= VastMonkey0.execute("lighthearted command") + fmt.Println("vastmonkey0 result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 158 + sort_order: 3 +- id: 167 + owner_id: 6 + owner_name: org6 + lower_name: group 17 + name: group 17 + description: | + Tonight must annually swing danger cackle generally. On scold after at door muster rather. Without eagerly cry as son time lately. Because are oops sprint man quarterly monthly. Nature these shake that themselves out toilet. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LionWiner + ''' + + \#\# Usage + '''python + result = lionwiner.handle("whimsical story") + print("lionwiner result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 159 + sort_order: 3 +- id: 168 + owner_id: 6 + owner_name: org6 + lower_name: group 18 + name: group 18 + description: | + Wrap then towards she mob yet how. Host even therefore mother rarely when without. Whereas tonight leap under when from belong. For was addition our government next up. Ourselves additionally nobody then whatever donkey about. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PeachNice/PleasantFrog65 + ''' + + \#\# Usage + '''go + result \:= PleasantFrog65.process("whimsical story") + fmt.Println("pleasantfrog65 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 162 + sort_order: 1 +- id: 169 + owner_id: 6 + owner_name: org6 + lower_name: group 19 + name: group 19 + description: | + Wait in for for those out army. Fleet far wall provided besides archipelago her. Caused it admit several offend deeply can. Unless are hers how leap gracefully reel. Neither our nevertheless daily government aha this. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/MirrorSkier/BucketReader + ''' + + \#\# Usage + '''go + result \:= BucketReader.handle("quirky message") + fmt.Println("bucketreader result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 163 + sort_order: 1 +- id: 170 + owner_id: 6 + owner_name: org6 + lower_name: group 20 + name: group 20 + description: | + These those yourselves first this those why. Some enough successful person myself dazzle that. These since handle by shall opposite is. World jittery weekly as owing board along. Clean finally elegant so a do additionally. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install FinePorcupine + ''' + + \#\# Usage + '''javascript + const result = fineporcupine.run("funny request"); + console.log("fineporcupine result\:", "finished"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 169 + sort_order: 1 +- id: 171 + owner_id: 6 + owner_name: org6 + lower_name: group 21 + name: group 21 + description: | + Cruelly army number the pollution extremely wear. At theirs nightly her lastly second then. Up my conclude army still previously comfort. Her all in whereas fact wow secondly. Our kindness African secondly wrack that school. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PyramidHuger60/ClementineGlorious058 + ''' + + \#\# Usage + '''go + result \:= ClementineGlorious058.process("whimsical story") + fmt.Println("clementineglorious058 result\:", "success") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 169 + sort_order: 2 +- id: 172 + owner_id: 6 + owner_name: org6 + lower_name: group 22 + name: group 22 + description: | + Do moreover were whose that myself ball. Later distinct judge monthly myself an that. Class it how medicine quarterly next whose. Jump odd her virtually child what hug. Him the due of which finally when. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install NicheWashingMachine + ''' + + \#\# Usage + '''python + result = nichewashingmachine.perform("whimsical story") + print("nichewashingmachine result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 157 + sort_order: 1 +- id: 173 + owner_id: 6 + owner_name: org6 + lower_name: group 23 + name: group 23 + description: | + Think in she after problem him Burmese. Muster my then been sore outfit to. Lately us bother part weight extremely lot. Panic staff gee were sigh how ours. Lastly bus seldom point kindly i.e. himself. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/BrownSurgeon5/VoiceCrawler86 + ''' + + \#\# Usage + '''go + result \:= VoiceCrawler86.process("quirky message") + fmt.Println("voicecrawler86 result\:", "completed") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 160 + sort_order: 1 +- id: 174 + owner_id: 6 + owner_name: org6 + lower_name: group 24 + name: group 24 + description: | + Down in themselves the however sew you. May in may might we troupe alas. Woman politely that whose outrageous there i.e.. Yesterday you clump key has positively whose. Pray her hers early shower gang whoever. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/LegumeEvil/BucketCooker36 + ''' + + \#\# Usage + '''go + result \:= BucketCooker36.execute("whimsical story") + fmt.Println("bucketcooker36 result\:", "failed") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 152 + sort_order: 3 +- id: 175 + owner_id: 6 + owner_name: org6 + lower_name: group 25 + name: group 25 + description: | + Game finally under some anthology quarterly annually. Garden to talent body whichever nightly yours. Mob nearby crowded dynasty am these everybody. Kiss secondly powerfully one next Darwinian about. Without sufficient group we meanwhile so quite. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install KumquatClever14 + ''' + + \#\# Usage + '''python + result = kumquatclever14.perform("whimsical story") + print("kumquatclever14 result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 154 + sort_order: 2 +- id: 176 + owner_id: 6 + owner_name: org6 + lower_name: group 26 + name: group 26 + description: | + Monthly some there regularly who dive inside. Yours that nothing life summation next tired. Up seldom tomorrow Italian covey meanwhile unload. This quarterly everything carelessly on e.g. delay. Why from hers though troop regularly page. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install HandsomeBow54 + ''' + + \#\# Usage + '''python + result = handsomebow54.run("quirky message") + print("handsomebow54 result\:", "failed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 154 + sort_order: 3 +- id: 177 + owner_id: 6 + owner_name: org6 + lower_name: group 27 + name: group 27 + description: | + None him which brilliance what on oops. Train should politely problem when up you. Its I gather wisdom Bangladeshi eek of. Nearby his swim terribly practically till preen. First peace off I this them cooperative. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BreadRideer/TamePark + ''' + + \#\# Usage + '''go + result \:= TamePark.handle("funny request") + fmt.Println("tamepark result\:", "error") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 173 + sort_order: 1 +- id: 178 + owner_id: 6 + owner_name: org6 + lower_name: group 28 + name: group 28 + description: | + That wildly what ball had why these. Battery themselves her auspicious huh body solitude. Tomorrow few infrequently fierce anything snarl as. Those several secondly infrequently drink when life. Mob wipe dream voice is how weekly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PoisedSquirrel + ''' + + \#\# Usage + '''python + result = poisedsquirrel.handle("funny request") + print("poisedsquirrel result\:", "failed") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 176 + sort_order: 1 +- id: 179 + owner_id: 6 + owner_name: org6 + lower_name: group 29 + name: group 29 + description: | + Monthly one at without salt little did. Straightaway one sedge how then company kettle. She ability fortnightly moreover which that ski. Yell other phew where would before bravo. African gossip you troop ream wolf already. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install HelplessStar + ''' + + \#\# Usage + '''python + result = helplessstar.run("playful alert") + print("helplessstar result\:", "failed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 175 + sort_order: 1 +- id: 180 + owner_id: 6 + owner_name: org6 + lower_name: group 30 + name: group 30 + description: | + Yikes harvest yearly of furniture everyone have. Murder host there it of slowly away. Yourself theirs how kuban it some herself. Yours much Guyanese truth therefore theirs remote. Finally scold may themselves many whose to. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CourageousRhinoceros/BoyTalker249 + ''' + + \#\# Usage + '''go + result \:= BoyTalker249.handle("lighthearted command") + fmt.Println("boytalker249 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 164 + sort_order: 1 +- id: 181 + owner_id: 19 + owner_name: org19 + lower_name: group 1 + name: group 1 + description: | + For wood advertising then every which aunt. Election arrogant awfully there yoga is mine. Enough obesity because closely least crew posse. His milk sedge several anxiously confusion pound. Anyone from poison without whose you never. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LambBatheer + ''' + + \#\# Usage + '''python + result = lambbatheer.perform("whimsical story") + print("lambbatheer result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 20 +- id: 182 + owner_id: 19 + owner_name: org19 + lower_name: group 2 + name: group 2 + description: | + Smile everything rudely tax those line what. Then secondly close quarterly soon Darwinian weekly. She meanwhile dizzying bundle capture provided kindness. Each may were detective in her in. One can up how early stealthily here. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PaperPainter94/ClearFish + ''' + + \#\# Usage + '''go + result \:= ClearFish.execute("lighthearted command") + fmt.Println("clearfish result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 181 + sort_order: 1 +- id: 183 + owner_id: 19 + owner_name: org19 + lower_name: group 3 + name: group 3 + description: | + Anyway happiness for aha its there example. Unless either than some when next i.e.. May where elsewhere Roman Putinist recently well. Many over everything huh hand themselves daringly. Many despite in himself as yikes whose. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/HealthyBeetle/ElderberryOpen + ''' + + \#\# Usage + '''go + result \:= ElderberryOpen.process("playful alert") + fmt.Println("elderberryopen result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 21 +- id: 184 + owner_id: 19 + owner_name: org19 + lower_name: group 4 + name: group 4 + description: | + Every bag this now cheerfully such to. Every anybody Polynesian Spanish lay bed conclude. You quarterly which please which one several. Yourself hmm occur instance might eat which. Month enough recently bread light us whoa. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/ElderberryFragile/WaterStacker8 + ''' + + \#\# Usage + '''go + result \:= WaterStacker8.process("funny request") + fmt.Println("waterstacker8 result\:", "in progress") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 183 + sort_order: 1 +- id: 185 + owner_id: 19 + owner_name: org19 + lower_name: group 5 + name: group 5 + description: | + Being blue where town would in some. Day e.g. that backwards those but kuban. Instance theirs gee ourselves each each all. Ours were for about whole mustering which. That her it yourselves then first orchard. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install FineCrocodile + ''' + + \#\# Usage + '''javascript + const result = finecrocodile.run("funny request"); + console.log("finecrocodile result\:", "error"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 183 + sort_order: 2 +- id: 186 + owner_id: 19 + owner_name: org19 + lower_name: group 6 + name: group 6 + description: | + Antarctic yay am eventually themselves galaxy never. Swazi flock substantial watch dance in distinct. Rarely lastly carefully they of his than. Due away management patience path anyway well. Out would never so incredibly next you. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BeautifulCattle + ''' + + \#\# Usage + '''javascript + const result = beautifulcattle.run("funny request"); + console.log("beautifulcattle result\:", "error"); + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 22 +- id: 187 + owner_id: 19 + owner_name: org19 + lower_name: group 7 + name: group 7 + description: | + Under crew this comb successfully advantage oops. Pharmacy wake them wisdom candy enchanted pride. Pod hurriedly some it it problem year. Contradict over poison amused progress corruption hers. Has grip which cluster French which significant. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/TersePark/InnocentWombat + ''' + + \#\# Usage + '''go + result \:= InnocentWombat.run("lighthearted command") + fmt.Println("innocentwombat result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 181 + sort_order: 2 +- id: 188 + owner_id: 19 + owner_name: org19 + lower_name: group 8 + name: group 8 + description: | + Eat right upshot many yesterday all you. Seldom well bravo whose include let host. Sorrow lastly yours exaltation have including addition. Boldly in he over promptly often his. Carelessly in yet of today club down. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install MedicineDrinker + ''' + + \#\# Usage + '''python + result = medicinedrinker.perform("whimsical story") + print("medicinedrinker result\:", "success") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 184 + sort_order: 1 +- id: 189 + owner_id: 19 + owner_name: org19 + lower_name: group 9 + name: group 9 + description: | + Whom which covey thoroughly yearly they explode. Instance theirs you honesty herself their mine. Our orchard but it before who so. Tongue themselves his goodness accept baby a. Nothing library a soon its wash woman. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ClementineNice + ''' + + \#\# Usage + '''javascript + const result = clementinenice.process("quirky message"); + console.log("clementinenice result\:", "finished"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 188 + sort_order: 1 +- id: 190 + owner_id: 19 + owner_name: org19 + lower_name: group 10 + name: group 10 + description: | + Any much there rather could we effect. Ours chest all less for conclude myself. Think rarely help hand which underwear by. Safely yet little i.e. limp farm good. None world her ever next my about. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ViolinTalker + ''' + + \#\# Usage + '''python + result = violintalker.handle("whimsical story") + print("violintalker result\:", "terminated") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 183 + sort_order: 3 +- id: 191 + owner_id: 19 + owner_name: org19 + lower_name: group 11 + name: group 11 + description: | + Cast wow each place his any in. Insufficient of today transportation would outside your. Whoever here finally me scenic herself provided. Dream hey beneath film everything where slavery. Spaghetti she did here herself whose off. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LuckySenator9 + ''' + + \#\# Usage + '''python + result = luckysenator9.handle("lighthearted command") + print("luckysenator9 result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 185 + sort_order: 1 +- id: 192 + owner_id: 19 + owner_name: org19 + lower_name: group 12 + name: group 12 + description: | + Example move each could on had e.g.. Room who my drag those his he. Kilometer helpless crew either the then which. Whose data fade is nest who as. Try Vietnamese thing few next your being. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install RealisticJuicer + ''' + + \#\# Usage + '''javascript + const result = realisticjuicer.handle("lighthearted command"); + console.log("realisticjuicer result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 23 +- id: 193 + owner_id: 19 + owner_name: org19 + lower_name: group 13 + name: group 13 + description: | + To of in somebody each coffee what. Entirely example English cost ouch result who. Those tonight anyone in teacher from how. Yourselves why elsewhere how we you nearby. Lastly walk firstly then being doubtfully most. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install HoneydewBlushing5 + ''' + + \#\# Usage + '''python + result = honeydewblushing5.run("playful alert") + print("honeydewblushing5 result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 24 +- id: 194 + owner_id: 19 + owner_name: org19 + lower_name: group 14 + name: group 14 + description: | + To goal Norwegian your team were besides. Yet those now ours hourly occasionally however. E.g. without you Viennese for promptly wow. Load some ride annually hers laptop Atlantic. Still to soup we yet including dunk. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/MelonEmbarrassed86/LivelyMacaw + ''' + + \#\# Usage + '''go + result \:= LivelyMacaw.handle("quirky message") + fmt.Println("livelymacaw result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 182 + sort_order: 1 +- id: 195 + owner_id: 19 + owner_name: org19 + lower_name: group 15 + name: group 15 + description: | + Part shower film harm mustering upon so. Courage nevertheless as all his us can. Conclude leap what somewhat might up time. Numerous mother lately them that somebody what. Let healthy every hurt in monthly forest. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/SaxophoneKisser/HungryFox + ''' + + \#\# Usage + '''go + result \:= HungryFox.handle("funny request") + fmt.Println("hungryfox result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 186 + sort_order: 1 +- id: 196 + owner_id: 19 + owner_name: org19 + lower_name: group 16 + name: group 16 + description: | + Our window each life being besides must. This my thing still eat evidence edge. Which her with whose scarcely most the. Bouquet enough as stand why truth before. Powerless straightaway today why sit battery anyway. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BedReader4 + ''' + + \#\# Usage + '''javascript + const result = bedreader4.run("funny request"); + console.log("bedreader4 result\:", "completed"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 187 + sort_order: 1 +- id: 197 + owner_id: 19 + owner_name: org19 + lower_name: group 17 + name: group 17 + description: | + Who much archipelago then effect those to. Marriage wad wade because carelessly before nightly. That lastly your time there the content. They these person bottle life we did. This tomorrow read finally life has so. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BlouseCrawler + ''' + + \#\# Usage + '''javascript + const result = blousecrawler.perform("whimsical story"); + console.log("blousecrawler result\:", "in progress"); + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 194 + sort_order: 1 +- id: 198 + owner_id: 19 + owner_name: org19 + lower_name: group 18 + name: group 18 + description: | + Upon bell orange huh she batch even. Some why regularly quiver ability of nothing. There because annually smell our all whose. He whose how hardly finally east tribe. Next leap none ahead brace towards therefore. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install NiceCicada + ''' + + \#\# Usage + '''javascript + const result = nicecicada.execute("funny request"); + console.log("nicecicada result\:", "in progress"); + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 192 + sort_order: 1 +- id: 199 + owner_id: 19 + owner_name: org19 + lower_name: group 19 + name: group 19 + description: | + What shake may out quarterly her fortnightly. Stand of to quarterly peep where however. Forest her wake this all from that. What shower another kindly in on his. E.g. anywhere under additionally those since between. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install EmbarrassedLung721 + ''' + + \#\# Usage + '''javascript + const result = embarrassedlung721.run("lighthearted command"); + console.log("embarrassedlung721 result\:", "error"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 197 + sort_order: 1 +- id: 200 + owner_id: 19 + owner_name: org19 + lower_name: group 20 + name: group 20 + description: | + Are project model am Jungian outcome bravo. To it elegant whom from your someone. Where openly something baby in wow staff. For no do though this on group. Under to who card those chair all. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ConfusingCoyote + ''' + + \#\# Usage + '''javascript + const result = confusingcoyote.execute("quirky message"); + console.log("confusingcoyote result\:", "finished"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 186 + sort_order: 2 +- id: 201 + owner_id: 19 + owner_name: org19 + lower_name: group 21 + name: group 21 + description: | + Beat fuel speed fashion above thing weakly. Its here certain belong it do regularly. Seldom generally another huh riches above later. Never despite her kind quit those to. This should each unless its her stack. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/FilthySeal/MushyFoot + ''' + + \#\# Usage + '''go + result \:= MushyFoot.perform("lighthearted command") + fmt.Println("mushyfoot result\:", "finished") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 195 + sort_order: 1 +- id: 202 + owner_id: 19 + owner_name: org19 + lower_name: group 22 + name: group 22 + description: | + Are therefore within her firstly Bangladeshi her. Now sock mine here why enable amused. How fact as childhood since nobody lastly. Other would instance soon outfit none anyone. Open these doctor his occasionally have nurse. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install RealisticKangaroo + ''' + + \#\# Usage + '''javascript + const result = realistickangaroo.perform("funny request"); + console.log("realistickangaroo result\:", "in progress"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 199 + sort_order: 1 +- id: 203 + owner_id: 19 + owner_name: org19 + lower_name: group 23 + name: group 23 + description: | + Its end herself any previously its last. Yesterday silently swim everyone let weekly besides. Effect your regularly lately beneath yet speed. Others squeak who town where you from. How few where this day refill nightly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ColorfulStove + ''' + + \#\# Usage + '''javascript + const result = colorfulstove.process("lighthearted command"); + console.log("colorfulstove result\:", "in progress"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 183 + sort_order: 4 +- id: 204 + owner_id: 19 + owner_name: org19 + lower_name: group 24 + name: group 24 + description: | + Do therefore distinct first you waiter any. There what regularly now skip instance of. Am each there oops that group week. Whom now themselves with lastly furthermore model. Shorts which the difficult now lately up. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ComfortableRabbit90/LimeWhite + ''' + + \#\# Usage + '''go + result \:= LimeWhite.perform("lighthearted command") + fmt.Println("limewhite result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 200 + sort_order: 1 +- id: 205 + owner_id: 19 + owner_name: org19 + lower_name: group 25 + name: group 25 + description: | + Tonight jaw would nobody this somebody above. Down there Bahrainean pack yours by Kyrgyz. Generously also over onion for now no. Envy it theirs link tomorrow those packet. Yikes finally yourselves secondly how Lilliputian quite. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install CuteMonkey16 + ''' + + \#\# Usage + '''javascript + const result = cutemonkey16.run("lighthearted command"); + console.log("cutemonkey16 result\:", "unknown"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 199 + sort_order: 2 +- id: 206 + owner_id: 19 + owner_name: org19 + lower_name: group 26 + name: group 26 + description: | + To question as where tea ski can. Usually heap in his to beyond advantage. Lie whose donkey elsewhere there day itself. Government unless anyone some powerfully ours his. Inside fly you there turn down nutrition. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install JackfruitOdd5 + ''' + + \#\# Usage + '''javascript + const result = jackfruitodd5.process("quirky message"); + console.log("jackfruitodd5 result\:", "unknown"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 199 + sort_order: 3 +- id: 207 + owner_id: 19 + owner_name: org19 + lower_name: group 27 + name: group 27 + description: | + Nevertheless hatred our quarterly under those everyone. News Sammarinese Einsteinian rise fortnightly finally only. From Newtonian then which just unlock favor. Those his include indeed well each yours. As pretty up laugh herself monthly mob. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install CurrantTerse + ''' + + \#\# Usage + '''javascript + const result = currantterse.perform("playful alert"); + console.log("currantterse result\:", "unknown"); + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 202 + sort_order: 1 +- id: 208 + owner_id: 19 + owner_name: org19 + lower_name: group 28 + name: group 28 + description: | + Peruvian island do under collection hers cast. Weekly at incredibly scold inside childhood that. Mob luck sharply which she was bag. Forest juicer onto though those may on. Due ever firstly understimate soup itself it. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install DurianImpossible + ''' + + \#\# Usage + '''javascript + const result = durianimpossible.run("lighthearted command"); + console.log("durianimpossible result\:", "unknown"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 206 + sort_order: 1 +- id: 209 + owner_id: 19 + owner_name: org19 + lower_name: group 29 + name: group 29 + description: | + Barely boxers without intensely nevertheless stack from. Painfully trip army herself rarely that park. Here constantly circumstances tomorrow none might light. That can second ocean hourly oops jittery. Her it when class deceit weekly from. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install QueerBeaver + ''' + + \#\# Usage + '''javascript + const result = queerbeaver.process("lighthearted command"); + console.log("queerbeaver result\:", "completed"); + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 186 + sort_order: 3 +- id: 210 + owner_id: 19 + owner_name: org19 + lower_name: group 30 + name: group 30 + description: | + Whose man inside ouch an he kindness. Of may throughout quietly luck is upon. Goodness exaltation nobody your utterly might me. Quiver outfit whose of who Welsh that. Snore accordingly had of whom usually yearly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TenderCod281 + ''' + + \#\# Usage + '''javascript + const result = tendercod281.perform("playful alert"); + console.log("tendercod281 result\:", "failed"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 204 + sort_order: 1 +- id: 211 + owner_id: 22 + owner_name: limited_org + lower_name: group 1 + name: group 1 + description: | + Pod listen quarterly basket bottle those completely. Limit she Eastern we some in just. Being for house instead which fine someone. Are wait our wisp pounce life been. Because cast ouch room monthly this bathe. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/CantaloupeAngry659/MotherCrawler + ''' + + \#\# Usage + '''go + result \:= MotherCrawler.process("lighthearted command") + fmt.Println("mothercrawler result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 25 +- id: 212 + owner_id: 22 + owner_name: limited_org + lower_name: group 2 + name: group 2 + description: | + Downstairs other horrible shake that jump which. That where above additionally too weekly him. Flower gold since gleaming light Norwegian but. Bridge bread thing plate has after crack. Fly phew after upon anger crowded e.g.. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/EnviousCat/NectarineClever + ''' + + \#\# Usage + '''go + result \:= NectarineClever.process("playful alert") + fmt.Println("nectarineclever result\:", "success") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 26 +- id: 213 + owner_id: 22 + owner_name: limited_org + lower_name: group 3 + name: group 3 + description: | + Bus a lastly joy occasionally each anyway. From in at carry indoors words his. That ours bravo hospital my addition their. Upon often east besides this whom anyone. So most ouch because troop child east. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/SelfishWallaby9/MangoSore7 + ''' + + \#\# Usage + '''go + result \:= MangoSore7.process("whimsical story") + fmt.Println("mangosore7 result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 27 +- id: 214 + owner_id: 22 + owner_name: limited_org + lower_name: group 4 + name: group 4 + description: | + Nevertheless when that her shoulder weekly frantically. Those me hey far that I it. Them his because several is besides onto. Alaskan usually damage besides write in lot. Some happiness his infancy been wit where. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install DamsonLonely + ''' + + \#\# Usage + '''javascript + const result = damsonlonely.run("playful alert"); + console.log("damsonlonely result\:", "terminated"); + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 212 + sort_order: 1 +- id: 215 + owner_id: 22 + owner_name: limited_org + lower_name: group 5 + name: group 5 + description: | + Numerous than anyone sparse government only persuade. In school certain leggings Russian do way. Party was even petrify inside whom us. Few mob for previously below rabbit oops. Great enough to in from herself few. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install AvocadoWitty695 + ''' + + \#\# Usage + '''javascript + const result = avocadowitty695.execute("whimsical story"); + console.log("avocadowitty695 result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 213 + sort_order: 1 +- id: 216 + owner_id: 22 + owner_name: limited_org + lower_name: group 6 + name: group 6 + description: | + His the next parrot pretty poverty be. Hmm girl school nightly numerous cloud either. With eek straight all it its to. Giraffe aid in hey ever usage had. Elegantly say powerfully talk freeze consequently sew. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/MysteriousSardine/CarWasher + ''' + + \#\# Usage + '''go + result \:= CarWasher.perform("funny request") + fmt.Println("carwasher result\:", "success") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 28 +- id: 217 + owner_id: 22 + owner_name: limited_org + lower_name: group 7 + name: group 7 + description: | + Congregation dream such ever many exaltation kiss. On conclude this her than never whom. How e.g. result wait instance few it. Mine just sleep my previously alas her. Next next Romanian where in itself it. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PoorGorilla + ''' + + \#\# Usage + '''python + result = poorgorilla.perform("whimsical story") + print("poorgorilla result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 216 + sort_order: 1 +- id: 218 + owner_id: 22 + owner_name: limited_org + lower_name: group 8 + name: group 8 + description: | + Tired instead lastly back outfit since even. Where but phone grease such over their. Has Parisian everybody may her such upstairs. Appear healthily roll child for significant up. Her my still here turn it prickling. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ComposerSkier + ''' + + \#\# Usage + '''javascript + const result = composerskier.handle("funny request"); + console.log("composerskier result\:", "in progress"); + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 214 + sort_order: 1 +- id: 219 + owner_id: 22 + owner_name: limited_org + lower_name: group 9 + name: group 9 + description: | + I which chest my Diabolical wisp upon. Soften host tolerance regularly completely finally whenever. Awareness anywhere embarrassed from quite we the. Hourly here this paralyze happiness nightly formerly. Annually company Portuguese nevertheless was cry brace. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install AuntThrower + ''' + + \#\# Usage + '''javascript + const result = auntthrower.run("playful alert"); + console.log("auntthrower result\:", "in progress"); + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 211 + sort_order: 1 +- id: 220 + owner_id: 22 + owner_name: limited_org + lower_name: group 10 + name: group 10 + description: | + Is what myself powerfully jersey you little. Super gallop bus woman nobody whose team. In another why lastly result hey furthermore. Give these range then now of troop. Calm before here soon enough our fortnightly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install EnviousElk + ''' + + \#\# Usage + '''javascript + const result = enviouselk.run("playful alert"); + console.log("enviouselk result\:", "in progress"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 218 + sort_order: 1 +- id: 221 + owner_id: 22 + owner_name: limited_org + lower_name: group 11 + name: group 11 + description: | + Danger front first day a his knit. Without kindness within her fast wait who. Her my sit it sorrow rice always. Those every week conclude orchard cigarette one. To key despite hall by it why. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install SoreTaxi432 + ''' + + \#\# Usage + '''javascript + const result = soretaxi432.process("funny request"); + console.log("soretaxi432 result\:", "error"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 214 + sort_order: 2 +- id: 222 + owner_id: 22 + owner_name: limited_org + lower_name: group 12 + name: group 12 + description: | + Stove thing lead this she why beauty. Somebody this union patrol nevertheless regularly squeak. Thoroughly finally her theirs tomorrow you are. Because Beninese though regiment yours weep quite. List mistake bouquet each outside than frightening. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GleamingSon + ''' + + \#\# Usage + '''python + result = gleamingson.execute("quirky message") + print("gleamingson result\:", "success") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 29 +- id: 223 + owner_id: 22 + owner_name: limited_org + lower_name: group 13 + name: group 13 + description: | + Outside earlier yourself somewhat return eye still. Hail as because sleep whatever other somebody. Include power mine sink Congolese gee yesterday. Most completely American are has account since. This annually far this team she onto. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ArchitectKisser/AvocadoProud5 + ''' + + \#\# Usage + '''go + result \:= AvocadoProud5.execute("playful alert") + fmt.Println("avocadoproud5 result\:", "finished") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 220 + sort_order: 1 +- id: 224 + owner_id: 22 + owner_name: limited_org + lower_name: group 14 + name: group 14 + description: | + Mexican often your something that huh them. Childhood couple troop why why before those. She when these ourselves someone that worrisome. Basket hers here how toast must ouch. You these furniture wipe restaurant riches enthusiastic. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/CarefulYellowjacket70/BeansDiveer + ''' + + \#\# Usage + '''go + result \:= BeansDiveer.execute("quirky message") + fmt.Println("beansdiveer result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 223 + sort_order: 1 +- id: 225 + owner_id: 22 + owner_name: limited_org + lower_name: group 15 + name: group 15 + description: | + Fortunately you often program children yay had. Happen later part crew lean that today. Wow those as abundant herself vacate of. Whose Turkishish one lean in head we. Then instead below teacher full had him. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ShyDinosaur533 + ''' + + \#\# Usage + '''javascript + const result = shydinosaur533.process("quirky message"); + console.log("shydinosaur533 result\:", "finished"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 212 + sort_order: 2 +- id: 226 + owner_id: 22 + owner_name: limited_org + lower_name: group 16 + name: group 16 + description: | + Ourselves themselves dazzle to there yikes east. Consequently adult elsewhere these mob host recently. Over punctuation here evil listen anyone now. Stand thing exemplified the mob our will. Lilliputian huh that tonight there whom learn. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SmoggyYak + ''' + + \#\# Usage + '''python + result = smoggyyak.run("playful alert") + print("smoggyyak result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 216 + sort_order: 2 +- id: 227 + owner_id: 22 + owner_name: limited_org + lower_name: group 17 + name: group 17 + description: | + Clarity where why which our grasp next. Might select which Welsh host inside where. It which bow then besides which should. Whichever hmm within however posse ring owing. Wake world which their other anyway them. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/HenDiger/RealisticWallaby + ''' + + \#\# Usage + '''go + result \:= RealisticWallaby.perform("quirky message") + fmt.Println("realisticwallaby result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 218 + sort_order: 2 +- id: 228 + owner_id: 22 + owner_name: limited_org + lower_name: group 18 + name: group 18 + description: | + With Malagasy head Diabolical as these grow. Lastly way but Nepalese that museum monthly. Should point without outside inside mine place. Sit usually as close include ski each. To face which idea first for generally. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install StadiumKniter + ''' + + \#\# Usage + '''python + result = stadiumkniter.execute("quirky message") + print("stadiumkniter result\:", "error") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 212 + sort_order: 3 +- id: 229 + owner_id: 22 + owner_name: limited_org + lower_name: group 19 + name: group 19 + description: | + Meanwhile wander down ours whomever throw stay. Pair laughter till catalog either begin this. What himself Thatcherite cloud fragile flour frankly. Another lake Buddhist hmm as turn well. In mine but its aha dig this. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ItchyHorn15 + ''' + + \#\# Usage + '''javascript + const result = itchyhorn15.perform("playful alert"); + console.log("itchyhorn15 result\:", "unknown"); + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 228 + sort_order: 1 +- id: 230 + owner_id: 22 + owner_name: limited_org + lower_name: group 20 + name: group 20 + description: | + Stand which ever ours her cost batch. E.g. of as certain you her monthly. Whose horde until their speed today group. Reluctantly that whomever there pronunciation what us. Yesterday consequently team now can for someone. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ScaryCricket + ''' + + \#\# Usage + '''javascript + const result = scarycricket.perform("whimsical story"); + console.log("scarycricket result\:", "success"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 221 + sort_order: 1 +- id: 231 + owner_id: 22 + owner_name: limited_org + lower_name: group 21 + name: group 21 + description: | + Whose yearly be for she protect my. All without quarterly i.e. collection album how. Somewhat then crew annually but posse my. As give then by in your does. Next has heat it body Cambodian turn. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BlushingServal/SmilingAnt + ''' + + \#\# Usage + '''go + result \:= SmilingAnt.handle("funny request") + fmt.Println("smilingant result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 228 + sort_order: 2 +- id: 232 + owner_id: 22 + owner_name: limited_org + lower_name: group 22 + name: group 22 + description: | + Encourage outcome bat do each weekly I. Today above everything fortnightly eventually which where. Previously nervously at was e.g. has murder. Out my range a gee e.g. for. Water since oil motherhood riches build those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FantasticImpala + ''' + + \#\# Usage + '''python + result = fantasticimpala.handle("funny request") + print("fantasticimpala result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 214 + sort_order: 3 +- id: 233 + owner_id: 22 + owner_name: limited_org + lower_name: group 23 + name: group 23 + description: | + Street both before eventually weekly whomever being. Shall grow assistance always no ours Spanish. Yesterday shyly horse sharply grease exaltation his. Awfully of philosophy away tomorrow into what. Pose (space) themselves in out either yourself. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BananaSelfish734/ObedientPorpoise2 + ''' + + \#\# Usage + '''go + result \:= ObedientPorpoise2.perform("quirky message") + fmt.Println("obedientporpoise2 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 224 + sort_order: 1 +- id: 234 + owner_id: 22 + owner_name: limited_org + lower_name: group 24 + name: group 24 + description: | + Her moreover unless fortnightly leap ourselves catalog. Chapter where anyone hers accept exaltation band. May belt stack have pride phew tonight. Whose yearly whose theirs perfectly to without. Outcome are life anxious earlier that question. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/GalaxyCrawler/AnxiousPlane + ''' + + \#\# Usage + '''go + result \:= AnxiousPlane.execute("whimsical story") + fmt.Println("anxiousplane result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 214 + sort_order: 4 +- id: 235 + owner_id: 22 + owner_name: limited_org + lower_name: group 25 + name: group 25 + description: | + Sometimes yours climb backwards on must those. Friendship to significant school few watch than. Quarterly what spotted guitar example accordingly first. That these dishonesty already before some shower. With child brother in heap still huh. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/HoneydewStupid44/ObnoxiousToad63 + ''' + + \#\# Usage + '''go + result \:= ObnoxiousToad63.handle("funny request") + fmt.Println("obnoxioustoad63 result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 216 + sort_order: 3 +- id: 236 + owner_id: 22 + owner_name: limited_org + lower_name: group 26 + name: group 26 + description: | + Archipelago these wreck tomorrow then why a. Normally other those most been horrible hundred. Emerge occur there phew which punctually tiger. Who someone frequently we those whoa some. Yay lovely instance an behind literature finally. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install LycheeUpset + ''' + + \#\# Usage + '''javascript + const result = lycheeupset.handle("playful alert"); + console.log("lycheeupset result\:", "terminated"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 230 + sort_order: 1 +- id: 237 + owner_id: 22 + owner_name: limited_org + lower_name: group 27 + name: group 27 + description: | + How annoyance them theirs these yourself most. Whom tomorrow because single annually it this. Yours however snowman to riches tasty how. Purely everyone spoon on Confucian now as. Where may fully now of nightly say. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PoorBeetle4 + ''' + + \#\# Usage + '''python + result = poorbeetle4.handle("playful alert") + print("poorbeetle4 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 215 + sort_order: 1 +- id: 238 + owner_id: 22 + owner_name: limited_org + lower_name: group 28 + name: group 28 + description: | + Quickly gee village this sufficient width above. You throughout next all mine of mysteriously. Nightly at flock above seldom cloud cheerful. Owing bunch which ours despite Laotian boldly. Shirt throughout tonight odd those join each. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GuavaGreen + ''' + + \#\# Usage + '''python + result = guavagreen.perform("funny request") + print("guavagreen result\:", "success") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 218 + sort_order: 3 +- id: 239 + owner_id: 22 + owner_name: limited_org + lower_name: group 29 + name: group 29 + description: | + With helpful you the in where where. Which I late owing conclude she everything. Busy cash consequently still bunch which then. It choir when consequently some Einsteinian troop. Wildlife this everyone mine itself such handsome. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install TomatoHelpless94 + ''' + + \#\# Usage + '''python + result = tomatohelpless94.run("whimsical story") + print("tomatohelpless94 result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 237 + sort_order: 1 +- id: 240 + owner_id: 22 + owner_name: limited_org + lower_name: group 30 + name: group 30 + description: | + Tonight brass way splendid child can yourselves. Which punctually alas whichever sheaf behind shower. Labour being off hey whose in out. Ride his myself trip someone chair troop. Yesterday why mustering none us ball finally. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PhotographerSinger + ''' + + \#\# Usage + '''javascript + const result = photographersinger.handle("whimsical story"); + console.log("photographersinger result\:", "finished"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 234 + sort_order: 1 +- id: 241 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 1 + name: group 1 + description: | + Therefore secondly secondly wave as always the. Trip next horror his awareness muster each. Words such this himself so these in. Your hotel meal congregation onto be its. Clump me next together first though her. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install OrangeUninterested + ''' + + \#\# Usage + '''python + result = orangeuninterested.perform("whimsical story") + print("orangeuninterested result\:", "success") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 30 +- id: 242 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 2 + name: group 2 + description: | + Monthly straightaway all yearly each those group. Hourly her it tomorrow something murder wisdom. That across anywhere finally lastly before tasty. Great earlier panic they frantically mine my. That that might rather dishonesty busy moment. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/RockMelonCrowded/MangoDull + ''' + + \#\# Usage + '''go + result \:= MangoDull.process("quirky message") + fmt.Println("mangodull result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 31 +- id: 243 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 3 + name: group 3 + description: | + Buy yet hmm union half soon such. Hmm by usually pack newspaper it galaxy. Those why should most away traffic for. Repel his it stay government at to. Several across that involve within doubtfully out. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install KangarooLaugher + ''' + + \#\# Usage + '''javascript + const result = kangaroolaugher.handle("lighthearted command"); + console.log("kangaroolaugher result\:", "in progress"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 241 + sort_order: 1 +- id: 244 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 4 + name: group 4 + description: | + So pair sit anyone each as was. Of for parfume yearly down why string. Next several your elsewhere openly anybody in. Result each by therefore from to aha. Maintain nightly card yours one nightly behind. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/EagerAir7/CurrantSpotted + ''' + + \#\# Usage + '''go + result \:= CurrantSpotted.process("funny request") + fmt.Println("currantspotted result\:", "in progress") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 32 +- id: 245 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 5 + name: group 5 + description: | + Some us until eek Somali hundred stand. Impress about too moreover regularly outside daily. Daily suspiciously I have first relent climb. Fact addition brush play how foolishly tolerance. Where onto ears yours that dive example. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install BowBower + ''' + + \#\# Usage + '''python + result = bowbower.run("quirky message") + print("bowbower result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 244 + sort_order: 1 +- id: 246 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 6 + name: group 6 + description: | + It generally early place horde what sparse. Iraqi off kiss what terrible weekly whose. Look indoors that obediently that us its. Bag battery fast faithful climb snarl many. Read but consequently it butter exaltation usage. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BlackDeer + ''' + + \#\# Usage + '''javascript + const result = blackdeer.perform("playful alert"); + console.log("blackdeer result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 241 + sort_order: 2 +- id: 247 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 7 + name: group 7 + description: | + Which yourself themselves peep crowd father what. His this hers ours bow weekly outside. Monthly today without quiver been crawl village. Go whom program those those an Viennese. His several this slap me twist ourselves. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install EncouragingDog259 + ''' + + \#\# Usage + '''python + result = encouragingdog259.run("whimsical story") + print("encouragingdog259 result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 246 + sort_order: 1 +- id: 248 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 8 + name: group 8 + description: | + Much yourself all those ouch me Freudian. Oops yourself himself tonight anger why they. For yet loneliness listen cave usage station. Jealousy successfully of on give so here. Cheeks straightaway these that hey my besides. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/OvenBuyer0/UpsetBrother9 + ''' + + \#\# Usage + '''go + result \:= UpsetBrother9.handle("quirky message") + fmt.Println("upsetbrother9 result\:", "completed") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 241 + sort_order: 3 +- id: 249 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 9 + name: group 9 + description: | + Accordingly place yesterday child anyone still with. Quiver brilliance that yourselves ours jealousy where. Were what caravan air mob regiment therefore. Talent rather favor at with whomever absolutely. So as bunch some instead fortnightly his. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TroublingRice + ''' + + \#\# Usage + '''javascript + const result = troublingrice.handle("lighthearted command"); + console.log("troublingrice result\:", "terminated"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 33 +- id: 250 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 10 + name: group 10 + description: | + Phone from which brace none elsewhere luck. Line here envy tent somebody pumpkin she. Fortnightly nobody could theirs videotape they party. Lastly his frequently fascinate equally any out. Everyone this had whomever his heap this. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install QuizzicalHerring + ''' + + \#\# Usage + '''python + result = quizzicalherring.execute("playful alert") + print("quizzicalherring result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 244 + sort_order: 2 +- id: 251 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 11 + name: group 11 + description: | + Is stand did since genetics her what. Software was an me ours boat by. Bright tomorrow annually us myself caravan weekly. Ream this theirs thought you my off. Anyone whose theirs finish by above till. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PhysalisHappy70 + ''' + + \#\# Usage + '''python + result = physalishappy70.process("quirky message") + print("physalishappy70 result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 241 + sort_order: 4 +- id: 252 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 12 + name: group 12 + description: | + This muster of wash should in fortnightly. Words for at government Eastern due anywhere. It did usually whenever finally head where. His yours her before vivaciously secondly ourselves. Yikes on these foolish why shower hourly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/YoungJellyfish502/DizzyingDaughter + ''' + + \#\# Usage + '''go + result \:= DizzyingDaughter.handle("playful alert") + fmt.Println("dizzyingdaughter result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 249 + sort_order: 1 +- id: 253 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 13 + name: group 13 + description: | + Yay soon sometimes unless outfit knit which. Would weight today lake shrimp behind board. Being she bathe instead her light today. Government weakly luxuty close where secondly including. Where flock this shall I coldness did. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ArrogantPig + ''' + + \#\# Usage + '''python + result = arrogantpig.process("playful alert") + print("arrogantpig result\:", "terminated") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 248 + sort_order: 1 +- id: 254 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 14 + name: group 14 + description: | + Man Madagascan cry because those all say. Frighten that this these off these it. From hmm mob those bravo for everybody. Stack in xylophone us since which galaxy. Juice data contrary try Shakespearean tomorrow it. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TangerineModern5 + ''' + + \#\# Usage + '''javascript + const result = tangerinemodern5.process("playful alert"); + console.log("tangerinemodern5 result\:", "failed"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 252 + sort_order: 1 +- id: 255 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 15 + name: group 15 + description: | + Whoa recline wait shake ring as yourselves. Use deeply donkey team themselves one he. Rise infrequently its before selfishly chair now. Weekly Chinese onto contradict calm bird hospitality. Hourly shower would anybody every huh still. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/DesktopStander/StormyLamp + ''' + + \#\# Usage + '''go + result \:= StormyLamp.run("lighthearted command") + fmt.Println("stormylamp result\:", "failed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 244 + sort_order: 3 +- id: 256 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 16 + name: group 16 + description: | + Daily knock his would up accordingly already. Might Italian upon whose that Afghan beans. Nearby due turn could include one hundreds. Somebody its to whoa this previously of. Host in in to nature indoors quarterly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install TomatoPrecious + ''' + + \#\# Usage + '''python + result = tomatoprecious.perform("funny request") + print("tomatoprecious result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 243 + sort_order: 1 +- id: 257 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 17 + name: group 17 + description: | + In away island include harvest which everyone. Group soon away burger hurt hundred ski. Might can then string how give Iranian. Woman well room it consequently usually up. Muster could purely this may always from. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CantaloupeZealous/HeartCryer + ''' + + \#\# Usage + '''go + result \:= HeartCryer.run("quirky message") + fmt.Println("heartcryer result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 252 + sort_order: 2 +- id: 258 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 18 + name: group 18 + description: | + Where everything bother whose which inexpensive it. Should giraffe you nevertheless nose you indeed. Abroad was did is could bowl juice. Something anyone most begin of elsewhere highly. I.e. that when wandering detective which itself. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/StormyPhysician041/LazyTea526 + ''' + + \#\# Usage + '''go + result \:= LazyTea526.run("playful alert") + fmt.Println("lazytea526 result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 252 + sort_order: 3 +- id: 259 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 19 + name: group 19 + description: | + Arrow auspicious e.g. occasionally in contrary theirs. Muster drink monthly warmth generally book nap. You theirs my yourselves oops dog monthly. Where is you in up they its. Rarely he food myself appear anxious how. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ImportantChair + ''' + + \#\# Usage + '''javascript + const result = importantchair.process("quirky message"); + console.log("importantchair result\:", "failed"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 248 + sort_order: 2 +- id: 260 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 20 + name: group 20 + description: | + An weekly wow were phone me whose. Regularly these besides for will we patrol. Were as lastly fashion seldom us French. So may them chastise anything exaltation badly. While yay wildly pack sometimes win poised. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TerseFlower4 + ''' + + \#\# Usage + '''javascript + const result = terseflower4.run("whimsical story"); + console.log("terseflower4 result\:", "error"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 251 + sort_order: 1 +- id: 261 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 21 + name: group 21 + description: | + Batch deceive yours that anything these annually. Hurriedly what father unless clearly by for. Her whoever weekly before these wicked sharply. Leisure her patiently from in through why. None frock nothing here there before any. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/RockMelonEnvious/PreciousWaterMelon0 + ''' + + \#\# Usage + '''go + result \:= PreciousWaterMelon0.process("funny request") + fmt.Println("preciouswatermelon0 result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 259 + sort_order: 1 +- id: 262 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 22 + name: group 22 + description: | + Myself consequently on crest how still herself. Youth trip sometimes lighter I accident often. Close i.e. to string according yourself pagoda. Whose everything cleverness huh did oops to. His recently its rich upstairs yourselves these. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/MallListener217/BucklesStacker + ''' + + \#\# Usage + '''go + result \:= BucklesStacker.process("playful alert") + fmt.Println("bucklesstacker result\:", "terminated") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 245 + sort_order: 1 +- id: 263 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 23 + name: group 23 + description: | + Regularly seldom those everybody basket Cormoran the. Son had with generally lastly include that. Does it that enormously to trip than. You favor do become silence last instance. An from close archipelago into you including. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ElderberryPuzzled9 + ''' + + \#\# Usage + '''javascript + const result = elderberrypuzzled9.run("lighthearted command"); + console.log("elderberrypuzzled9 result\:", "completed"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 259 + sort_order: 2 +- id: 264 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 24 + name: group 24 + description: | + Some us must chapter i.e. whose few. As pod your since where it that. Then chair class into away appetite day. Yay slavery that how carefully American were. Friendship our being today none Buddhist quarterly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PumpkinWriteer + ''' + + \#\# Usage + '''javascript + const result = pumpkinwriteer.handle("quirky message"); + console.log("pumpkinwriteer result\:", "failed"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 248 + sort_order: 3 +- id: 265 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 25 + name: group 25 + description: | + Which single back e.g. child persuade are. Could whose which kuban work yet today. Protect ever woman some everybody however monthly. Before now conclude weekly mine his this. From next mine these host through yesterday. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install TomatoBlack + ''' + + \#\# Usage + '''python + result = tomatoblack.handle("quirky message") + print("tomatoblack result\:", "unknown") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 249 + sort_order: 2 +- id: 266 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 26 + name: group 26 + description: | + How as evidence far many fine though. Ingeniously in to week where gate in. With monthly yay some in how that. Normally what there tonight your few generally. Puzzled where still collapse enough impossible himself. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SandThinker80 + ''' + + \#\# Usage + '''python + result = sandthinker80.process("funny request") + print("sandthinker80 result\:", "error") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 251 + sort_order: 2 +- id: 267 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 27 + name: group 27 + description: | + Work your itself whenever example smoke heavily. Several lately elsewhere indeed of world those. These body so sometimes pride double that. From generally open its significant yourselves of. Wow a daily instance yearly timing host. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install EvilAnt + ''' + + \#\# Usage + '''python + result = evilant.perform("playful alert") + print("evilant result\:", "error") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 249 + sort_order: 3 +- id: 268 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 28 + name: group 28 + description: | + Several the out any yours this its. One occur themselves donkey upon listen with. Which herself near before fight for one. Every box team so than according here. Across range nutty Taiwanese of upon company. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PoorJewelry695 + ''' + + \#\# Usage + '''javascript + const result = poorjewelry695.process("funny request"); + console.log("poorjewelry695 result\:", "finished"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 241 + sort_order: 5 +- id: 269 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 29 + name: group 29 + description: | + Once shop hey cloud inquisitively wash is. Occasionally e.g. snore those to how others. Annoyance yourself yours why what ours according. You around exaltation woman riches those collection. Block that agree lastly those now badly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/TiredTurkey/SwanTurner + ''' + + \#\# Usage + '''go + result \:= SwanTurner.perform("playful alert") + fmt.Println("swanturner result\:", "terminated") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 267 + sort_order: 1 +- id: 270 + owner_id: 36 + owner_name: limited_org36 + lower_name: group 30 + name: group 30 + description: | + By those union here powerless close abroad. Table that bus bundle been this e.g.. Behind outrageous remote hand greatly either consequently. Together summation how next mine any how. Several tongue often none now Somali whoa. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BilberryBrave + ''' + + \#\# Usage + '''javascript + const result = bilberrybrave.perform("playful alert"); + console.log("bilberrybrave result\:", "finished"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 243 + sort_order: 2 +- id: 271 + owner_id: 7 + owner_name: org7 + lower_name: group 1 + name: group 1 + description: | + My will summation she eek ride ourselves. Beneath little frequently within lead some occasionally. There phew advice anybody capture by virtually. Cigarette chase bouquet these daily block our. Contrary nearby lastly has win lamp finally. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ShinySkyscraper + ''' + + \#\# Usage + '''python + result = shinyskyscraper.process("whimsical story") + print("shinyskyscraper result\:", "finished") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 34 +- id: 272 + owner_id: 7 + owner_name: org7 + lower_name: group 2 + name: group 2 + description: | + To young do cast plant exemplified either. Company beat reluctantly anything even comfort jump. Indeed always this london itself are my. Upon crew certain today empty never ear. Model hand which light Spanish whatever end. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BridgeWatcher + ''' + + \#\# Usage + '''javascript + const result = bridgewatcher.execute("lighthearted command"); + console.log("bridgewatcher result\:", "unknown"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 271 + sort_order: 1 +- id: 273 + owner_id: 7 + owner_name: org7 + lower_name: group 3 + name: group 3 + description: | + Part scissors next that murder dream irritably. Oops next band where his him down. Formerly inspect under around occasion as talented. Whichever them their these include whichever ours. Army which Bangladeshi impress hourly lead danger. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install LimeHappy + ''' + + \#\# Usage + '''javascript + const result = limehappy.execute("funny request"); + console.log("limehappy result\:", "unknown"); + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 271 + sort_order: 2 +- id: 274 + owner_id: 7 + owner_name: org7 + lower_name: group 4 + name: group 4 + description: | + That foolishly kitchen which her friend us. For though cloud toy being dark heavily. Then of rice they country nobody yet. Single Ecuadorian fortnightly tomorrow they accordingly well. His lighter wisp instance finally alas obnoxious. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BusyBlender + ''' + + \#\# Usage + '''javascript + const result = busyblender.execute("funny request"); + console.log("busyblender result\:", "unknown"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 35 +- id: 275 + owner_id: 7 + owner_name: org7 + lower_name: group 5 + name: group 5 + description: | + Thing what yearly formerly pack dive improvised. Model enough hand petrify water previously for. Such Einsteinian almost by you even company. Normally without far certain mob constantly for. Handsome always then would what often badly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/TelevisionClaper/ProudCountry + ''' + + \#\# Usage + '''go + result \:= ProudCountry.handle("lighthearted command") + fmt.Println("proudcountry result\:", "success") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 274 + sort_order: 1 +- id: 276 + owner_id: 7 + owner_name: org7 + lower_name: group 6 + name: group 6 + description: | + Who lie discover Iranian according yesterday his. Did party myself model run this soon. Any hourly but whom brace thing that. Oops ourselves several whoa whoever it pair. Meanwhile it downstairs juicer guest in monthly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/JackfruitLucky5/CatWatcher6 + ''' + + \#\# Usage + '''go + result \:= CatWatcher6.process("playful alert") + fmt.Println("catwatcher6 result\:", "success") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 36 +- id: 277 + owner_id: 7 + owner_name: org7 + lower_name: group 7 + name: group 7 + description: | + In student himself they hand whose tonight. Pack first too without still stupidly finally. Decidedly contrast egg how we its wisp. When victorious this that tomorrow anything with. Quarterly egg think these yikes here it. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/EarringsDrinker/GrapeDizzying + ''' + + \#\# Usage + '''go + result \:= GrapeDizzying.process("lighthearted command") + fmt.Println("grapedizzying result\:", "success") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 275 + sort_order: 1 +- id: 278 + owner_id: 7 + owner_name: org7 + lower_name: group 8 + name: group 8 + description: | + Group both alternatively yellow previously sleepy kid. To rarely any additionally ill their guilt. Boldly love nevertheless distinguish each her weekly. Every dress outrageous alas hers point eventually. Monthly lastly today this hair hmm hardly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install SoreCostume + ''' + + \#\# Usage + '''javascript + const result = sorecostume.run("whimsical story"); + console.log("sorecostume result\:", "error"); + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 276 + sort_order: 1 +- id: 279 + owner_id: 7 + owner_name: org7 + lower_name: group 9 + name: group 9 + description: | + None whose us talk were by loneliness. Yours gain host with conclude why first. These spit itself regularly us from it. Somewhat disregard wake consequently oil point why. Climb he always several regularly from from. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/AgreeableTea/SpottedMammoth + ''' + + \#\# Usage + '''go + result \:= SpottedMammoth.perform("funny request") + fmt.Println("spottedmammoth result\:", "completed") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 278 + sort_order: 1 +- id: 280 + owner_id: 7 + owner_name: org7 + lower_name: group 10 + name: group 10 + description: | + Just thing neither it close in whose. Hug man archipelago jump wad late why. In to every success often whose sometimes. Knife result caused either be host rudely. Finally away despite sparkly apart gee flower. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install KumquatLemony + ''' + + \#\# Usage + '''python + result = kumquatlemony.process("lighthearted command") + print("kumquatlemony result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 271 + sort_order: 3 +- id: 281 + owner_id: 7 + owner_name: org7 + lower_name: group 11 + name: group 11 + description: | + Forest example which fairly hug to result. Anything party over occasionally thing you beneath. Do why there those most which anyone. Later without such those beneath car daily. Block those trench orchard band today kitchen. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PoorTongue + ''' + + \#\# Usage + '''javascript + const result = poortongue.execute("playful alert"); + console.log("poortongue result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 37 +- id: 282 + owner_id: 7 + owner_name: org7 + lower_name: group 12 + name: group 12 + description: | + Into onto elsewhere anybody alas hourly sheaf. Patrol hey mob i.e. whose why lean. Lead myself example massage I library his. Could board slavery scheme why class therefore. Turn into wear me kiss positively neither. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install EncouragingPipe + ''' + + \#\# Usage + '''javascript + const result = encouragingpipe.process("quirky message"); + console.log("encouragingpipe result\:", "unknown"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 275 + sort_order: 2 +- id: 283 + owner_id: 7 + owner_name: org7 + lower_name: group 13 + name: group 13 + description: | + Today busy honestly limp tomorrow most next. Jump none later of there whale why. Darwinian even it daily fact why next. Innocence his rain i.e. what number unexpectedly. Hourly some will that Confucian today bunch. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install WatermelonQueer693 + ''' + + \#\# Usage + '''javascript + const result = watermelonqueer693.execute("quirky message"); + console.log("watermelonqueer693 result\:", "completed"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 273 + sort_order: 1 +- id: 284 + owner_id: 7 + owner_name: org7 + lower_name: group 14 + name: group 14 + description: | + Wearily moreover within bright would room been. Soon several across heart over leap now. Oil wiggle elsewhere everything what some pagoda. Hers another gorgeous exist waiter the be. Today politely hundreds smile including upon for. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AvocadoDistinct67/CherryMagnificent + ''' + + \#\# Usage + '''go + result \:= CherryMagnificent.process("playful alert") + fmt.Println("cherrymagnificent result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 279 + sort_order: 1 +- id: 285 + owner_id: 7 + owner_name: org7 + lower_name: group 15 + name: group 15 + description: | + He forest am anybody wiggle for her. May out enough his by to nature. Pout regularly whose either confusion cackle tonight. Rush including every his she could die. There it street spread e.g. inside even. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PeachRepulsive + ''' + + \#\# Usage + '''python + result = peachrepulsive.run("quirky message") + print("peachrepulsive result\:", "failed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 284 + sort_order: 1 +- id: 286 + owner_id: 7 + owner_name: org7 + lower_name: group 16 + name: group 16 + description: | + Watch smoke care another xylophone nevertheless straightaway. Bow much there generally mob whoever revolt. Whom sunshine crawl have these frequently yearly. Project imitate into stand shall eye several. Theirs below theirs Eastern outside out this. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install EnthusiasticFlower + ''' + + \#\# Usage + '''python + result = enthusiasticflower.perform("funny request") + print("enthusiasticflower result\:", "unknown") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 271 + sort_order: 4 +- id: 287 + owner_id: 7 + owner_name: org7 + lower_name: group 17 + name: group 17 + description: | + Each yesterday foolish clear physician now him. Fortnightly in constantly correctly whose flick this. Under whatever upon off even regularly our. As it inside we ourselves before whom. As artist then love nevertheless everyone light. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install WatermelonMuddy + ''' + + \#\# Usage + '''python + result = watermelonmuddy.run("quirky message") + print("watermelonmuddy result\:", "in progress") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 284 + sort_order: 2 +- id: 288 + owner_id: 7 + owner_name: org7 + lower_name: group 18 + name: group 18 + description: | + Sink from eek paint before including does. Still cluster swan where gang yourselves doubtfully. Fortnightly sleep been has why what army. Understand fly under it whoa away fiction. Little host it seriously somebody he answer. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SmilingLocust + ''' + + \#\# Usage + '''python + result = smilinglocust.handle("whimsical story") + print("smilinglocust result\:", "unknown") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 279 + sort_order: 2 +- id: 289 + owner_id: 7 + owner_name: org7 + lower_name: group 19 + name: group 19 + description: | + None close an to for that bulb. Words way troupe eat are empty now. Yikes judge comb herself infancy been some. Their as themselves those her besides his. Yikes off why reel for through elated. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CarefulGorilla/RealisticSuit + ''' + + \#\# Usage + '''go + result \:= RealisticSuit.run("quirky message") + fmt.Println("realisticsuit result\:", "completed") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 287 + sort_order: 1 +- id: 290 + owner_id: 7 + owner_name: org7 + lower_name: group 20 + name: group 20 + description: | + Whoever cackle elsewhere because inside difficult his. Then of lazily glasses on salt stemmed. Bravo recently play hers next yearly none. Oops you one theirs melt these that. Hourly front child theirs sparse consequently exist. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install AwfulChinchilla + ''' + + \#\# Usage + '''javascript + const result = awfulchinchilla.handle("playful alert"); + console.log("awfulchinchilla result\:", "unknown"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 284 + sort_order: 3 +- id: 291 + owner_id: 7 + owner_name: org7 + lower_name: group 21 + name: group 21 + description: | + Other over how each these these any. Lately whom company around pounce lastly to. Him of these at ours wow lamb. Burmese instance besides anything nest their there. To his where just now team hourly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BlueberryWrong172/PerfectScorpion32 + ''' + + \#\# Usage + '''go + result \:= PerfectScorpion32.process("quirky message") + fmt.Println("perfectscorpion32 result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 277 + sort_order: 1 +- id: 292 + owner_id: 7 + owner_name: org7 + lower_name: group 22 + name: group 22 + description: | + Orwellian son this no steak pride oops. Him without orchard whatever either does since. Yours park today this who to above. Why our string by enormously sometimes quarterly. Out then a her this collapse that. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FancyOrange + ''' + + \#\# Usage + '''python + result = fancyorange.run("lighthearted command") + print("fancyorange result\:", "terminated") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 279 + sort_order: 3 +- id: 293 + owner_id: 7 + owner_name: org7 + lower_name: group 23 + name: group 23 + description: | + Why Kazakh management day this here shall. Him board to nightly oops where most. Without school Bangladeshi his nightly Mexican always. His huh some without one consequently tonight. Light Honduran everyone lastly Icelandic gleaming car. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install GrumpyFox7 + ''' + + \#\# Usage + '''javascript + const result = grumpyfox7.process("lighthearted command"); + console.log("grumpyfox7 result\:", "unknown"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 280 + sort_order: 1 +- id: 294 + owner_id: 7 + owner_name: org7 + lower_name: group 24 + name: group 24 + description: | + Anything along would preen kneel consequently its. From tomorrow might hen gee next chest. Listen himself what here thing finally those. Fortnightly why flock gossip every due her. Fortnightly Congolese eat it usually perfectly (space). + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/TalentedOx46/EnchantedToad49 + ''' + + \#\# Usage + '''go + result \:= EnchantedToad49.process("lighthearted command") + fmt.Println("enchantedtoad49 result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 275 + sort_order: 3 +- id: 295 + owner_id: 7 + owner_name: org7 + lower_name: group 25 + name: group 25 + description: | + Shake bundle next due all bunch earlier. Some then everything in gee then Laotian. Weekly hand aha handsome from why throughout. Afghan at whoever it become of host. But Brazilian who panic on anybody army. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AvocadoAngry70/ClockListener + ''' + + \#\# Usage + '''go + result \:= ClockListener.perform("quirky message") + fmt.Println("clocklistener result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 288 + sort_order: 1 +- id: 296 + owner_id: 7 + owner_name: org7 + lower_name: group 26 + name: group 26 + description: | + Conclude deeply do their all whomever line. Abundant me hurriedly Sri-Lankan whatever band account. Garlic always strike juice work itself myself. The which afterwards to hey group mouse. So over then such daily how hourly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/IllFrog/WatermelonTasty87 + ''' + + \#\# Usage + '''go + result \:= WatermelonTasty87.perform("lighthearted command") + fmt.Println("watermelontasty87 result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 273 + sort_order: 2 +- id: 297 + owner_id: 7 + owner_name: org7 + lower_name: group 27 + name: group 27 + description: | + Fruit bravo from tomorrow Torontonian bunch gracefully. Hiccup softly you instance lamp whoever lie. Couple consequently Gabonese host have they throw. It including us empty did all each. Yay every them hair apartment somebody yourselves. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ParfumeTurner28 + ''' + + \#\# Usage + '''python + result = parfumeturner28.process("lighthearted command") + print("parfumeturner28 result\:", "failed") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 274 + sort_order: 2 +- id: 298 + owner_id: 7 + owner_name: org7 + lower_name: group 28 + name: group 28 + description: | + What case congregation rather sedge fact sufficient. How inadequately troubling petrify Jungian last aha. Healthily these open whatever would so brightly. To shall troop this want you am. Army party either usually do from a. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/HappyMammoth96/ZooWriteer27 + ''' + + \#\# Usage + '''go + result \:= ZooWriteer27.execute("playful alert") + fmt.Println("zoowriteer27 result\:", "error") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 295 + sort_order: 1 +- id: 299 + owner_id: 7 + owner_name: org7 + lower_name: group 29 + name: group 29 + description: | + Huh chocolate hardly yesterday why I inside. Us foolishly listen racism conclude besides be. Those you no vase due it instead. That herself though being any anything where. Next lately whose thing by write beyond. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ShyArchitect + ''' + + \#\# Usage + '''javascript + const result = shyarchitect.handle("quirky message"); + console.log("shyarchitect result\:", "failed"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 289 + sort_order: 1 +- id: 300 + owner_id: 7 + owner_name: org7 + lower_name: group 30 + name: group 30 + description: | + Wings above about ouch luck include kid. Herself hospitality to what yourself cruel us. Be what why those trend ill which. Stand one Turkishish her book theirs none. This east run however fact each Confucian. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install WhiteLion7 + ''' + + \#\# Usage + '''javascript + const result = whitelion7.process("quirky message"); + console.log("whitelion7 result\:", "finished"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 291 + sort_order: 1 +- id: 301 + owner_id: 17 + owner_name: org17 + lower_name: group 1 + name: group 1 + description: | + Everybody moreover collapse consequently with itself towards. Great yikes Viennese toothpaste below shower formerly. Beyond out it all bread out off. Child annually whose who whichever murder which. Outstanding frequently anything everyone later their inquisitively. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install JerseyEater + ''' + + \#\# Usage + '''javascript + const result = jerseyeater.handle("funny request"); + console.log("jerseyeater result\:", "finished"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 38 +- id: 302 + owner_id: 17 + owner_name: org17 + lower_name: group 2 + name: group 2 + description: | + All been Freudian yourself fact yesterday whom. The motor from whose on seldom anywhere. Battery orange whose besides daughter hourly exaltation. My theirs than here we repel hers. Abroad jealousy us crowd posse most back. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/TiredWheelchair/LimeUninterested + ''' + + \#\# Usage + '''go + result \:= LimeUninterested.run("playful alert") + fmt.Println("limeuninterested result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 39 +- id: 303 + owner_id: 17 + owner_name: org17 + lower_name: group 3 + name: group 3 + description: | + Had company me some your others close. Kindness lots bale who wildly there themselves. Her bale whom with yours just next. Deliberately from nearby dark kindness being you. That scold hmm including army been toss. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PanickedBed998 + ''' + + \#\# Usage + '''python + result = panickedbed998.execute("funny request") + print("panickedbed998 result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 301 + sort_order: 1 +- id: 304 + owner_id: 17 + owner_name: org17 + lower_name: group 4 + name: group 4 + description: | + Many upon everyone still onto should town. Monthly joy insufficient hey hundreds bread bear. Near little from electricity these himself under. Me there ouch whomever am Greek yourself. Stemmed nurse till on hers every comb. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install InexpensiveGoat + ''' + + \#\# Usage + '''javascript + const result = inexpensivegoat.process("playful alert"); + console.log("inexpensivegoat result\:", "finished"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 303 + sort_order: 1 +- id: 305 + owner_id: 17 + owner_name: org17 + lower_name: group 5 + name: group 5 + description: | + Gun most covey anywhere of also Thai. Comb outside stove win weekly whose of. Himself book were being up up few. Thing lastly it it wade occasionally by. Yours hand then did carefully in that. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/RestaurantDreamer/GentleGasStation12 + ''' + + \#\# Usage + '''go + result \:= GentleGasStation12.handle("quirky message") + fmt.Println("gentlegasstation12 result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 304 + sort_order: 1 +- id: 306 + owner_id: 17 + owner_name: org17 + lower_name: group 6 + name: group 6 + description: | + Hers then how by that whichever assistance. Backwards that herself late inquisitively teach red. I no no whichever in wash so. Now instance it that finally hiccup inquisitively. From one of hostel that why you. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PearOutrageous7 + ''' + + \#\# Usage + '''python + result = pearoutrageous7.execute("playful alert") + print("pearoutrageous7 result\:", "error") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 301 + sort_order: 2 +- id: 307 + owner_id: 17 + owner_name: org17 + lower_name: group 7 + name: group 7 + description: | + These finally in freedom secondly pouch whose. With loudly herself towards frequently behind whose. Party that other stack patiently shiny it. Her thoughtfully late group constantly cry some. Fleet every cloud grammar what place onto. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LegumeSelfish + ''' + + \#\# Usage + '''python + result = legumeselfish.handle("lighthearted command") + print("legumeselfish result\:", "finished") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 304 + sort_order: 2 +- id: 308 + owner_id: 17 + owner_name: org17 + lower_name: group 8 + name: group 8 + description: | + Sunglasses it where someone himself lots eek. Way any weekly snore consequently do whose. Of lady goodness frantically it that company. That from smell live which until ever. Daily straightaway so hey towards lemon still. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SillyWaiter + ''' + + \#\# Usage + '''python + result = sillywaiter.perform("playful alert") + print("sillywaiter result\:", "terminated") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 306 + sort_order: 1 +- id: 309 + owner_id: 17 + owner_name: org17 + lower_name: group 9 + name: group 9 + description: | + My upon up tonight most cheese those. She additionally without fortnightly other catalog my. Day we yet being brush ourselves lots. Toothpaste hammer between point include their pain. E.g. over who any under me Bahrainean. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/KiwiGrieving/EmbarrassedOyster + ''' + + \#\# Usage + '''go + result \:= EmbarrassedOyster.execute("funny request") + fmt.Println("embarrassedoyster result\:", "unknown") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 308 + sort_order: 1 +- id: 310 + owner_id: 17 + owner_name: org17 + lower_name: group 10 + name: group 10 + description: | + Heavily oops e.g. nightly they what you. Might week will it line nevertheless bus. Embarrass warmth fortnightly cackle result between ours. Truth since be whichever next last team. Weekly this each monthly a they barely. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ProudHound + ''' + + \#\# Usage + '''python + result = proudhound.perform("lighthearted command") + print("proudhound result\:", "failed") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 301 + sort_order: 3 +- id: 311 + owner_id: 17 + owner_name: org17 + lower_name: group 11 + name: group 11 + description: | + Daringly him whoa snore our till sprint. How theirs hug these ourselves freedom recently. Sprint weight hers before could as that. Inside lingering might east it which should. Than one there ship am flock being. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install TalentedBall + ''' + + \#\# Usage + '''python + result = talentedball.handle("lighthearted command") + print("talentedball result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 306 + sort_order: 2 +- id: 312 + owner_id: 17 + owner_name: org17 + lower_name: group 12 + name: group 12 + description: | + Throw stand entirely tame before frog hundred. My conclude it herself over ouch nature. Just these anywhere month my Newtonian one. Quite where enormously Tibetan yours depend as. Board accordingly yesterday which quarterly do lead. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/CookerEater/PineappleNaughty + ''' + + \#\# Usage + '''go + result \:= PineappleNaughty.execute("lighthearted command") + fmt.Println("pineapplenaughty result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 40 +- id: 313 + owner_id: 17 + owner_name: org17 + lower_name: group 13 + name: group 13 + description: | + Mine accordingly ours recently here exaltation cry. Then eye some was does fiction inadequately. Too normally thing youth where laugh powerfully. Whose frequently that whose whom inquiring orange. These yesterday tighten its cautious that bouquet. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/StarCuter/DurianStupid + ''' + + \#\# Usage + '''go + result \:= DurianStupid.handle("funny request") + fmt.Println("durianstupid result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 309 + sort_order: 1 +- id: 314 + owner_id: 17 + owner_name: org17 + lower_name: group 14 + name: group 14 + description: | + Yesterday his then behind it that hmm. We yet for up why time your. Read half moment why this slavery regularly. Hers lag upon that what quarterly anyone. He its shock accordingly cleverness watch turn. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install RaisinWhite + ''' + + \#\# Usage + '''python + result = raisinwhite.run("whimsical story") + print("raisinwhite result\:", "unknown") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 306 + sort_order: 3 +- id: 315 + owner_id: 17 + owner_name: org17 + lower_name: group 15 + name: group 15 + description: | + Whom listen its not must scold tonight. Ouch work along above in today why. His disturbed finally huge church any were. It have by stand himself government everything. Finally lay she it emerge his could. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GuavaHelpful + ''' + + \#\# Usage + '''python + result = guavahelpful.perform("quirky message") + print("guavahelpful result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 307 + sort_order: 1 +- id: 316 + owner_id: 17 + owner_name: org17 + lower_name: group 16 + name: group 16 + description: | + Of then crowd that ever which eventually. Doctor hourly between precious moreover will why. Hmm ever in of i.e. am why. Which many company neither tender flock secondly. Loudly absolutely sedge how his for Buddhist. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BananaScenic/ClearStomach3 + ''' + + \#\# Usage + '''go + result \:= ClearStomach3.execute("playful alert") + fmt.Println("clearstomach3 result\:", "terminated") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 306 + sort_order: 4 +- id: 317 + owner_id: 17 + owner_name: org17 + lower_name: group 17 + name: group 17 + description: | + He behind yourselves what this whom we. Our bowl ours hair sufficient accidentally for. Herself mob is why secondly use nothing. Congregation those wear there from his frailty. Being upon album that to everything galaxy. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ToesListener + ''' + + \#\# Usage + '''python + result = toeslistener.run("whimsical story") + print("toeslistener result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 307 + sort_order: 2 +- id: 318 + owner_id: 17 + owner_name: org17 + lower_name: group 18 + name: group 18 + description: | + Gee today little it this accordingly how. Mine being then that staff which single. Say to often wash so band her. Anything onto hence I meeting time riches. Be few to accordingly with yesterday shall. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/YellowWombat21/ProudHyena + ''' + + \#\# Usage + '''go + result \:= ProudHyena.handle("whimsical story") + fmt.Println("proudhyena result\:", "failed") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 41 +- id: 319 + owner_id: 17 + owner_name: org17 + lower_name: group 19 + name: group 19 + description: | + Whereas someone few tomorrow too her others. Cast besides whomever yearly hourly furniture alternatively. Wow any us ouch under have sit. Little tonight together relieved me almost someone. Inside kiss respects goodness an anyone his. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/BootsDiger/ToothpasteStacker + ''' + + \#\# Usage + '''go + result \:= ToothpasteStacker.process("whimsical story") + fmt.Println("toothpastestacker result\:", "failed") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 316 + sort_order: 1 +- id: 320 + owner_id: 17 + owner_name: org17 + lower_name: group 20 + name: group 20 + description: | + Myself my late nevertheless Alpine list she. All above cut being person which has. Which whoever monthly annually been them his. Bouquet this wit bravery out by whose. Those distinguish posse down others firstly whoever. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/SunReader6/OrangeSandwich + ''' + + \#\# Usage + '''go + result \:= OrangeSandwich.run("playful alert") + fmt.Println("orangesandwich result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 315 + sort_order: 1 +- id: 321 + owner_id: 17 + owner_name: org17 + lower_name: group 21 + name: group 21 + description: | + Next these several graceful several cackle kindly. His these angry openly as then frequently. Blender it purely chest of do lastly. Strongly his her nearby such so heavily. Whereas our whom float half here also. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install SingerThinker + ''' + + \#\# Usage + '''javascript + const result = singerthinker.handle("lighthearted command"); + console.log("singerthinker result\:", "error"); + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 317 + sort_order: 1 +- id: 322 + owner_id: 17 + owner_name: org17 + lower_name: group 22 + name: group 22 + description: | + Alas group estate leg additionally year instance. Union for cookware transform your there may. This yet stemmed team week it glorious. Her which oops us annually mob themselves. Bouquet some loosely I how in whose. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CondemnedCasino326/SlippersDiger + ''' + + \#\# Usage + '''go + result \:= SlippersDiger.perform("whimsical story") + fmt.Println("slippersdiger result\:", "terminated") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 42 +- id: 323 + owner_id: 17 + owner_name: org17 + lower_name: group 23 + name: group 23 + description: | + First upon since whoa regiment anything those. You less itself sari sedge as both. Freedom costume play hedge you this turn. Me mine posse yesterday up single today. Read to secondly Burmese since stand play. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FeijoaSelfish461 + ''' + + \#\# Usage + '''python + result = feijoaselfish461.handle("lighthearted command") + print("feijoaselfish461 result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 319 + sort_order: 1 +- id: 324 + owner_id: 17 + owner_name: org17 + lower_name: group 24 + name: group 24 + description: | + Her those towards generation I which finally. Our tablet Gaussian instance do we annually. Omen work murder nightly lastly tonight even. Depend murder even therefore though which first. Here your otherwise viplate in fortnightly this. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AwfulCasino/TangerineAuspicious + ''' + + \#\# Usage + '''go + result \:= TangerineAuspicious.run("quirky message") + fmt.Println("tangerineauspicious result\:", "error") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 309 + sort_order: 2 +- id: 325 + owner_id: 17 + owner_name: org17 + lower_name: group 25 + name: group 25 + description: | + Jump beautifully maintain than these yet team. Covey out software their yell its later. Have for other massage light stand the. Certain yourselves mob infrequently steak upon into. Some another snore week Canadian would speed. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LonelyBill + ''' + + \#\# Usage + '''python + result = lonelybill.perform("playful alert") + print("lonelybill result\:", "terminated") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 324 + sort_order: 1 +- id: 326 + owner_id: 17 + owner_name: org17 + lower_name: group 26 + name: group 26 + description: | + String innocently dance that nearby ever sometimes. Union whose of little not positively unless. Each orchard all rather of doubtfully crew. In alas clump some host that tribe. Now my each numerous ability your work. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TomatoConfusing31 + ''' + + \#\# Usage + '''javascript + const result = tomatoconfusing31.perform("quirky message"); + console.log("tomatoconfusing31 result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 303 + sort_order: 2 +- id: 327 + owner_id: 17 + owner_name: org17 + lower_name: group 27 + name: group 27 + description: | + From dynasty way onto shorts next embarrass. Cluster out to instance vanish no Guyanese. Anyone what since bevy whose comfort previously. Child bakery it in somewhat secondly timing. There swim moreover cast that above today. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LegumePowerless + ''' + + \#\# Usage + '''python + result = legumepowerless.perform("funny request") + print("legumepowerless result\:", "in progress") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 324 + sort_order: 2 +- id: 328 + owner_id: 17 + owner_name: org17 + lower_name: group 28 + name: group 28 + description: | + Fortnightly whenever Afghan because where yikes been. Mine research this indeed soon work eye. Anger yikes group soon load cluster me. Ourselves wandering whoever just bunch Burmese today. Ourselves him them these describe yikes plant. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ClumsySalmon/JambulTame + ''' + + \#\# Usage + '''go + result \:= JambulTame.run("quirky message") + fmt.Println("jambultame result\:", "in progress") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 311 + sort_order: 1 +- id: 329 + owner_id: 17 + owner_name: org17 + lower_name: group 29 + name: group 29 + description: | + Did before these talk certain however since. Weekly it his vast we those one. Have architect half exuberant genetics tonight plant. Time though anyone were wad where a. How pod friendship previously instance this fact. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install FantasticTemple8 + ''' + + \#\# Usage + '''javascript + const result = fantastictemple8.process("playful alert"); + console.log("fantastictemple8 result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 307 + sort_order: 3 +- id: 330 + owner_id: 17 + owner_name: org17 + lower_name: group 30 + name: group 30 + description: | + He her dream whichever few growth with. Still nightly you importance from that tomorrow. Somebody reel infrequently key yourself roll most. Its however why upstairs Peruvian each armchair. Fact hand rather hurt bevy think whenever. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install CantaloupePrickling + ''' + + \#\# Usage + '''javascript + const result = cantaloupeprickling.execute("lighthearted command"); + console.log("cantaloupeprickling result\:", "unknown"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 321 + sort_order: 1 +- id: 331 + owner_id: 23 + owner_name: privated_org + lower_name: group 1 + name: group 1 + description: | + Repulsive fortnightly flower regiment when roll chair. Fortnightly all indeed those there patrol first. Tomorrow anthology whose those greedily nest about. Afterwards rabbit sprint how sedge those basket. Troop each his whose huh wad of. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ConfusingTelevision/MushyKoala78 + ''' + + \#\# Usage + '''go + result \:= MushyKoala78.run("playful alert") + fmt.Println("mushykoala78 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 43 +- id: 332 + owner_id: 23 + owner_name: privated_org + lower_name: group 2 + name: group 2 + description: | + Remain woman despite slavery in Norwegian squeak. Every us was black in himself everybody. For next on anyway by though divorce. Troop success within theirs turn we exaltation. Hardly yours government though rather Polynesian eyes. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BoyStander72/PinkFrog + ''' + + \#\# Usage + '''go + result \:= PinkFrog.execute("whimsical story") + fmt.Println("pinkfrog result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 331 + sort_order: 1 +- id: 333 + owner_id: 23 + owner_name: privated_org + lower_name: group 3 + name: group 3 + description: | + My anybody should happiness too Finnish lastly. Garage time staff nightly however downstairs dog. On lastly chocolate daughter tonight what might. Oops much this consist last quarterly last. School where hence this moreover mine game. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/ScenicCockroach/CleanMink + ''' + + \#\# Usage + '''go + result \:= CleanMink.handle("lighthearted command") + fmt.Println("cleanmink result\:", "error") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 0 + sort_order: 44 +- id: 334 + owner_id: 23 + owner_name: privated_org + lower_name: group 4 + name: group 4 + description: | + Wearily so tensely admit our should case. Half government inquiring due most brace the. So film this any usually this point. E.g. differs weary Einsteinian mobile why Burmese. They example clever must woman numerous my. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/EmbarrassedDog7/StrangeDolphin + ''' + + \#\# Usage + '''go + result \:= StrangeDolphin.perform("funny request") + fmt.Println("strangedolphin result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 331 + sort_order: 2 +- id: 335 + owner_id: 23 + owner_name: privated_org + lower_name: group 5 + name: group 5 + description: | + First which why under in with transportation. Yesterday hey riches in lucky upon kuban. One one you beneath since as the. This example which really nobody barely first. Pleasure cautiously these hand case what ingeniously. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install GlamorousLamp61 + ''' + + \#\# Usage + '''python + result = glamorouslamp61.handle("whimsical story") + print("glamorouslamp61 result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 332 + sort_order: 1 +- id: 336 + owner_id: 23 + owner_name: privated_org + lower_name: group 6 + name: group 6 + description: | + Were Elizabethan were you fact this rather. Line wit you themselves you Iraqi would. These Aristotelian occasion stay hence scold creepy. That being then indeed yesterday these his. Might this frequently heavy been person now. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/TiredBalloon/ImportantWeasel + ''' + + \#\# Usage + '''go + result \:= ImportantWeasel.execute("whimsical story") + fmt.Println("importantweasel result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 334 + sort_order: 1 +- id: 337 + owner_id: 23 + owner_name: privated_org + lower_name: group 7 + name: group 7 + description: | + Team her on bundle cast tonight disregard. Despite all mine the scream mustering they. Muscovite was of where all I in. Deeply person extremely beneath well itself cast. Rich instance every did stack host hourly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install WaterCooker + ''' + + \#\# Usage + '''python + result = watercooker.perform("lighthearted command") + print("watercooker result\:", "error") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 334 + sort_order: 2 +- id: 338 + owner_id: 23 + owner_name: privated_org + lower_name: group 8 + name: group 8 + description: | + Obediently Cambodian her the ever could when. Too their since panic firstly each mine. Aristotelian whichever today lastly you did her. Depending dynasty his gee first ring does. Little suitcase problem so most its because. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install EncouragingCinema + ''' + + \#\# Usage + '''javascript + const result = encouragingcinema.handle("quirky message"); + console.log("encouragingcinema result\:", "failed"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 334 + sort_order: 3 +- id: 339 + owner_id: 23 + owner_name: privated_org + lower_name: group 9 + name: group 9 + description: | + Scale these shall cackle certain for yours. Other up their which everything well that. In today firstly milk themselves strongly off. Tomorrow lean stack yourself whom that stadium. Now that me shake mob everybody vivaciously. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install KiwiGleaming + ''' + + \#\# Usage + '''python + result = kiwigleaming.process("quirky message") + print("kiwigleaming result\:", "failed") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 333 + sort_order: 1 +- id: 340 + owner_id: 23 + owner_name: privated_org + lower_name: group 10 + name: group 10 + description: | + Including wash others wave being judge wings. Far Pacific team I i.e. she as. Infrequently was management move host aha whose. Whose interrupt formerly bale throughout maintain other. Always museum honesty that oops wrap riches. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/JoyousMink/TangerineFrantic + ''' + + \#\# Usage + '''go + result \:= TangerineFrantic.process("whimsical story") + fmt.Println("tangerinefrantic result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 334 + sort_order: 4 +- id: 341 + owner_id: 23 + owner_name: privated_org + lower_name: group 11 + name: group 11 + description: | + None formerly in with that weekly Bangladeshi. Somebody her fact luck what strongly yet. Deeply bundle finally you lastly half on. Innocence first that because paralyze single those. Portuguese might someone whose sufficient instead moreover. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/PlumGrumpy/HelplessBuffalo01 + ''' + + \#\# Usage + '''go + result \:= HelplessBuffalo01.handle("playful alert") + fmt.Println("helplessbuffalo01 result\:", "unknown") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 45 +- id: 342 + owner_id: 23 + owner_name: privated_org + lower_name: group 12 + name: group 12 + description: | + Sometimes end group in point neither tomorrow. Could part Hindu east there afterwards a. Though child number what justice an accordingly. Doubtfully conclude either be my he under. Several hardly his since jump tomorrow whoa. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LimeFancy + ''' + + \#\# Usage + '''python + result = limefancy.run("playful alert") + print("limefancy result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 334 + sort_order: 5 +- id: 343 + owner_id: 23 + owner_name: privated_org + lower_name: group 13 + name: group 13 + description: | + Any cookware firstly who greatly do here. Whose whoever lastly frantically today still sedge. Virtually cast lie ouch from he gee. My each Cormoran forget hang was other. At later awfully Uzbek several happen wisely. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PitayaRepulsive4/SchoolSleeper + ''' + + \#\# Usage + '''go + result \:= SchoolSleeper.execute("quirky message") + fmt.Println("schoolsleeper result\:", "terminated") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 341 + sort_order: 1 +- id: 344 + owner_id: 23 + owner_name: privated_org + lower_name: group 14 + name: group 14 + description: | + In shirt cup enough their plain also. Here that whom whom east Bismarckian bird. Lincolnian hour each how one when huh. Tomorrow anything lastly Costa whose shake drink. Ours has finally from agree than lastly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install TerseBear + ''' + + \#\# Usage + '''javascript + const result = tersebear.execute("whimsical story"); + console.log("tersebear result\:", "success"); + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 341 + sort_order: 2 +- id: 345 + owner_id: 23 + owner_name: privated_org + lower_name: group 15 + name: group 15 + description: | + Generally their everybody outside they chest ours. Week finger library toilet eventually myself myself. Am grab government never than this hers. Its tolerance moreover these set on straw. Child luxury us either than will slide. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/TerseGiraffe/BeautifulButterfly7 + ''' + + \#\# Usage + '''go + result \:= BeautifulButterfly7.execute("playful alert") + fmt.Println("beautifulbutterfly7 result\:", "completed") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 337 + sort_order: 1 +- id: 346 + owner_id: 23 + owner_name: privated_org + lower_name: group 16 + name: group 16 + description: | + Indeed besides yay whichever yourselves herself speedily. Ourselves ourselves thing Malagasy problem whose joyously. That him after most certain many crow. Gee select last begin you under so. Then on pack firstly itself first sheep. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/IllChildren969/PoisedLizard8 + ''' + + \#\# Usage + '''go + result \:= PoisedLizard8.run("funny request") + fmt.Println("poisedlizard8 result\:", "in progress") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 344 + sort_order: 1 +- id: 347 + owner_id: 23 + owner_name: privated_org + lower_name: group 17 + name: group 17 + description: | + Whose lots till whose should shake my. Slovak inside on whose who for may. None was place many contrast regularly across. But nap it album some of team. Those ride peace now pretty team nightly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AloofLocust0/KumquatFrantic + ''' + + \#\# Usage + '''go + result \:= KumquatFrantic.process("quirky message") + fmt.Println("kumquatfrantic result\:", "completed") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 343 + sort_order: 1 +- id: 348 + owner_id: 23 + owner_name: privated_org + lower_name: group 18 + name: group 18 + description: | + Weekly must finally fully supermarket out nevertheless. Laugh including scold otherwise upon hail ever. Why our belief of which shall his. Key whose happen whose something pronunciation himself. To hospitality many wow frantically gee in. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CharmingMacaw497/CantaloupeHelpless + ''' + + \#\# Usage + '''go + result \:= CantaloupeHelpless.handle("quirky message") + fmt.Println("cantaloupehelpless result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 344 + sort_order: 2 +- id: 349 + owner_id: 23 + owner_name: privated_org + lower_name: group 19 + name: group 19 + description: | + Now these world year it perfectly lot. Box mustering themselves decidedly other lie summation. Upshot it of monthly us pod Polish. Very ours generally huh hourly annually that. That meeting week whom loss yesterday itself. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SurgeonJumper + ''' + + \#\# Usage + '''python + result = surgeonjumper.perform("lighthearted command") + print("surgeonjumper result\:", "terminated") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 341 + sort_order: 3 +- id: 350 + owner_id: 23 + owner_name: privated_org + lower_name: group 20 + name: group 20 + description: | + Pharmacy bravo Monacan bravo half then in. Through swiftly whom pray this Guyanese how. When will deceit now from are lastly. Many giraffe product can band there these. Whose toss terrible some meal retard much. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/ToothbrushCrawler4/RedcurrantJoyous780 + ''' + + \#\# Usage + '''go + result \:= RedcurrantJoyous780.execute("playful alert") + fmt.Println("redcurrantjoyous780 result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 337 + sort_order: 2 +- id: 351 + owner_id: 23 + owner_name: privated_org + lower_name: group 21 + name: group 21 + description: | + Contrast dive a voice tense yearly loudly. Towards whatever elsewhere besides Diabolical unless British. Galaxy till how little whoever of wear. None little noisily half should formerly for. You some those early as each a. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PeachScenic + ''' + + \#\# Usage + '''python + result = peachscenic.execute("whimsical story") + print("peachscenic result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 348 + sort_order: 1 +- id: 352 + owner_id: 23 + owner_name: privated_org + lower_name: group 22 + name: group 22 + description: | + Blindly galaxy accommodation will little board apartment. Without you egg why till our instead. In ouch somebody work college what certain. You which point embarrass yoga what oxygen. Where troop gladly castle eat was that. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install FruitBatheer + ''' + + \#\# Usage + '''python + result = fruitbatheer.process("lighthearted command") + print("fruitbatheer result\:", "success") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 336 + sort_order: 1 +- id: 353 + owner_id: 23 + owner_name: privated_org + lower_name: group 23 + name: group 23 + description: | + Improvised her next open promptly how trip. First whatever mistake whom tomorrow preen heap. Religion hourly each Somali ouch meeting there. Out deceive for off we she eat. Pair us retard but problem whose that. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/BananaAshamed/DateUninterested7 + ''' + + \#\# Usage + '''go + result \:= DateUninterested7.execute("lighthearted command") + fmt.Println("dateuninterested7 result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 336 + sort_order: 2 +- id: 354 + owner_id: 23 + owner_name: privated_org + lower_name: group 24 + name: group 24 + description: | + Such timing whose to angrily it fortnightly. A yet unexpectedly e.g. bathe light where. Hence wow how alas frailty any tomorrow. Watch had her what Lebanese violin however. Whose army tribe example attractive man then. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install MangoProud + ''' + + \#\# Usage + '''javascript + const result = mangoproud.perform("playful alert"); + console.log("mangoproud result\:", "terminated"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 341 + sort_order: 4 +- id: 355 + owner_id: 23 + owner_name: privated_org + lower_name: group 25 + name: group 25 + description: | + Whom some an pain sleep down generally. You shiny oops myself slap think every. Sparse desktop provided tasty punctuation you Burkinese. From eventually been a wit occasionally mob. Conclude congregation sit what anything fact of. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/SpottedHerring8/FeijoaThankful9 + ''' + + \#\# Usage + '''go + result \:= FeijoaThankful9.perform("playful alert") + fmt.Println("feijoathankful9 result\:", "finished") + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 354 + sort_order: 1 +- id: 356 + owner_id: 23 + owner_name: privated_org + lower_name: group 26 + name: group 26 + description: | + Gently tribe nobody up yay otherwise onto. Perfectly under apart company enough down within. That you aha strongly too in her. Besides fly patience her why hers body. Today fortnightly furthermore whose i.e. daily formerly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install OrangeDefiant + ''' + + \#\# Usage + '''python + result = orangedefiant.handle("quirky message") + print("orangedefiant result\:", "success") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 353 + sort_order: 1 +- id: 357 + owner_id: 23 + owner_name: privated_org + lower_name: group 27 + name: group 27 + description: | + Alternatively are ourselves husband firstly until we. From peep do additionally repulsive hers team. Keep correctly yesterday how i.e. tomorrow her. Justice nice handle ride religion to now. Cat positively tomorrow might mine owing quarterly. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install JealousShorts + ''' + + \#\# Usage + '''python + result = jealousshorts.handle("whimsical story") + print("jealousshorts result\:", "terminated") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 350 + sort_order: 1 +- id: 358 + owner_id: 23 + owner_name: privated_org + lower_name: group 28 + name: group 28 + description: | + I it why being above obesity phew. Open both lastly any these Mayan before. Whomever him motionless because then ever how. Those distinguish Canadian include every monthly yearly. Swim itself whom your here this out. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CheerfulHerring/SuperSparrow + ''' + + \#\# Usage + '''go + result \:= SuperSparrow.handle("whimsical story") + fmt.Println("supersparrow result\:", "completed") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 351 + sort_order: 1 +- id: 359 + owner_id: 23 + owner_name: privated_org + lower_name: group 29 + name: group 29 + description: | + To hourly rush who off many embarrass. Wallet another but look whose there packet. My group of couple conclude she circumstances. All whereas beyond yearly throughout you quarterly. Enough tomorrow someone accordingly why result many. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install LemonBower + ''' + + \#\# Usage + '''python + result = lemonbower.process("funny request") + print("lemonbower result\:", "unknown") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 339 + sort_order: 1 +- id: 360 + owner_id: 23 + owner_name: privated_org + lower_name: group 30 + name: group 30 + description: | + Painfully his quarterly parrot mustering man few. Before quite why taste but her where. Happiness fact its timing hastily my its. Ourselves madly everything heavily though our how. Over you jump still ever Caesarian Turkmen. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install SpoonCuter5 + ''' + + \#\# Usage + '''python + result = spooncuter5.process("funny request") + print("spooncuter5 result\:", "unknown") + ''' + + \#\# License + GPL-3.0 + + visibility: 2 + avatar: "" + parent_group_id: 338 + sort_order: 1 +- id: 361 + owner_id: 35 + owner_name: private_org35 + lower_name: group 1 + name: group 1 + description: | + Covey couple what upon nobody neck bundle. Shakespearean as yikes therefore politely all today. We chair artist itself rather finally several. Scary whomever philosophy weight one light quantity. Do politely these spit nightly that to. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ApartmentBatheer + ''' + + \#\# Usage + '''python + result = apartmentbatheer.execute("whimsical story") + print("apartmentbatheer result\:", "error") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 0 + sort_order: 46 +- id: 362 + owner_id: 35 + owner_name: private_org35 + lower_name: group 2 + name: group 2 + description: | + It has than Tibetan for class can. Which city nearby any ours they calmly. Anybody some where party nest fact onto. Slide when monthly secondly straightaway Sammarinese oops. Should most any by group must our. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BowCloseer/MagnificentSuit + ''' + + \#\# Usage + '''go + result \:= MagnificentSuit.perform("whimsical story") + fmt.Println("magnificentsuit result\:", "in progress") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 361 + sort_order: 1 +- id: 363 + owner_id: 35 + owner_name: private_org35 + lower_name: group 3 + name: group 3 + description: | + Rather what his secondly tax rather of. Troop barely do neither of first then. Does now yesterday religion consequently whoa bevy. How bulb shark theirs ours smoggy whose. Wide next ours courage woman above who. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install MotionlessOcean26 + ''' + + \#\# Usage + '''javascript + const result = motionlessocean26.process("lighthearted command"); + console.log("motionlessocean26 result\:", "finished"); + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 362 + sort_order: 1 +- id: 364 + owner_id: 35 + owner_name: private_org35 + lower_name: group 4 + name: group 4 + description: | + Exactly obesity she respond luggage up over. She yet your yours battery refill case. Wad that yet pretty each underwear myself. Your though wade off baby poison should. Me beautifully hers weep repel do happiness. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install ConcerningHyena587 + ''' + + \#\# Usage + '''javascript + const result = concerninghyena587.perform("lighthearted command"); + console.log("concerninghyena587 result\:", "failed"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 0 + avatar: "" + parent_group_id: 362 + sort_order: 2 +- id: 365 + owner_id: 35 + owner_name: private_org35 + lower_name: group 5 + name: group 5 + description: | + For then himself catalog dynasty book constantly. Tomorrow everyone indeed what what my quiver. Wisp horrible Hindu delightful something yay were. Somebody after it there nothing this alternatively. Band at theirs firstly it quarterly backwards. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/CourageousYellowjacket/CheerfulRaven + ''' + + \#\# Usage + '''go + result \:= CheerfulRaven.run("quirky message") + fmt.Println("cheerfulraven result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 363 + sort_order: 1 +- id: 366 + owner_id: 35 + owner_name: private_org35 + lower_name: group 6 + name: group 6 + description: | + Eventually early crawl company some meanwhile host. Whichever tennis nap shake world Uzbek are. Above his under these anyone rarely off. Out which dream them sit bow grains. Accordingly what dream lastly Turkmen problem somebody. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/BeautifulSardine/TroublingTown0 + ''' + + \#\# Usage + '''go + result \:= TroublingTown0.execute("whimsical story") + fmt.Println("troublingtown0 result\:", "in progress") + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 361 + sort_order: 2 +- id: 367 + owner_id: 35 + owner_name: private_org35 + lower_name: group 7 + name: group 7 + description: | + Across Hitlerian might ours such today look. His fortnightly when in whose man into. Us off dream nevertheless capture those a. Not themselves phew with always its which. Nevertheless you were e.g. horde would whatever. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BoredMole38 + ''' + + \#\# Usage + '''javascript + const result = boredmole38.run("funny request"); + console.log("boredmole38 result\:", "completed"); + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 362 + sort_order: 3 +- id: 368 + owner_id: 35 + owner_name: private_org35 + lower_name: group 8 + name: group 8 + description: | + Few body month none whom herself as. Wandering mine before lazy its weekly any. Her off heavily example account anger have. Right how anybody bowl to least result. Up several whose dizzying later incredibly barely. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/PouchSmeller499/SoreSwimmingPool + ''' + + \#\# Usage + '''go + result \:= SoreSwimmingPool.handle("quirky message") + fmt.Println("soreswimmingpool result\:", "completed") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 361 + sort_order: 3 +- id: 369 + owner_id: 35 + owner_name: private_org35 + lower_name: group 9 + name: group 9 + description: | + My far instead quite quarterly despite nightly. Certain as can muster many over of. Catalog perfectly clean every whomever justice anything. Snarl indeed yet neck brightly besides annually. Him shake wake yourself including remain downstairs. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install PumpkinStander + ''' + + \#\# Usage + '''python + result = pumpkinstander.perform("funny request") + print("pumpkinstander result\:", "error") + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 361 + sort_order: 4 +- id: 370 + owner_id: 35 + owner_name: private_org35 + lower_name: group 10 + name: group 10 + description: | + Consequently who time annually upon early you. Patience remain had must solemnly whose woman. Dark place where from daily baby she. Tonight infrequently which run castle rhythm equally. Yet utterly its be frequently indeed had. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/DisturbedPlatypus/QueerWaterBuffalo + ''' + + \#\# Usage + '''go + result \:= QueerWaterBuffalo.perform("whimsical story") + fmt.Println("queerwaterbuffalo result\:", "in progress") + ''' + + \#\# License + MIT + + visibility: 0 + avatar: "" + parent_group_id: 363 + sort_order: 2 +- id: 371 + owner_id: 35 + owner_name: private_org35 + lower_name: group 11 + name: group 11 + description: | + Here barely his her what year intensely. Understimate tomorrow him joyously Viennese furthermore several. Sternly back neither toss here hundreds for. Interrupt bank from yet accordingly lots myself. Fall earlier define finally tonight where now. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/GrapefruitScary/FilthyCockroach + ''' + + \#\# Usage + '''go + result \:= FilthyCockroach.process("quirky message") + fmt.Println("filthycockroach result\:", "failed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 368 + sort_order: 1 +- id: 372 + owner_id: 35 + owner_name: private_org35 + lower_name: group 12 + name: group 12 + description: | + Near swiftly down with yesterday evidence corner. Closely some might Portuguese fondly now since. Still above sprint whose did Malagasy later. This for theirs but equipment raise peep. Finally troop finally mustering remind for none. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/AircraftCrawler/SenatorDreamer + ''' + + \#\# Usage + '''go + result \:= SenatorDreamer.process("funny request") + fmt.Println("senatordreamer result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 0 + avatar: "" + parent_group_id: 0 + sort_order: 47 +- id: 373 + owner_id: 35 + owner_name: private_org35 + lower_name: group 13 + name: group 13 + description: | + Elsewhere there theirs garage frequently in so. Do black them gee boots himself ball. There throughout murder itself tickle patience almost. Those failure at who his carry point. Chastise e.g. scold I whose every electricity. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ThoughtfulGloves + ''' + + \#\# Usage + '''python + result = thoughtfulgloves.run("quirky message") + print("thoughtfulgloves result\:", "success") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 361 + sort_order: 5 +- id: 374 + owner_id: 35 + owner_name: private_org35 + lower_name: group 14 + name: group 14 + description: | + With without that these then who daringly. Tensely early moreover to crawl theirs wildly. How fuel pose cackle each those hmm. Ourselves how secondly leggings who I eat. Next none also summation why whose scold. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/RelievedKid/GrumpyRaven + ''' + + \#\# Usage + '''go + result \:= GrumpyRaven.execute("lighthearted command") + fmt.Println("grumpyraven result\:", "terminated") + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 369 + sort_order: 1 +- id: 375 + owner_id: 35 + owner_name: private_org35 + lower_name: group 15 + name: group 15 + description: | + Deceive whereas afterwards any tightly accordingly annually. Thing instance yikes with water its of. Practically my what time as what all. I her some love our e.g. muster. However queer anyone depending any will her. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get bitbucket.org/GrapefruitCalm/BlueberryIll + ''' + + \#\# Usage + '''go + result \:= BlueberryIll.perform("playful alert") + fmt.Println("blueberryill result\:", "finished") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 370 + sort_order: 1 +- id: 376 + owner_id: 35 + owner_name: private_org35 + lower_name: group 16 + name: group 16 + description: | + Fortnightly up today before hundred this range. Energy your advertising nobody what intimidate why. Yesterday consist first grandmother whichever how be. Yet the where whole ouch her greatly. Mob our luck yesterday usually preen those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/SenatorOpener/CantaloupeTired + ''' + + \#\# Usage + '''go + result \:= CantaloupeTired.handle("lighthearted command") + fmt.Println("cantaloupetired result\:", "finished") + ''' + + \#\# License + MIT + + visibility: 1 + avatar: "" + parent_group_id: 372 + sort_order: 1 +- id: 377 + owner_id: 35 + owner_name: private_org35 + lower_name: group 17 + name: group 17 + description: | + Dangerous of British into nobody neither play. Anyone other it several what will constantly. Earlier across pounce our soon class must. Accordingly mine thing Bahrainean heavy whoever union. Team who were have hurt everyone tomorrow. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install InnocentMuskrat161 + ''' + + \#\# Usage + '''javascript + const result = innocentmuskrat161.process("playful alert"); + console.log("innocentmuskrat161 result\:", "in progress"); + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 362 + sort_order: 4 +- id: 378 + owner_id: 35 + owner_name: private_org35 + lower_name: group 18 + name: group 18 + description: | + This it sometimes their nobody Laotian its. These formerly later without its all loneliness. Anything consequently what down join hey himself. Point which those east yours in Einsteinian. Comfort not teacher nevertheless might what antlers. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BrightMink71 + ''' + + \#\# Usage + '''javascript + const result = brightmink71.perform("funny request"); + console.log("brightmink71 result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 1 + avatar: "" + parent_group_id: 367 + sort_order: 1 +- id: 379 + owner_id: 35 + owner_name: private_org35 + lower_name: group 19 + name: group 19 + description: | + It did childhood wisp ours wisdom hourly. As these otherwise then pack Iraqi their. Sew generally to bed define occasionally she. Earlier hiccup damage warmly Elizabethan now upstairs. Fall have another vision those this almost. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/TenderSlippers/JewelryHuger103 + ''' + + \#\# Usage + '''go + result \:= JewelryHuger103.perform("lighthearted command") + fmt.Println("jewelryhuger103 result\:", "error") + ''' + + \#\# License + Apache 2.0 + + visibility: 1 + avatar: "" + parent_group_id: 376 + sort_order: 1 +- id: 380 + owner_id: 35 + owner_name: private_org35 + lower_name: group 20 + name: group 20 + description: | + Anyway everyone car recently are consist Rican. Ever so was her open besides whichever. You herself terrible do whose clump whoa. Under healthy how phew straightaway seafood consequently. Other Norwegian other you gloves life including. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/DetectiveOpener/UglyChinchilla + ''' + + \#\# Usage + '''go + result \:= UglyChinchilla.perform("whimsical story") + fmt.Println("uglychinchilla result\:", "unknown") + ''' + + \#\# License + Apache 2.0 + + visibility: 2 + avatar: "" + parent_group_id: 363 + sort_order: 3 +- id: 381 + owner_id: 35 + owner_name: private_org35 + lower_name: group 21 + name: group 21 + description: | + Stand whom either a innocent neither being. Whose is occasionally may sometimes inside so. Antarctic recently finally sedge him ever must. Which hotel weekly i.e. line us work. Today line over let itself tonight soon. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install WearyPorpoise + ''' + + \#\# Usage + '''javascript + const result = wearyporpoise.execute("lighthearted command"); + console.log("wearyporpoise result\:", "unknown"); + ''' + + \#\# License + GPL-3.0 + + visibility: 0 + avatar: "" + parent_group_id: 370 + sort_order: 2 +- id: 382 + owner_id: 35 + owner_name: private_org35 + lower_name: group 22 + name: group 22 + description: | + Her machine fortnightly hand shall many failure. Car anyone down thing away elsewhere where. Many to fight irritate yoga pod terribly. Finally snore now instance envy this everybody. Where yet shall any specify next its. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install DistinctCrocodile + ''' + + \#\# Usage + '''python + result = distinctcrocodile.handle("funny request") + print("distinctcrocodile result\:", "completed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 1 + avatar: "" + parent_group_id: 368 + sort_order: 2 +- id: 383 + owner_id: 35 + owner_name: private_org35 + lower_name: group 23 + name: group 23 + description: | + Accordingly place now garage its wait to. Usage of kindness example crime herself upon. Crawl head wave frail whom entirely thing. What number i.e. its woman a path. Anyone yourself disregard each because lastly give. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/EmbarrassedReindeer/WearySinger + ''' + + \#\# Usage + '''go + result \:= WearySinger.run("quirky message") + fmt.Println("wearysinger result\:", "completed") + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 376 + sort_order: 2 +- id: 384 + owner_id: 35 + owner_name: private_org35 + lower_name: group 24 + name: group 24 + description: | + Will earlier one near class idea me. Caesarian anything kiss secondly Machiavellian under including. Greatly whose accordingly onto these hug soon. Does occur the were constantly bunch tonight. Those all later others within did spin. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/LovelyHouse3/RockMelonFrail + ''' + + \#\# Usage + '''go + result \:= RockMelonFrail.perform("funny request") + fmt.Println("rockmelonfrail result\:", "success") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 366 + sort_order: 1 +- id: 385 + owner_id: 35 + owner_name: private_org35 + lower_name: group 25 + name: group 25 + description: | + Tomorrow how example upon hers choir across. Mob outside neither tonight e.g. anyone occasionally. Cackle accordingly what army monthly its ours. What niche theirs wealth heap beneath through. Hail nightly back which bunch its he. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''bash + pip install ZealousConditioner + ''' + + \#\# Usage + '''python + result = zealousconditioner.handle("quirky message") + print("zealousconditioner result\:", "error") + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 365 + sort_order: 1 +- id: 386 + owner_id: 35 + owner_name: private_org35 + lower_name: group 26 + name: group 26 + description: | + Most shoulder fast whatever huh summation battery. Soon which those happiness whose whose those. Far before work about pod us much. Rainbow early pad child there begin hers. Man time it I must crime those. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install PhysalisWicked2 + ''' + + \#\# Usage + '''javascript + const result = physaliswicked2.process("funny request"); + console.log("physaliswicked2 result\:", "finished"); + ''' + + \#\# License + ISC + + visibility: 0 + avatar: "" + parent_group_id: 375 + sort_order: 1 +- id: 387 + owner_id: 35 + owner_name: private_org35 + lower_name: group 27 + name: group 27 + description: | + How band heavy rarely tasty less without. Talk besides other nervously infrequently as Colombian. From finally one palm that completely then. Huh this when relent yearly party hourly. Through weekly straightaway perfectly whom live lead. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install DistinctHound + ''' + + \#\# Usage + '''javascript + const result = distincthound.run("funny request"); + console.log("distincthound result\:", "success"); + ''' + + \#\# License + BSD-3-Clause + + visibility: 2 + avatar: "" + parent_group_id: 386 + sort_order: 1 +- id: 388 + owner_id: 35 + owner_name: private_org35 + lower_name: group 28 + name: group 28 + description: | + To are cluster week with angry that. Yesterday hmm slavery company first on infrequently. Tomorrow host odd company nightly off theirs. Neither as our those we over snarl. Bunch eye ourselves seriously besides slavery there. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get github.com/ModernGnu/JewelryLaugher + ''' + + \#\# Usage + '''go + result \:= JewelryLaugher.run("funny request") + fmt.Println("jewelrylaugher result\:", "unknown") + ''' + + \#\# License + MIT + + visibility: 2 + avatar: "" + parent_group_id: 369 + sort_order: 2 +- id: 389 + owner_id: 35 + owner_name: private_org35 + lower_name: group 29 + name: group 29 + description: | + Scenic just in one who still that. Whom ouch however Machiavellian lie nobody that. Any ring huh himself to these fragile. E.g. rarely due themselves in rice day. One enough her e.g. gift daringly finally. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''js + npm install BlushingCookware05 + ''' + + \#\# Usage + '''javascript + const result = blushingcookware05.perform("lighthearted command"); + console.log("blushingcookware05 result\:", "failed"); + ''' + + \#\# License + ISC + + visibility: 2 + avatar: "" + parent_group_id: 374 + sort_order: 1 +- id: 390 + owner_id: 35 + owner_name: private_org35 + lower_name: group 30 + name: group 30 + description: | + Differs has spin that does to lower. For thing their according murder there line. This of piano yikes imagination but finally. In under example bundle panicked bridge onto. That down few under earlier party would. + + \#\# Table of Contents + - [Installation](\#installation) + - [Usage](\#usage) + - [License](\#license) + + \#\# Installation + '''go + go get gitlab.com/GloriousMallard/BilberryBlack85 + ''' + + \#\# Usage + '''go + result \:= BilberryBlack85.process("funny request") + fmt.Println("bilberryblack85 result\:", "failed") + ''' + + \#\# License + GPL-3.0 + + visibility: 1 + avatar: "" + parent_group_id: 361 + sort_order: 6 diff --git a/models/fixtures/repo_group_team.yml b/models/fixtures/repo_group_team.yml new file mode 100644 index 0000000000000..803529a474a7f --- /dev/null +++ b/models/fixtures/repo_group_team.yml @@ -0,0 +1,150 @@ +- id: 1 + org_id: 25 + team_id: 10 + group_id: 12 + access_mode: 1 + can_create_in: true +- id: 2 + org_id: 25 + team_id: 23 + group_id: 12 + access_mode: 4 + can_create_in: true +- id: 3 + org_id: 26 + team_id: 11 + group_id: 41 + access_mode: 1 + can_create_in: false +- id: 4 + org_id: 41 + team_id: 21 + group_id: 88 + access_mode: 3 + can_create_in: true +- id: 5 + org_id: 41 + team_id: 22 + group_id: 88 + access_mode: 1 + can_create_in: true +- id: 6 + org_id: 3 + team_id: 1 + group_id: 148 + access_mode: 4 + can_create_in: true +- id: 7 + org_id: 3 + team_id: 2 + group_id: 148 + access_mode: 1 + can_create_in: false +- id: 8 + org_id: 3 + team_id: 7 + group_id: 148 + access_mode: 1 + can_create_in: false +- id: 9 + org_id: 3 + team_id: 12 + group_id: 148 + access_mode: 1 + can_create_in: false +- id: 10 + org_id: 3 + team_id: 14 + group_id: 148 + access_mode: 1 + can_create_in: true +- id: 11 + org_id: 6 + team_id: 3 + group_id: 179 + access_mode: 4 + can_create_in: true +- id: 12 + org_id: 6 + team_id: 13 + group_id: 179 + access_mode: 3 + can_create_in: false +- id: 13 + org_id: 19 + team_id: 6 + group_id: 203 + access_mode: 3 + can_create_in: false +- id: 14 + org_id: 22 + team_id: 15 + group_id: 239 + access_mode: 2 + can_create_in: false +- id: 15 + org_id: 36 + team_id: 19 + group_id: 241 + access_mode: 4 + can_create_in: false +- id: 16 + org_id: 36 + team_id: 20 + group_id: 241 + access_mode: 1 + can_create_in: true +- id: 17 + org_id: 7 + team_id: 4 + group_id: 280 + access_mode: 1 + can_create_in: false +- id: 18 + org_id: 17 + team_id: 5 + group_id: 312 + access_mode: 3 + can_create_in: true +- id: 19 + org_id: 17 + team_id: 8 + group_id: 312 + access_mode: 1 + can_create_in: false +- id: 20 + org_id: 17 + team_id: 9 + group_id: 312 + access_mode: 1 + can_create_in: true +- id: 21 + org_id: 23 + team_id: 16 + group_id: 360 + access_mode: 3 + can_create_in: false +- id: 22 + org_id: 23 + team_id: 17 + group_id: 360 + access_mode: 1 + can_create_in: false +- id: 23 + org_id: 35 + team_id: 18 + group_id: 376 + access_mode: 2 + can_create_in: false +- id: 24 + org_id: 35 + team_id: 24 + group_id: 376 + access_mode: 1 + can_create_in: false +- id: 25 + org_id: 3 + team_id: 12 + group_id: 123 + access_mode: 4 + can_create_in: true diff --git a/models/fixtures/repo_group_unit.yml b/models/fixtures/repo_group_unit.yml new file mode 100644 index 0000000000000..2fce9b528659d --- /dev/null +++ b/models/fixtures/repo_group_unit.yml @@ -0,0 +1,1200 @@ +- id: 1 + group_id: 12 + team_id: 10 + type: 2 + access_mode: 0 +- id: 2 + group_id: 12 + team_id: 10 + type: 7 + access_mode: 2 +- id: 3 + group_id: 12 + team_id: 10 + type: 3 + access_mode: 0 +- id: 4 + group_id: 12 + team_id: 10 + type: 4 + access_mode: 0 +- id: 5 + group_id: 12 + team_id: 10 + type: 5 + access_mode: 0 +- id: 6 + group_id: 12 + team_id: 10 + type: 1 + access_mode: 1 +- id: 7 + group_id: 12 + team_id: 10 + type: 6 + access_mode: 0 +- id: 8 + group_id: 12 + team_id: 10 + type: 8 + access_mode: 1 +- id: 9 + group_id: 12 + team_id: 10 + type: 9 + access_mode: 1 +- id: 10 + group_id: 12 + team_id: 10 + type: 10 + access_mode: 0 +- id: 11 + group_id: 12 + team_id: 23 + type: 9 + access_mode: 0 +- id: 12 + group_id: 12 + team_id: 23 + type: 10 + access_mode: 0 +- id: 13 + group_id: 12 + team_id: 23 + type: 1 + access_mode: 2 +- id: 14 + group_id: 12 + team_id: 23 + type: 6 + access_mode: 0 +- id: 15 + group_id: 12 + team_id: 23 + type: 8 + access_mode: 1 +- id: 16 + group_id: 12 + team_id: 23 + type: 4 + access_mode: 0 +- id: 17 + group_id: 12 + team_id: 23 + type: 5 + access_mode: 0 +- id: 18 + group_id: 12 + team_id: 23 + type: 2 + access_mode: 0 +- id: 19 + group_id: 12 + team_id: 23 + type: 7 + access_mode: 0 +- id: 20 + group_id: 12 + team_id: 23 + type: 3 + access_mode: 1 +- id: 21 + group_id: 41 + team_id: 11 + type: 7 + access_mode: 0 +- id: 22 + group_id: 41 + team_id: 11 + type: 3 + access_mode: 1 +- id: 23 + group_id: 41 + team_id: 11 + type: 4 + access_mode: 0 +- id: 24 + group_id: 41 + team_id: 11 + type: 5 + access_mode: 0 +- id: 25 + group_id: 41 + team_id: 11 + type: 2 + access_mode: 2 +- id: 26 + group_id: 41 + team_id: 11 + type: 6 + access_mode: 1 +- id: 27 + group_id: 41 + team_id: 11 + type: 8 + access_mode: 2 +- id: 28 + group_id: 41 + team_id: 11 + type: 9 + access_mode: 0 +- id: 29 + group_id: 41 + team_id: 11 + type: 10 + access_mode: 0 +- id: 30 + group_id: 41 + team_id: 11 + type: 1 + access_mode: 0 +- id: 31 + group_id: 88 + team_id: 21 + type: 8 + access_mode: 0 +- id: 32 + group_id: 88 + team_id: 21 + type: 9 + access_mode: 2 +- id: 33 + group_id: 88 + team_id: 21 + type: 10 + access_mode: 0 +- id: 34 + group_id: 88 + team_id: 21 + type: 1 + access_mode: 1 +- id: 35 + group_id: 88 + team_id: 21 + type: 6 + access_mode: 1 +- id: 36 + group_id: 88 + team_id: 21 + type: 3 + access_mode: 0 +- id: 37 + group_id: 88 + team_id: 21 + type: 4 + access_mode: 2 +- id: 38 + group_id: 88 + team_id: 21 + type: 5 + access_mode: 2 +- id: 39 + group_id: 88 + team_id: 21 + type: 2 + access_mode: 1 +- id: 40 + group_id: 88 + team_id: 21 + type: 7 + access_mode: 1 +- id: 41 + group_id: 88 + team_id: 22 + type: 4 + access_mode: 0 +- id: 42 + group_id: 88 + team_id: 22 + type: 5 + access_mode: 0 +- id: 43 + group_id: 88 + team_id: 22 + type: 2 + access_mode: 0 +- id: 44 + group_id: 88 + team_id: 22 + type: 7 + access_mode: 0 +- id: 45 + group_id: 88 + team_id: 22 + type: 3 + access_mode: 0 +- id: 46 + group_id: 88 + team_id: 22 + type: 9 + access_mode: 0 +- id: 47 + group_id: 88 + team_id: 22 + type: 10 + access_mode: 0 +- id: 48 + group_id: 88 + team_id: 22 + type: 1 + access_mode: 1 +- id: 49 + group_id: 88 + team_id: 22 + type: 6 + access_mode: 0 +- id: 50 + group_id: 88 + team_id: 22 + type: 8 + access_mode: 0 +- id: 51 + group_id: 148 + team_id: 1 + type: 4 + access_mode: 1 +- id: 52 + group_id: 148 + team_id: 1 + type: 5 + access_mode: 0 +- id: 53 + group_id: 148 + team_id: 1 + type: 2 + access_mode: 0 +- id: 54 + group_id: 148 + team_id: 1 + type: 7 + access_mode: 0 +- id: 55 + group_id: 148 + team_id: 1 + type: 3 + access_mode: 0 +- id: 56 + group_id: 148 + team_id: 1 + type: 9 + access_mode: 1 +- id: 57 + group_id: 148 + team_id: 1 + type: 10 + access_mode: 0 +- id: 58 + group_id: 148 + team_id: 1 + type: 1 + access_mode: 0 +- id: 59 + group_id: 148 + team_id: 1 + type: 6 + access_mode: 1 +- id: 60 + group_id: 148 + team_id: 1 + type: 8 + access_mode: 0 +- id: 61 + group_id: 148 + team_id: 2 + type: 3 + access_mode: 2 +- id: 62 + group_id: 148 + team_id: 2 + type: 4 + access_mode: 0 +- id: 63 + group_id: 148 + team_id: 2 + type: 5 + access_mode: 1 +- id: 64 + group_id: 148 + team_id: 2 + type: 2 + access_mode: 0 +- id: 65 + group_id: 148 + team_id: 2 + type: 7 + access_mode: 0 +- id: 66 + group_id: 148 + team_id: 2 + type: 8 + access_mode: 1 +- id: 67 + group_id: 148 + team_id: 2 + type: 9 + access_mode: 0 +- id: 68 + group_id: 148 + team_id: 2 + type: 10 + access_mode: 1 +- id: 69 + group_id: 148 + team_id: 2 + type: 1 + access_mode: 0 +- id: 70 + group_id: 148 + team_id: 2 + type: 6 + access_mode: 0 +- id: 71 + group_id: 148 + team_id: 7 + type: 4 + access_mode: 0 +- id: 72 + group_id: 148 + team_id: 7 + type: 5 + access_mode: 0 +- id: 73 + group_id: 148 + team_id: 7 + type: 2 + access_mode: 2 +- id: 74 + group_id: 148 + team_id: 7 + type: 7 + access_mode: 0 +- id: 75 + group_id: 148 + team_id: 7 + type: 3 + access_mode: 0 +- id: 76 + group_id: 148 + team_id: 7 + type: 9 + access_mode: 1 +- id: 77 + group_id: 148 + team_id: 7 + type: 10 + access_mode: 1 +- id: 78 + group_id: 148 + team_id: 7 + type: 1 + access_mode: 1 +- id: 79 + group_id: 148 + team_id: 7 + type: 6 + access_mode: 0 +- id: 80 + group_id: 148 + team_id: 7 + type: 8 + access_mode: 1 +- id: 81 + group_id: 148 + team_id: 12 + type: 3 + access_mode: 1 +- id: 82 + group_id: 148 + team_id: 12 + type: 4 + access_mode: 0 +- id: 83 + group_id: 148 + team_id: 12 + type: 5 + access_mode: 0 +- id: 84 + group_id: 148 + team_id: 12 + type: 2 + access_mode: 1 +- id: 85 + group_id: 148 + team_id: 12 + type: 7 + access_mode: 2 +- id: 86 + group_id: 148 + team_id: 12 + type: 8 + access_mode: 2 +- id: 87 + group_id: 148 + team_id: 12 + type: 9 + access_mode: 1 +- id: 88 + group_id: 148 + team_id: 12 + type: 10 + access_mode: 0 +- id: 89 + group_id: 148 + team_id: 12 + type: 1 + access_mode: 1 +- id: 90 + group_id: 148 + team_id: 12 + type: 6 + access_mode: 0 +- id: 91 + group_id: 148 + team_id: 14 + type: 6 + access_mode: 0 +- id: 92 + group_id: 148 + team_id: 14 + type: 8 + access_mode: 0 +- id: 93 + group_id: 148 + team_id: 14 + type: 9 + access_mode: 0 +- id: 94 + group_id: 148 + team_id: 14 + type: 10 + access_mode: 1 +- id: 95 + group_id: 148 + team_id: 14 + type: 1 + access_mode: 0 +- id: 96 + group_id: 148 + team_id: 14 + type: 7 + access_mode: 0 +- id: 97 + group_id: 148 + team_id: 14 + type: 3 + access_mode: 1 +- id: 98 + group_id: 148 + team_id: 14 + type: 4 + access_mode: 0 +- id: 99 + group_id: 148 + team_id: 14 + type: 5 + access_mode: 1 +- id: 100 + group_id: 148 + team_id: 14 + type: 2 + access_mode: 1 +- id: 101 + group_id: 179 + team_id: 3 + type: 2 + access_mode: 1 +- id: 102 + group_id: 179 + team_id: 3 + type: 7 + access_mode: 1 +- id: 103 + group_id: 179 + team_id: 3 + type: 3 + access_mode: 2 +- id: 104 + group_id: 179 + team_id: 3 + type: 4 + access_mode: 0 +- id: 105 + group_id: 179 + team_id: 3 + type: 5 + access_mode: 0 +- id: 106 + group_id: 179 + team_id: 3 + type: 1 + access_mode: 1 +- id: 107 + group_id: 179 + team_id: 3 + type: 6 + access_mode: 0 +- id: 108 + group_id: 179 + team_id: 3 + type: 8 + access_mode: 0 +- id: 109 + group_id: 179 + team_id: 3 + type: 9 + access_mode: 1 +- id: 110 + group_id: 179 + team_id: 3 + type: 10 + access_mode: 2 +- id: 111 + group_id: 179 + team_id: 13 + type: 5 + access_mode: 2 +- id: 112 + group_id: 179 + team_id: 13 + type: 2 + access_mode: 0 +- id: 113 + group_id: 179 + team_id: 13 + type: 7 + access_mode: 0 +- id: 114 + group_id: 179 + team_id: 13 + type: 3 + access_mode: 1 +- id: 115 + group_id: 179 + team_id: 13 + type: 4 + access_mode: 0 +- id: 116 + group_id: 179 + team_id: 13 + type: 10 + access_mode: 0 +- id: 117 + group_id: 179 + team_id: 13 + type: 1 + access_mode: 0 +- id: 118 + group_id: 179 + team_id: 13 + type: 6 + access_mode: 1 +- id: 119 + group_id: 179 + team_id: 13 + type: 8 + access_mode: 1 +- id: 120 + group_id: 179 + team_id: 13 + type: 9 + access_mode: 1 +- id: 121 + group_id: 203 + team_id: 6 + type: 2 + access_mode: 1 +- id: 122 + group_id: 203 + team_id: 6 + type: 7 + access_mode: 0 +- id: 123 + group_id: 203 + team_id: 6 + type: 3 + access_mode: 0 +- id: 124 + group_id: 203 + team_id: 6 + type: 4 + access_mode: 0 +- id: 125 + group_id: 203 + team_id: 6 + type: 5 + access_mode: 0 +- id: 126 + group_id: 203 + team_id: 6 + type: 1 + access_mode: 1 +- id: 127 + group_id: 203 + team_id: 6 + type: 6 + access_mode: 0 +- id: 128 + group_id: 203 + team_id: 6 + type: 8 + access_mode: 0 +- id: 129 + group_id: 203 + team_id: 6 + type: 9 + access_mode: 1 +- id: 130 + group_id: 203 + team_id: 6 + type: 10 + access_mode: 2 +- id: 131 + group_id: 239 + team_id: 15 + type: 3 + access_mode: 0 +- id: 132 + group_id: 239 + team_id: 15 + type: 4 + access_mode: 1 +- id: 133 + group_id: 239 + team_id: 15 + type: 5 + access_mode: 0 +- id: 134 + group_id: 239 + team_id: 15 + type: 2 + access_mode: 1 +- id: 135 + group_id: 239 + team_id: 15 + type: 7 + access_mode: 0 +- id: 136 + group_id: 239 + team_id: 15 + type: 8 + access_mode: 0 +- id: 137 + group_id: 239 + team_id: 15 + type: 9 + access_mode: 0 +- id: 138 + group_id: 239 + team_id: 15 + type: 10 + access_mode: 0 +- id: 139 + group_id: 239 + team_id: 15 + type: 1 + access_mode: 0 +- id: 140 + group_id: 239 + team_id: 15 + type: 6 + access_mode: 1 +- id: 141 + group_id: 241 + team_id: 19 + type: 1 + access_mode: 0 +- id: 142 + group_id: 241 + team_id: 19 + type: 6 + access_mode: 1 +- id: 143 + group_id: 241 + team_id: 19 + type: 8 + access_mode: 1 +- id: 144 + group_id: 241 + team_id: 19 + type: 9 + access_mode: 0 +- id: 145 + group_id: 241 + team_id: 19 + type: 10 + access_mode: 0 +- id: 146 + group_id: 241 + team_id: 19 + type: 2 + access_mode: 0 +- id: 147 + group_id: 241 + team_id: 19 + type: 7 + access_mode: 0 +- id: 148 + group_id: 241 + team_id: 19 + type: 3 + access_mode: 2 +- id: 149 + group_id: 241 + team_id: 19 + type: 4 + access_mode: 0 +- id: 150 + group_id: 241 + team_id: 19 + type: 5 + access_mode: 0 +- id: 151 + group_id: 241 + team_id: 20 + type: 9 + access_mode: 0 +- id: 152 + group_id: 241 + team_id: 20 + type: 10 + access_mode: 0 +- id: 153 + group_id: 241 + team_id: 20 + type: 1 + access_mode: 0 +- id: 154 + group_id: 241 + team_id: 20 + type: 6 + access_mode: 0 +- id: 155 + group_id: 241 + team_id: 20 + type: 8 + access_mode: 1 +- id: 156 + group_id: 241 + team_id: 20 + type: 4 + access_mode: 0 +- id: 157 + group_id: 241 + team_id: 20 + type: 5 + access_mode: 0 +- id: 158 + group_id: 241 + team_id: 20 + type: 2 + access_mode: 2 +- id: 159 + group_id: 241 + team_id: 20 + type: 7 + access_mode: 0 +- id: 160 + group_id: 241 + team_id: 20 + type: 3 + access_mode: 0 +- id: 161 + group_id: 280 + team_id: 4 + type: 9 + access_mode: 1 +- id: 162 + group_id: 280 + team_id: 4 + type: 10 + access_mode: 0 +- id: 163 + group_id: 280 + team_id: 4 + type: 1 + access_mode: 1 +- id: 164 + group_id: 280 + team_id: 4 + type: 6 + access_mode: 1 +- id: 165 + group_id: 280 + team_id: 4 + type: 8 + access_mode: 0 +- id: 166 + group_id: 280 + team_id: 4 + type: 4 + access_mode: 1 +- id: 167 + group_id: 280 + team_id: 4 + type: 5 + access_mode: 1 +- id: 168 + group_id: 280 + team_id: 4 + type: 2 + access_mode: 0 +- id: 169 + group_id: 280 + team_id: 4 + type: 7 + access_mode: 0 +- id: 170 + group_id: 280 + team_id: 4 + type: 3 + access_mode: 0 +- id: 171 + group_id: 312 + team_id: 5 + type: 5 + access_mode: 0 +- id: 172 + group_id: 312 + team_id: 5 + type: 2 + access_mode: 1 +- id: 173 + group_id: 312 + team_id: 5 + type: 7 + access_mode: 1 +- id: 174 + group_id: 312 + team_id: 5 + type: 3 + access_mode: 0 +- id: 175 + group_id: 312 + team_id: 5 + type: 4 + access_mode: 0 +- id: 176 + group_id: 312 + team_id: 5 + type: 10 + access_mode: 0 +- id: 177 + group_id: 312 + team_id: 5 + type: 1 + access_mode: 0 +- id: 178 + group_id: 312 + team_id: 5 + type: 6 + access_mode: 1 +- id: 179 + group_id: 312 + team_id: 5 + type: 8 + access_mode: 0 +- id: 180 + group_id: 312 + team_id: 5 + type: 9 + access_mode: 1 +- id: 181 + group_id: 312 + team_id: 8 + type: 1 + access_mode: 0 +- id: 182 + group_id: 312 + team_id: 8 + type: 6 + access_mode: 0 +- id: 183 + group_id: 312 + team_id: 8 + type: 8 + access_mode: 0 +- id: 184 + group_id: 312 + team_id: 8 + type: 9 + access_mode: 0 +- id: 185 + group_id: 312 + team_id: 8 + type: 10 + access_mode: 0 +- id: 186 + group_id: 312 + team_id: 8 + type: 2 + access_mode: 2 +- id: 187 + group_id: 312 + team_id: 8 + type: 7 + access_mode: 1 +- id: 188 + group_id: 312 + team_id: 8 + type: 3 + access_mode: 2 +- id: 189 + group_id: 312 + team_id: 8 + type: 4 + access_mode: 2 +- id: 190 + group_id: 312 + team_id: 8 + type: 5 + access_mode: 0 +- id: 191 + group_id: 312 + team_id: 9 + type: 5 + access_mode: 1 +- id: 192 + group_id: 312 + team_id: 9 + type: 2 + access_mode: 1 +- id: 193 + group_id: 312 + team_id: 9 + type: 7 + access_mode: 0 +- id: 194 + group_id: 312 + team_id: 9 + type: 3 + access_mode: 0 +- id: 195 + group_id: 312 + team_id: 9 + type: 4 + access_mode: 0 +- id: 196 + group_id: 312 + team_id: 9 + type: 10 + access_mode: 1 +- id: 197 + group_id: 312 + team_id: 9 + type: 1 + access_mode: 2 +- id: 198 + group_id: 312 + team_id: 9 + type: 6 + access_mode: 1 +- id: 199 + group_id: 312 + team_id: 9 + type: 8 + access_mode: 1 +- id: 200 + group_id: 312 + team_id: 9 + type: 9 + access_mode: 0 +- id: 201 + group_id: 360 + team_id: 16 + type: 8 + access_mode: 0 +- id: 202 + group_id: 360 + team_id: 16 + type: 9 + access_mode: 0 +- id: 203 + group_id: 360 + team_id: 16 + type: 10 + access_mode: 0 +- id: 204 + group_id: 360 + team_id: 16 + type: 1 + access_mode: 0 +- id: 205 + group_id: 360 + team_id: 16 + type: 6 + access_mode: 1 +- id: 206 + group_id: 360 + team_id: 16 + type: 3 + access_mode: 1 +- id: 207 + group_id: 360 + team_id: 16 + type: 4 + access_mode: 2 +- id: 208 + group_id: 360 + team_id: 16 + type: 5 + access_mode: 0 +- id: 209 + group_id: 360 + team_id: 16 + type: 2 + access_mode: 0 +- id: 210 + group_id: 360 + team_id: 16 + type: 7 + access_mode: 2 +- id: 211 + group_id: 360 + team_id: 17 + type: 1 + access_mode: 0 +- id: 212 + group_id: 360 + team_id: 17 + type: 6 + access_mode: 0 +- id: 213 + group_id: 360 + team_id: 17 + type: 8 + access_mode: 0 +- id: 214 + group_id: 360 + team_id: 17 + type: 9 + access_mode: 0 +- id: 215 + group_id: 360 + team_id: 17 + type: 10 + access_mode: 1 +- id: 216 + group_id: 360 + team_id: 17 + type: 2 + access_mode: 1 +- id: 217 + group_id: 360 + team_id: 17 + type: 7 + access_mode: 1 +- id: 218 + group_id: 360 + team_id: 17 + type: 3 + access_mode: 0 +- id: 219 + group_id: 360 + team_id: 17 + type: 4 + access_mode: 2 +- id: 220 + group_id: 360 + team_id: 17 + type: 5 + access_mode: 1 +- id: 221 + group_id: 376 + team_id: 18 + type: 2 + access_mode: 0 +- id: 222 + group_id: 376 + team_id: 18 + type: 7 + access_mode: 1 +- id: 223 + group_id: 376 + team_id: 18 + type: 3 + access_mode: 0 +- id: 224 + group_id: 376 + team_id: 18 + type: 4 + access_mode: 0 +- id: 225 + group_id: 376 + team_id: 18 + type: 5 + access_mode: 0 +- id: 226 + group_id: 376 + team_id: 18 + type: 1 + access_mode: 0 +- id: 227 + group_id: 376 + team_id: 18 + type: 6 + access_mode: 2 +- id: 228 + group_id: 376 + team_id: 18 + type: 8 + access_mode: 0 +- id: 229 + group_id: 376 + team_id: 18 + type: 9 + access_mode: 1 +- id: 230 + group_id: 376 + team_id: 18 + type: 10 + access_mode: 0 +- id: 231 + group_id: 376 + team_id: 24 + type: 6 + access_mode: 0 +- id: 232 + group_id: 376 + team_id: 24 + type: 8 + access_mode: 0 +- id: 233 + group_id: 376 + team_id: 24 + type: 9 + access_mode: 1 +- id: 234 + group_id: 376 + team_id: 24 + type: 10 + access_mode: 1 +- id: 235 + group_id: 376 + team_id: 24 + type: 1 + access_mode: 1 +- id: 236 + group_id: 376 + team_id: 24 + type: 7 + access_mode: 0 +- id: 237 + group_id: 376 + team_id: 24 + type: 3 + access_mode: 0 +- id: 238 + group_id: 376 + team_id: 24 + type: 4 + access_mode: 0 +- id: 239 + group_id: 376 + team_id: 24 + type: 5 + access_mode: 0 +- id: 240 + group_id: 376 + team_id: 24 + type: 2 + access_mode: 0 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 552a78cbd2773..c0dace2177256 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -1,10 +1,10 @@ -# don't forget to add fixtures in repo_unit.yml -- - id: 1 +- id: 1 owner_id: 2 owner_name: user2 lower_name: repo1 name: repo1 + description: "" + website: "" default_branch: master num_watches: 4 num_stars: 0 @@ -22,20 +22,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 2 + group_id: 0 + group_sort_order: 0 +- id: 2 owner_id: 2 owner_name: user2 lower_name: repo2 name: repo2 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 1 @@ -53,20 +55,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: true - -- - id: 3 + group_id: 0 + group_sort_order: 0 +- id: 3 owner_id: 3 owner_name: org3 lower_name: repo3 name: repo3 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -84,20 +88,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 4 + group_id: 129 + group_sort_order: 1 +- id: 4 owner_id: 5 owner_name: user5 lower_name: repo4 name: repo4 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 1 @@ -115,20 +121,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 5 + group_id: 0 + group_sort_order: 0 +- id: 5 owner_id: 3 owner_name: org3 lower_name: repo5 name: repo5 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -145,20 +154,23 @@ is_archived: false is_mirror: true status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 6 + group_id: 139 + group_sort_order: 1 +- id: 6 owner_id: 10 owner_name: user10 lower_name: repo6 name: repo6 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -175,20 +187,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 7 + group_id: 0 + group_sort_order: 0 +- id: 7 owner_id: 10 owner_name: user10 lower_name: repo7 name: repo7 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -205,20 +220,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 8 + group_id: 0 + group_sort_order: 0 +- id: 8 owner_id: 10 owner_name: user10 lower_name: repo8 name: repo8 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -235,20 +253,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 9 + group_id: 0 + group_sort_order: 0 +- id: 9 owner_id: 11 owner_name: user11 lower_name: repo9 name: repo9 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -265,20 +286,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 10 + group_id: 0 + group_sort_order: 0 +- id: 10 owner_id: 12 owner_name: user12 lower_name: repo10 name: repo10 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -296,20 +319,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 11 + group_id: 0 + group_sort_order: 0 +- id: 11 owner_id: 13 owner_name: user13 lower_name: repo11 name: repo11 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -327,20 +352,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: true fork_id: 10 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 12 + group_id: 0 + group_sort_order: 0 +- id: 12 owner_id: 14 owner_name: user14 lower_name: test_repo_12 name: test_repo_12 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -357,20 +385,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 13 + group_id: 0 + group_sort_order: 0 +- id: 13 owner_id: 14 owner_name: user14 lower_name: test_repo_13 name: test_repo_13 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -387,21 +418,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 14 + group_id: 0 + group_sort_order: 0 +- id: 14 owner_id: 14 owner_name: user14 lower_name: test_repo_14 name: test_repo_14 description: test_description_14 + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -418,20 +451,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 15 + group_id: 0 + group_sort_order: 0 +- id: 15 owner_id: 2 owner_name: user2 lower_name: repo15 name: repo15 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -449,20 +484,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 16 + group_id: 0 + group_sort_order: 0 +- id: 16 owner_id: 2 owner_name: user2 lower_name: repo16 name: repo16 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -480,20 +517,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 17 + group_id: 0 + group_sort_order: 0 +- id: 17 owner_id: 15 owner_name: user15 lower_name: big_test_public_1 name: big_test_public_1 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -510,20 +550,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 18 + group_id: 0 + group_sort_order: 0 +- id: 18 owner_id: 15 owner_name: user15 lower_name: big_test_public_2 name: big_test_public_2 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -540,20 +583,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 19 + group_id: 0 + group_sort_order: 0 +- id: 19 owner_id: 15 owner_name: user15 lower_name: big_test_private_1 name: big_test_private_1 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -570,20 +616,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 20 + group_id: 0 + group_sort_order: 0 +- id: 20 owner_id: 15 owner_name: user15 lower_name: big_test_private_2 name: big_test_private_2 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -600,20 +649,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 21 + group_id: 0 + group_sort_order: 0 +- id: 21 owner_id: 16 owner_name: user16 lower_name: big_test_public_3 name: big_test_public_3 + description: "" + website: "" + default_branch: "" num_watches: 1 num_stars: 1 num_forks: 0 @@ -630,20 +682,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 22 + group_id: 0 + group_sort_order: 0 +- id: 22 owner_id: 16 owner_name: user16 lower_name: big_test_private_3 name: big_test_private_3 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -660,20 +715,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 23 + group_id: 0 + group_sort_order: 0 +- id: 23 owner_id: 17 owner_name: org17 lower_name: big_test_public_4 name: big_test_public_4 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -690,20 +748,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 24 + group_id: 313 + group_sort_order: 1 +- id: 24 owner_id: 17 owner_name: org17 lower_name: big_test_private_4 name: big_test_private_4 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -720,20 +781,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 25 + group_id: 318 + group_sort_order: 1 +- id: 25 owner_id: 20 owner_name: user20 lower_name: big_test_public_mirror_5 name: big_test_public_mirror_5 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -750,20 +814,23 @@ is_archived: false is_mirror: true status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 26 + group_id: 0 + group_sort_order: 0 +- id: 26 owner_id: 20 owner_name: user20 lower_name: big_test_private_mirror_5 name: big_test_private_mirror_5 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -780,20 +847,23 @@ is_archived: false is_mirror: true status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 27 + group_id: 0 + group_sort_order: 0 +- id: 27 owner_id: 19 owner_name: org19 lower_name: big_test_public_mirror_6 name: big_test_public_mirror_6 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 1 @@ -810,20 +880,23 @@ is_archived: false is_mirror: true status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 28 + group_id: 201 + group_sort_order: 1 +- id: 28 owner_id: 19 owner_name: org19 lower_name: big_test_private_mirror_6 name: big_test_private_mirror_6 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 1 @@ -840,20 +913,23 @@ is_archived: false is_mirror: true status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 29 + group_id: 188 + group_sort_order: 1 +- id: 29 owner_id: 20 owner_name: user20 lower_name: big_test_public_fork_7 name: big_test_public_fork_7 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -870,20 +946,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: true fork_id: 27 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 30 + group_id: 0 + group_sort_order: 0 +- id: 30 owner_id: 20 owner_name: user20 lower_name: big_test_private_fork_7 name: big_test_private_fork_7 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -900,20 +979,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: true fork_id: 28 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 31 + group_id: 0 + group_sort_order: 0 +- id: 31 owner_id: 2 owner_name: user2 lower_name: repo20 name: repo20 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -931,20 +1012,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 32 # org public repo + group_id: 0 + group_sort_order: 0 +- id: 32 owner_id: 3 owner_name: org3 lower_name: repo21 name: repo21 + description: "" + website: "" + default_branch: "" num_watches: 1 num_stars: 1 num_forks: 0 @@ -961,20 +1045,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 33 + group_id: 144 + group_sort_order: 1 +- id: 33 owner_id: 2 owner_name: user2 lower_name: utf8 name: utf8 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -992,20 +1078,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 34 + group_id: 0 + group_sort_order: 0 +- id: 34 owner_id: 21 owner_name: user21 lower_name: golang name: golang + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -1022,20 +1111,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 35 + group_id: 0 + group_sort_order: 0 +- id: 35 owner_id: 21 owner_name: user21 lower_name: graphql name: graphql + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -1052,20 +1144,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 36 + group_id: 0 + group_sort_order: 0 +- id: 36 owner_id: 2 owner_name: user2 lower_name: commits_search_test name: commits_search_test + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1083,20 +1177,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 37 + group_id: 0 + group_sort_order: 0 +- id: 37 owner_id: 2 owner_name: user2 lower_name: git_hooks_test name: git_hooks_test + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1114,20 +1210,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 38 + group_id: 0 + group_sort_order: 0 +- id: 38 owner_id: 22 owner_name: limited_org lower_name: public_repo_on_limited_org name: public_repo_on_limited_org + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1145,20 +1243,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 39 + group_id: 231 + group_sort_order: 1 +- id: 39 owner_id: 22 owner_name: limited_org lower_name: private_repo_on_limited_org name: private_repo_on_limited_org + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1176,20 +1276,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 40 + group_id: 221 + group_sort_order: 1 +- id: 40 owner_id: 23 owner_name: privated_org lower_name: public_repo_on_private_org name: public_repo_on_private_org + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1207,20 +1309,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 41 + group_id: 340 + group_sort_order: 1 +- id: 41 owner_id: 23 owner_name: privated_org lower_name: private_repo_on_private_org name: private_repo_on_private_org + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1238,20 +1342,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 42 + group_id: 352 + group_sort_order: 1 +- id: 42 owner_id: 2 owner_name: user2 lower_name: glob name: glob + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1269,20 +1375,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 43 + group_id: 0 + group_sort_order: 0 +- id: 43 owner_id: 26 owner_name: org26 lower_name: repo26 name: repo26 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -1299,20 +1408,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 44 + group_id: 55 + group_sort_order: 1 +- id: 44 owner_id: 27 owner_name: user27 lower_name: template1 name: template1 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1330,20 +1441,23 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: true template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 45 + group_id: 0 + group_sort_order: 0 +- id: 45 owner_id: 27 owner_name: user27 lower_name: template2 name: template2 + description: "" + website: "" + default_branch: "" num_watches: 0 num_stars: 0 num_forks: 0 @@ -1360,20 +1474,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: true template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 46 + group_id: 0 + group_sort_order: 0 +- id: 46 owner_id: 26 owner_name: org26 lower_name: repo_external_tracker name: repo_external_tracker + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1391,20 +1507,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 47 + group_id: 49 + group_sort_order: 1 +- id: 47 owner_id: 26 owner_name: org26 lower_name: repo_external_tracker_numeric name: repo_external_tracker_numeric + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1422,20 +1540,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 48 + group_id: 53 + group_sort_order: 1 +- id: 48 owner_id: 26 owner_name: org26 lower_name: repo_external_tracker_alpha name: repo_external_tracker_alpha + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1453,20 +1573,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 49 + group_id: 41 + group_sort_order: 1 +- id: 49 owner_id: 27 owner_name: user27 lower_name: repo49 name: repo49 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1484,20 +1606,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 50 + group_id: 0 + group_sort_order: 0 +- id: 50 owner_id: 30 owner_name: user30 lower_name: repo50 name: repo50 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1515,20 +1639,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 51 + group_id: 0 + group_sort_order: 0 +- id: 51 owner_id: 30 owner_name: user30 lower_name: repo51 name: repo51 + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1546,20 +1672,22 @@ is_archived: true is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 52 + group_id: 0 + group_sort_order: 0 +- id: 52 owner_id: 30 owner_name: user30 lower_name: empty name: empty + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1577,98 +1705,187 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 53 + group_id: 0 + group_sort_order: 0 +- id: 53 owner_id: 30 owner_name: user30 lower_name: renderer name: renderer + description: "" + website: "" default_branch: master - is_archived: false - is_empty: false - is_private: false + num_watches: 0 + num_stars: 0 + num_forks: 0 num_issues: 0 num_closed_issues: 0 num_pulls: 0 num_closed_pulls: 0 num_milestones: 0 num_closed_milestones: 0 - num_watches: 0 num_projects: 0 num_closed_projects: 0 + is_private: false + is_empty: false + is_archived: false + is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 54 + group_id: 0 + group_sort_order: 0 +- id: 54 owner_id: 2 owner_name: user2 lower_name: lfs name: lfs + description: "" + website: "" default_branch: master + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: true is_empty: false is_archived: false - is_private: true + is_mirror: false status: 0 - -- - id: 55 + is_fsck_enabled: false + is_fork: false + fork_id: 0 + is_template: false + template_id: 0 + size: 0 + close_issues_via_commit_in_any_branch: false + group_id: 0 + group_sort_order: 0 +- id: 55 owner_id: 2 owner_name: user2 lower_name: scoped_label name: scoped_label + description: "" + website: "" + default_branch: "" + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 1 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: true is_empty: false is_archived: false - is_private: true - num_issues: 1 + is_mirror: false status: 0 - -- - id: 56 + is_fsck_enabled: false + is_fork: false + fork_id: 0 + is_template: false + template_id: 0 + size: 0 + close_issues_via_commit_in_any_branch: false + group_id: 0 + group_sort_order: 0 +- id: 56 owner_id: 2 owner_name: user2 lower_name: readme-test name: readme-test + description: "" + website: "" default_branch: master + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: true is_empty: false is_archived: false - is_private: true + is_mirror: false status: 0 - num_issues: 0 - -- - id: 57 + is_fsck_enabled: false + is_fork: false + fork_id: 0 + is_template: false + template_id: 0 + size: 0 + close_issues_via_commit_in_any_branch: false + group_id: 0 + group_sort_order: 0 +- id: 57 owner_id: 2 owner_name: user2 lower_name: repo-release name: repo-release + description: "" + website: "" default_branch: main + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: false is_empty: false is_archived: false - is_private: false + is_mirror: false status: 0 - num_issues: 0 - -- - id: 58 # org public repo + is_fsck_enabled: false + is_fork: false + fork_id: 0 + is_template: false + template_id: 0 + size: 0 + close_issues_via_commit_in_any_branch: false + group_id: 0 + group_sort_order: 0 +- id: 58 owner_id: 2 owner_name: user2 lower_name: commitsonpr name: commitsonpr + description: "" + website: "" default_branch: main num_watches: 0 num_stars: 0 @@ -1686,20 +1903,55 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true + is_fork: false + fork_id: 0 + is_template: false + template_id: 0 + size: 0 + close_issues_via_commit_in_any_branch: false + group_id: 0 + group_sort_order: 0 +- id: 59 + owner_id: 2 + owner_name: user2 + lower_name: test_commit_revert + name: test_commit_revert + description: "" + website: "" + default_branch: main + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: true + is_empty: false + is_archived: false + is_mirror: false + status: 0 + is_fsck_enabled: false is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 60 + group_id: 0 + group_sort_order: 0 +- id: 60 owner_id: 40 owner_name: user40 lower_name: repo60 name: repo60 + description: "" + website: "" default_branch: main num_watches: 0 num_stars: 0 @@ -1717,20 +1969,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 61 + group_id: 0 + group_sort_order: 0 +- id: 61 owner_id: 41 owner_name: org41 lower_name: repo61 name: repo61 + description: "" + website: "" default_branch: main num_watches: 0 num_stars: 0 @@ -1748,20 +2002,22 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false - -- - id: 62 + group_id: 90 + group_sort_order: 1 +- id: 62 owner_id: 42 owner_name: org42 lower_name: search-by-path name: search-by-path + description: "" + website: "" default_branch: master num_watches: 0 num_stars: 0 @@ -1779,10 +2035,12 @@ is_archived: false is_mirror: false status: 0 + is_fsck_enabled: true is_fork: false fork_id: 0 is_template: false template_id: 0 size: 0 - is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + group_id: 106 + group_sort_order: 1 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 976a236011cc9..b3bece5589c9b 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -67,7 +67,7 @@ num_followers: 2 num_following: 1 num_stars: 2 - num_repos: 14 + num_repos: 15 num_teams: 0 num_members: 0 visibility: 0 diff --git a/models/group/avatar.go b/models/group/avatar.go new file mode 100644 index 0000000000000..04f507279c30d --- /dev/null +++ b/models/group/avatar.go @@ -0,0 +1,41 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + "net/url" + + "code.gitea.io/gitea/models/avatars" + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/setting" +) + +func (g *Group) CustomAvatarRelativePath() string { + return g.Avatar +} + +func (g *Group) relAvatarLink() string { + // If no avatar - path is empty + avatarPath := g.CustomAvatarRelativePath() + if len(avatarPath) == 0 { + return "" + } + return setting.AppSubURL + "/group-avatars/" + url.PathEscape(g.Avatar) +} + +func (g *Group) AvatarLink(ctx context.Context) string { + relLink := g.relAvatarLink() + if relLink != "" { + return httplib.MakeAbsoluteURL(ctx, relLink) + } + return "" +} + +func (g *Group) AvatarLinkWithSize(size int) string { + if g.Avatar == "" { + return avatars.DefaultAvatarLink() + } + return avatars.GenerateUserAvatarImageLink(g.Avatar, size) +} diff --git a/models/group/errors.go b/models/group/errors.go new file mode 100644 index 0000000000000..812fc6ddc9979 --- /dev/null +++ b/models/group/errors.go @@ -0,0 +1,44 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "errors" + "fmt" + + "code.gitea.io/gitea/modules/util" +) + +type ErrGroupNotExist struct { + ID int64 +} + +// IsErrGroupNotExist checks if an error is a ErrCommentNotExist. +func IsErrGroupNotExist(err error) bool { + var errGroupNotExist ErrGroupNotExist + ok := errors.As(err, &errGroupNotExist) + return ok +} + +func (err ErrGroupNotExist) Error() string { + return fmt.Sprintf("group does not exist [id: %d]", err.ID) +} + +func (err ErrGroupNotExist) Unwrap() error { + return util.ErrNotExist +} + +type ErrGroupTooDeep struct { + ID int64 +} + +func IsErrGroupTooDeep(err error) bool { + var errGroupTooDeep ErrGroupTooDeep + ok := errors.As(err, &errGroupTooDeep) + return ok +} + +func (err ErrGroupTooDeep) Error() string { + return fmt.Sprintf("group has reached or exceeded the subgroup nesting limit [id: %d]", err.ID) +} diff --git a/models/group/group.go b/models/group/group.go new file mode 100644 index 0000000000000..de87cdf3c0b7c --- /dev/null +++ b/models/group/group.go @@ -0,0 +1,367 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + "fmt" + "net/url" + "slices" + "strconv" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" +) + +// Group represents a group of repositories for a user or organization +type Group struct { + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"INDEX NOT NULL"` + OwnerName string + Owner *user_model.User `xorm:"-"` + LowerName string `xorm:"TEXT NOT NULL"` + Name string `xorm:"TEXT NOT NULL"` + Description string `xorm:"TEXT"` + Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"` + Avatar string `xorm:"VARCHAR(64)"` + + ParentGroupID int64 `xorm:"INDEX DEFAULT NULL"` + ParentGroup *Group `xorm:"-"` + Subgroups RepoGroupList `xorm:"-"` + + SortOrder int `xorm:"INDEX"` +} + +// GroupLink returns the link to this group +func (g *Group) GroupLink() string { + return setting.AppSubURL + "/" + url.PathEscape(g.OwnerName) + "/groups/" + strconv.FormatInt(g.ID, 10) +} + +func (g *Group) OrgGroupLink() string { + return setting.AppSubURL + "/org/" + url.PathEscape(g.OwnerName) + "/groups/" + strconv.FormatInt(g.ID, 10) +} + +func (Group) TableName() string { return "repo_group" } + +func init() { + db.RegisterModel(new(Group)) + db.RegisterModel(new(RepoGroupTeam)) + db.RegisterModel(new(RepoGroupUnit)) +} + +func (g *Group) doLoadSubgroups(ctx context.Context, recursive bool, cond builder.Cond, currentLevel int) error { + if currentLevel >= 20 { + return ErrGroupTooDeep{ + g.ID, + } + } + if g.Subgroups != nil { + return nil + } + var err error + g.Subgroups, err = FindGroupsByCond(ctx, &FindGroupsOptions{ + ParentGroupID: g.ID, + }, cond) + if err != nil { + return err + } + slices.SortStableFunc(g.Subgroups, func(a, b *Group) int { + return a.SortOrder - b.SortOrder + }) + if recursive { + for _, group := range g.Subgroups { + err = group.doLoadSubgroups(ctx, recursive, cond, currentLevel+1) + if err != nil { + return err + } + } + } + return nil +} + +func (g *Group) LoadSubgroups(ctx context.Context, recursive bool) error { + fgo := &FindGroupsOptions{ + ParentGroupID: g.ID, + } + return g.doLoadSubgroups(ctx, recursive, fgo.ToConds(), 0) +} + +func (g *Group) LoadAccessibleSubgroups(ctx context.Context, recursive bool, doer *user_model.User) error { + return g.doLoadSubgroups(ctx, recursive, AccessibleGroupCondition(doer, unit.TypeInvalid, perm.AccessModeRead), 0) +} + +func (g *Group) LoadAttributes(ctx context.Context) error { + err := g.LoadOwner(ctx) + if err != nil { + return err + } + return g.LoadParentGroup(ctx) +} + +func (g *Group) LoadParentGroup(ctx context.Context) error { + if g.ParentGroup != nil { + return nil + } + if g.ParentGroupID == 0 { + return nil + } + parentGroup, err := GetGroupByID(ctx, g.ParentGroupID) + if err != nil { + return err + } + g.ParentGroup = parentGroup + return nil +} + +func (g *Group) LoadOwner(ctx context.Context) error { + if g.Owner != nil { + return nil + } + var err error + g.Owner, err = user_model.GetUserByID(ctx, g.OwnerID) + return err +} + +func (g *Group) CanAccess(ctx context.Context, user *user_model.User) (bool, error) { + return g.CanAccessAtLevel(ctx, user, perm.AccessModeRead) +} + +func (g *Group) CanAccessAtLevel(ctx context.Context, user *user_model.User, level perm.AccessMode) (bool, error) { + return db.GetEngine(ctx).Where(AccessibleGroupCondition(user, unit.TypeInvalid, level).And(builder.Eq{"`repo_group`.id": g.ID})).Exist(&Group{}) +} + +func (g *Group) IsOwnedBy(ctx context.Context, userID int64) (bool, error) { + return db.GetEngine(ctx). + Where("team_user.uid = ?", userID). + Join("INNER", "team_user", "team_user.team_id = repo_group_team.team_id"). + And("repo_group_team.access_mode = ?", perm.AccessModeOwner). + And("repo_group_team.group_id = ?", g.ID). + Table("repo_group_team"). + Exist() +} + +func (g *Group) CanCreateIn(ctx context.Context, userID int64) (bool, error) { + return db.GetEngine(ctx). + Where("team_user.uid = ?", userID). + Join("INNER", "team_user", "team_user.team_id = repo_group_team.team_id"). + And("repo_group_team.group_id = ?", g.ID). + And("repo_group_team.can_create_in = ?", true). + Table("repo_group_team"). + Exist() +} + +func (g *Group) IsAdminOf(ctx context.Context, userID int64) (bool, error) { + return db.GetEngine(ctx). + Where("team_user.uid = ?", userID). + Join("INNER", "team_user", "team_user.team_id = repo_group_team.team_id"). + And("repo_group_team.group_id = ?", g.ID). + And("repo_group_team.access_mode >= ?", perm.AccessModeAdmin). + Table("repo_group_team"). + Exist() +} + +func (g *Group) ShortName(length int) string { + return util.EllipsisDisplayString(g.Name, length) +} + +func GetGroupByID(ctx context.Context, id int64) (*Group, error) { + group := new(Group) + + has, err := db.GetEngine(ctx).ID(id).Get(group) + if err != nil { + return nil, err + } else if !has { + return nil, ErrGroupNotExist{id} + } + return group, nil +} + +func GetGroupByRepoID(ctx context.Context, repoID int64) (*Group, error) { + group := new(Group) + _, err := db.GetEngine(ctx). + In("id", builder. + Select("group_id"). + From("repo"). + Where(builder.Eq{"id": repoID})). + Get(group) + return group, err +} + +func ParentGroupCondByRepoID(ctx context.Context, repoID int64, idStr string) builder.Cond { + g, err := GetGroupByRepoID(ctx, repoID) + if err != nil { + return builder.In(idStr) + } + return ParentGroupCond(ctx, idStr, g.ID) +} + +type FindGroupsOptions struct { + db.ListOptions + OwnerID int64 + ParentGroupID int64 + CanCreateIn optional.Option[bool] + ActorID int64 + Name string +} + +func (opts FindGroupsOptions) ToConds() builder.Cond { + cond := builder.NewCond() + if opts.OwnerID != 0 { + cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) + } + if opts.ParentGroupID > 0 { + cond = cond.And(builder.Eq{"parent_group_id": opts.ParentGroupID}) + } else if opts.ParentGroupID == 0 { + cond = cond.And(builder.Eq{"parent_group_id": 0}) + } + if opts.CanCreateIn.Has() && opts.ActorID > 0 { + cond = cond.And(builder.In("id", + builder.Select("repo_group_team.group_id"). + From("repo_group_team"). + Where(builder.Eq{"team_user.uid": opts.ActorID}). + Join("INNER", "team_user", "team_user.team_id = repo_group_team.team_id"). + And(builder.Eq{"repo_group_team.can_create_in": true}))) + } + if opts.Name != "" { + cond = cond.And(builder.Eq{"lower_name": opts.Name}) + } + return cond +} + +func FindGroups(ctx context.Context, opts *FindGroupsOptions) (RepoGroupList, error) { + sess := db.GetEngine(ctx).Where(opts.ToConds()) + if opts.Page > 0 { + sess = db.SetSessionPagination(sess, opts) + } + + groups := make([]*Group, 0, 10) + return groups, sess. + Asc("repo_group.sort_order"). + Find(&groups) +} + +func findGroupsByCond(ctx context.Context, opts *FindGroupsOptions, cond builder.Cond) db.Engine { + if opts.Page <= 0 { + opts.Page = 1 + } + + sess := db.GetEngine(ctx).Where(cond.And(opts.ToConds())) + if opts.PageSize > 0 { + sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + } + return sess.Asc("sort_order") +} + +func FindGroupsByCond(ctx context.Context, opts *FindGroupsOptions, cond builder.Cond) (RepoGroupList, error) { + defaultSize := 50 + if opts.PageSize > 0 { + defaultSize = opts.PageSize + } + sess := findGroupsByCond(ctx, opts, cond) + groups := make([]*Group, 0, defaultSize) + if err := sess.Find(&groups); err != nil { + return nil, err + } + return groups, nil +} + +func CountGroups(ctx context.Context, opts *FindGroupsOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.ToConds()).Count(new(Group)) +} + +func UpdateGroupOwnerName(ctx context.Context, oldUser, newUser string) error { + if _, err := db.GetEngine(ctx).Exec("UPDATE `repo_group` SET owner_name=? WHERE owner_name=?", newUser, oldUser); err != nil { + return fmt.Errorf("change group owner name: %w", err) + } + return nil +} + +// GetParentGroupChain returns a slice containing a group and its ancestors +func GetParentGroupChain(ctx context.Context, groupID int64) (RepoGroupList, error) { + groupList := make([]*Group, 0, 20) + currentGroupID := groupID + for { + if currentGroupID < 1 { + break + } + if len(groupList) >= 20 { + return nil, ErrGroupTooDeep{currentGroupID} + } + currentGroup, err := GetGroupByID(ctx, currentGroupID) + if err != nil { + return nil, err + } + groupList = append(groupList, currentGroup) + currentGroupID = currentGroup.ParentGroupID + } + slices.Reverse(groupList) + return groupList, nil +} + +func GetParentGroupIDChain(ctx context.Context, groupID int64) ([]int64, error) { + var ids []int64 + groupList, err := GetParentGroupChain(ctx, groupID) + if err != nil { + return nil, err + } + ids = util.SliceMap(groupList, func(g *Group) int64 { + return g.ID + }) + return ids, err +} + +// ParentGroupCond returns a condition matching a group and its ancestors +func ParentGroupCond(ctx context.Context, idStr string, groupID int64) builder.Cond { + groupList, err := GetParentGroupIDChain(ctx, groupID) + if err != nil { + log.Info("Error building group cond: %w", err) + return builder.NotIn(idStr) + } + return builder.In(idStr, groupList) +} + +func UpdateGroup(ctx context.Context, group *Group) error { + sess := db.GetEngine(ctx) + _, err := sess.Table(group.TableName()).ID(group.ID).Update(group) + return err +} + +func MoveGroup(ctx context.Context, group *Group, newParent int64, newSortOrder int) error { + sess := db.GetEngine(ctx) + ng, err := GetGroupByID(ctx, newParent) + if err != nil && !IsErrGroupNotExist(err) { + return err + } + + if ng != nil { + if ng.OwnerID != group.OwnerID { + return fmt.Errorf("group[%d]'s ownerID is not equal to new parent group[%d]'s owner ID", group.ID, ng.ID) + } + } + + group.ParentGroupID = newParent + group.SortOrder = newSortOrder + if _, err = sess.Table(group.TableName()). + ID(group.ID). + AllCols(). + Update(group); err != nil { + return err + } + if group.ParentGroup != nil && newParent != 0 { + group.ParentGroup = nil + if err = group.LoadParentGroup(ctx); err != nil { + return err + } + } + return nil +} diff --git a/models/group/group_list.go b/models/group/group_list.go new file mode 100644 index 0000000000000..e502b33be74da --- /dev/null +++ b/models/group/group_list.go @@ -0,0 +1,95 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/structs" + + "xorm.io/builder" +) + +type RepoGroupList []*Group + +func (groups RepoGroupList) LoadOwners(ctx context.Context) error { + for _, g := range groups { + if g.Owner == nil { + err := g.LoadOwner(ctx) + if err != nil { + return err + } + } + } + return nil +} + +// userOrgTeamGroupBuilder returns group ids where user's teams can access. +func userOrgTeamGroupBuilder(userID int64) *builder.Builder { + return builder.Select("`repo_group_team`.group_id"). + From("repo_group_team"). + Join("INNER", "team_user", "`team_user`.team_id = `repo_group_team`.team_id"). + Where(builder.Eq{"`team_user`.uid": userID}) +} + +// UserOrgTeamPermCond returns a condition to select ids of groups that a user can access at the level described by `level` +func UserOrgTeamPermCond(idStr string, userID int64, level perm.AccessMode) builder.Cond { + selCond := userOrgTeamGroupBuilder(userID) + selCond = selCond.InnerJoin("team", "`team`.id = `repo_group_team`.team_id"). + And(builder.Or(builder.Gte{"`team`.authorize": level}, builder.Gte{"`repo_group_team`.access_mode": level})) + return builder.In(idStr, selCond) +} + +// UserOrgTeamGroupCond returns a condition to select ids of groups that a user's team can access +func UserOrgTeamGroupCond(idStr string, userID int64) builder.Cond { + return builder.In(idStr, userOrgTeamGroupBuilder(userID)) +} + +// userOrgTeamUnitGroupCond returns a condition to select group ids where user's teams can access the special unit. +func userOrgTeamUnitGroupCond(idStr string, userID int64, unitType unit.Type) builder.Cond { + return builder.Or(builder.In( + idStr, userOrgTeamUnitGroupBuilder(userID, unitType))) +} + +// userOrgTeamUnitGroupBuilder returns group ids where user's teams can access the special unit. +func userOrgTeamUnitGroupBuilder(userID int64, unitType unit.Type) *builder.Builder { + return userOrgTeamGroupBuilder(userID). + Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). + Where(builder.Eq{"`team_unit`.`type`": unitType}). + And(builder.Gt{"`team_unit`.`access_mode`": int(perm.AccessModeNone)}) +} + +// AccessibleGroupCondition returns a condition that matches groups which a user can access via the specified unit +func AccessibleGroupCondition(user *user_model.User, unitType unit.Type, minMode perm.AccessMode) builder.Cond { + cond := builder.NewCond() + if user == nil || !user.IsRestricted || user.ID <= 0 { + orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate} + if user == nil || user.ID <= 0 { + orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited) + } + cond = cond.Or(builder.And( + builder.Eq{"`repo_group`.visibility": structs.VisibleTypePublic}, + builder.NotIn("`repo_group`.owner_id", builder.Select("id").From("`user`").Where( + builder.And( + builder.Eq{"type": user_model.UserTypeOrganization}, + builder.In("visibility", orgVisibilityLimit)), + )))) + } + if user != nil { + cond = cond.Or(UserOrgTeamPermCond("`repo_group`.id", user.ID, minMode)) + if unitType == unit.TypeInvalid { + cond = cond.Or( + UserOrgTeamGroupCond("`repo_group`.id", user.ID), + ) + } else { + cond = cond.Or( + userOrgTeamUnitGroupCond("`repo_group`.id", user.ID, unitType), + ) + } + } + return cond +} diff --git a/models/group/group_team.go b/models/group/group_team.go new file mode 100644 index 0000000000000..102e567b3be53 --- /dev/null +++ b/models/group/group_team.go @@ -0,0 +1,149 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" +) + +// RepoGroupTeam represents a relation for a team's access to a group +type RepoGroupTeam struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + GroupID int64 `xorm:"UNIQUE(s)"` + AccessMode perm.AccessMode + CanCreateIn bool + Units []*RepoGroupUnit `xorm:"-"` +} + +func (g *RepoGroupTeam) LoadGroupUnits(ctx context.Context) error { + var err error + g.Units, err = GetUnitsByGroupID(ctx, g.GroupID) + return err +} + +func (g *RepoGroupTeam) UnitAccessModeEx(ctx context.Context, tp unit.Type) (accessMode perm.AccessMode, exist bool) { + accessMode = perm.AccessModeNone + if err := g.LoadGroupUnits(ctx); err != nil { + log.Warn("Error loading units of team for group[%d] (ID: %d): %s", g.GroupID, g.TeamID, err.Error()) + return accessMode, false + } + for _, u := range g.Units { + if u.Type == tp { + accessMode = u.AccessMode + exist = true + break + } + } + return accessMode, exist +} + +// HasTeamGroup returns true if the given group belongs to a team. +func HasTeamGroup(ctx context.Context, orgID, teamID, groupID int64) bool { + has, _ := db.GetEngine(ctx). + Where("org_id=?", orgID). + And("team_id=?", teamID). + And("group_id=?", groupID). + And("access_mode >= ?", perm.AccessModeRead). + Get(new(RepoGroupTeam)) + return has +} + +// AddTeamGroup adds a group to a team +func AddTeamGroup(ctx context.Context, orgID, teamID, groupID int64, access perm.AccessMode, canCreateIn bool) error { + if access <= perm.AccessModeWrite { + canCreateIn = false + } + _, err := db.GetEngine(ctx).Insert(&RepoGroupTeam{ + OrgID: orgID, + GroupID: groupID, + TeamID: teamID, + AccessMode: access, + CanCreateIn: canCreateIn, + }) + return err +} + +func UpdateTeamGroup(ctx context.Context, orgID, teamID, groupID int64, access perm.AccessMode, canCreateIn, isNew bool) (err error) { + if access <= perm.AccessModeNone { + canCreateIn = false + } + if isNew { + err = AddTeamGroup(ctx, orgID, teamID, groupID, access, canCreateIn) + } else { + _, err = db.GetEngine(ctx). + Table("repo_group_team"). + Where("org_id=?", orgID). + And("team_id=?", teamID). + And("group_id =?", groupID). + Update(&RepoGroupTeam{ + OrgID: orgID, + TeamID: teamID, + GroupID: groupID, + AccessMode: access, + CanCreateIn: canCreateIn, + }) + } + + return err +} + +// RemoveTeamGroup removes a group from a team +func RemoveTeamGroup(ctx context.Context, orgID, teamID, groupID int64) error { + _, err := db.DeleteByBean(ctx, &RepoGroupTeam{ + TeamID: teamID, + GroupID: groupID, + OrgID: orgID, + }) + return err +} + +func FindGroupTeams(ctx context.Context, groupID int64) (gteams []*RepoGroupTeam, err error) { + return gteams, db.GetEngine(ctx). + Where("group_id=?", groupID). + Table("repo_group_team"). + Find(>eams) +} + +func FindGroupTeamByTeamID(ctx context.Context, groupID, teamID int64) (gteam *RepoGroupTeam, err error) { + gteam = new(RepoGroupTeam) + has, err := db.GetEngine(ctx). + Where("group_id=?", groupID). + And("team_id = ?", teamID). + Table("repo_group_team"). + Get(gteam) + if !has { + gteam = nil + } + return gteam, err +} + +func GetAncestorPermissions(ctx context.Context, groupID, teamID int64) (perm.AccessMode, error) { + sess := db.GetEngine(ctx) + groups, err := GetParentGroupIDChain(ctx, groupID) + if err != nil { + return perm.AccessModeNone, err + } + gteams := make([]*RepoGroupTeam, 0) + err = sess.In("group_id", groups).And("team_id = ?", teamID).Find(>eams) + if err != nil { + return perm.AccessModeNone, err + } + mapped := util.SliceMap(gteams, func(g *RepoGroupTeam) perm.AccessMode { + return g.AccessMode + }) + maxMode := max(mapped[0]) + + for _, m := range mapped[1:] { + maxMode = max(maxMode, m) + } + return maxMode, nil +} diff --git a/models/group/group_unit.go b/models/group/group_unit.go new file mode 100644 index 0000000000000..716770f85efe9 --- /dev/null +++ b/models/group/group_unit.go @@ -0,0 +1,56 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" +) + +// RepoGroupUnit describes all units of a repository group +type RepoGroupUnit struct { + ID int64 `xorm:"pk autoincr"` + GroupID int64 `xorm:"UNIQUE(s)"` + TeamID int64 `xorm:"UNIQUE(s)"` + Type unit.Type `xorm:"UNIQUE(s)"` + AccessMode perm.AccessMode +} + +func (g *RepoGroupUnit) Unit() unit.Unit { + return unit.Units[g.Type] +} + +func GetUnitsByGroupID(ctx context.Context, groupID int64) (units []*RepoGroupUnit, err error) { + return units, db.GetEngine(ctx).Where("group_id = ?", groupID).Find(&units) +} + +func GetGroupUnit(ctx context.Context, groupID, teamID int64, unitType unit.Type) (unit *RepoGroupUnit, err error) { + unit = new(RepoGroupUnit) + _, err = db.GetEngine(ctx). + Where("group_id = ?", groupID). + And("team_id = ?", teamID). + And("type = ?", unitType). + Get(unit) + return unit, err +} + +func GetMaxGroupUnit(ctx context.Context, groupID int64, unitType unit.Type) (unit *RepoGroupUnit, err error) { + units := make([]*RepoGroupUnit, 0) + err = db.GetEngine(ctx). + Where("group_id = ?", groupID). + And("type = ?", unitType). + Find(&units) + if err != nil { + return nil, err + } + for _, u := range units { + if unit == nil || u.AccessMode > unit.AccessMode { + unit = u + } + } + return unit, err +} diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go index f8495929cf98f..b379240fdc7f0 100644 --- a/models/issues/issue_xref.go +++ b/models/issues/issue_xref.go @@ -148,7 +148,7 @@ func (issue *Issue) getCrossReferences(stdCtx context.Context, ctx *crossReferen refRepo = ctx.OrigIssue.Repo } else { // Issues in other repositories - refRepo, err = repo_model.GetRepositoryByOwnerAndName(stdCtx, ref.Owner, ref.Name) + refRepo, err = repo_model.GetRepositoryByOwnerAndName(stdCtx, ref.Owner, ref.Name, ref.GroupID) if err != nil { if repo_model.IsErrRepoNotExist(err) { continue diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 4f899453b5f57..a8b814c462715 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -386,6 +386,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.24.0 ends at database version 321 newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs), + newMigration(322, "Add group_id and group_sort_order columns to repository table", v1_25.AddGroupColumnsToRepositoryTable), } return preparedMigrations } diff --git a/models/migrations/v1_25/v322.go b/models/migrations/v1_25/v322.go new file mode 100644 index 0000000000000..e7e70a9c2108e --- /dev/null +++ b/models/migrations/v1_25/v322.go @@ -0,0 +1,19 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_25 + +import "xorm.io/xorm" + +func AddGroupColumnsToRepositoryTable(x *xorm.Engine) error { + type Repository struct { + GroupID int64 `xorm:"UNIQUE(s) INDEX DEFAULT NULL"` + GroupSortOrder int + } + _, err := x.SyncWithOptions(xorm.SyncOptions{ + IgnoreConstrains: false, + IgnoreIndices: false, + }, new(Repository)) + return err + +} diff --git a/models/organization/team_group.go b/models/organization/team_group.go new file mode 100644 index 0000000000000..e81a4e0aa2361 --- /dev/null +++ b/models/organization/team_group.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package organization + +import ( + "context" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" +) + +func GetTeamsWithAccessToGroup(ctx context.Context, orgID, groupID int64, mode perm.AccessMode) ([]*Team, error) { + teams := make([]*Team, 0) + inCond := group_model.ParentGroupCond(ctx, "repo_group_team.group_id", groupID) + return teams, db.GetEngine(ctx).Distinct("team.*").Where("repo_group_team.access_mode >= ?", mode). + Join("INNER", "repo_group_team", "repo_group_team.team_id = team.id and repo_group_team.org_id = ?", orgID). + And("repo_group_team.org_id = ?", orgID). + And(inCond). + OrderBy("name"). + Find(&teams) +} + +func GetTeamsWithAccessToGroupUnit(ctx context.Context, orgID, groupID int64, mode perm.AccessMode, unitType unit.Type) ([]*Team, error) { + teams := make([]*Team, 0) + inCond := group_model.ParentGroupCond(ctx, "repo_group_team.group_id", groupID) + return teams, db.GetEngine(ctx).Where("repo_group_team.access_mode >= ?", mode). + Join("INNER", "repo_group_team", "repo_group_team.team_id = team.id"). + Join("INNER", "repo_group_unit", "repo_group_unit.team_id = team.id"). + And("repo_group_team.org_id = ?", orgID). + And(inCond). + And("repo_group_unit.type = ?", unitType). + OrderBy("name"). + Find(&teams) +} diff --git a/models/organization/team_list.go b/models/organization/team_list.go index 0274f9c5ba4cc..a7e5e850516ec 100644 --- a/models/organization/team_list.go +++ b/models/organization/team_list.go @@ -126,6 +126,17 @@ func GetUserRepoTeams(ctx context.Context, orgID, userID, repoID int64) (teams T Find(&teams) } +// GetUserGroupTeams returns teams in a group that a user has access to +func GetUserGroupTeams(ctx context.Context, groupID, userID int64) (teams TeamList, err error) { + return teams, db.GetEngine(ctx). + Where("`repo_group_team`.group_id = ?", groupID). + Join("INNER", "repo_group_team", "`repo_group_team`.team_id = `team`.id"). + Join("INNER", "team_user", "`team_user`.team_id = `team`.id"). + And("`team_user`.uid = ?", userID). + Asc("`team`.name"). + Find(&teams) +} + func GetTeamsByOrgIDs(ctx context.Context, orgIDs []int64) (TeamList, error) { teams := make([]*Team, 0, 10) return teams, db.GetEngine(ctx).Where(builder.In("org_id", orgIDs)).Find(&teams) diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 7de43ecd07c56..719f8d1dc213e 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -9,6 +9,7 @@ import ( "slices" "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" "code.gitea.io/gitea/models/organization" perm_model "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" @@ -345,14 +346,31 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use return perm, nil } } - + groupTeams, err := group_model.FindGroupTeams(ctx, repo.GroupID) + for _, team := range groupTeams { + if team.AccessMode >= perm_model.AccessModeAdmin { + perm.AccessMode = perm_model.AccessModeOwner + perm.unitsMode = nil + return perm, nil + } + } for _, u := range repo.Units { - for _, team := range teams { - unitAccessMode := minAccessMode + var found bool + for _, team := range groupTeams { if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist { - unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode) + perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode) + found = true + } + } + if !found { + for _, team := range teams { + unitAccessMode := minAccessMode + if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist { + unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode) + found = true + } + perm.unitsMode[u.Type] = unitAccessMode } - perm.unitsMode[u.Type] = unitAccessMode } } diff --git a/models/repo/org_repo.go b/models/repo/org_repo.go index 96f21ba2aca7a..ef06cd7a9487f 100644 --- a/models/repo/org_repo.go +++ b/models/repo/org_repo.go @@ -30,10 +30,14 @@ type SearchTeamRepoOptions struct { func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (RepositoryList, error) { sess := db.GetEngine(ctx) if opts.TeamID > 0 { - sess = sess.In("id", - builder.Select("repo_id"). - From("team_repo"). - Where(builder.Eq{"team_id": opts.TeamID}), + sess = sess.Where( + builder.Or( + builder.In("id", builder.Select("repo_id"). + From("team_repo"). + Where(builder.Eq{"team_id": opts.TeamID}), + ), + builder.In("id", ReposAccessibleByGroupTeamBuilder(opts.TeamID)), + ), ) } if opts.PageSize > 0 { diff --git a/models/repo/repo.go b/models/repo/repo.go index 2403b3b40bafe..4e1b1419a69a8 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -219,19 +219,30 @@ type Repository struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` + + GroupID int64 `xorm:"UNIQUE(s) INDEX DEFAULT NULL"` + GroupSortOrder int `xorm:"INDEX"` } func init() { db.RegisterModel(new(Repository)) } -func RelativePath(ownerName, repoName string) string { - return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git" +func RelativePathBaseName(ownerName, repoName string, groupID int64) string { + var groupSegment string + if groupID > 0 { + groupSegment = strconv.FormatInt(groupID, 10) + "/" + } + return strings.ToLower(ownerName) + "/" + groupSegment + strings.ToLower(repoName) +} + +func RelativePath(ownerName, repoName string, groupID int64) string { + return RelativePathBaseName(ownerName, repoName, groupID) + ".git" } // RelativePath should be an unix style path like username/reponame.git func (repo *Repository) RelativePath() string { - return RelativePath(repo.OwnerName, repo.Name) + return RelativePath(repo.OwnerName, repo.Name, repo.GroupID) } type StorageRepo string @@ -242,7 +253,7 @@ func (sr StorageRepo) RelativePath() string { } func (repo *Repository) WikiStorageRepo() StorageRepo { - return StorageRepo(strings.ToLower(repo.OwnerName) + "/" + strings.ToLower(repo.Name) + ".wiki.git") + return StorageRepo(RelativePathBaseName(repo.Name, repo.OwnerName, repo.GroupID) + ".wiki.git") } // SanitizedOriginalURL returns a sanitized OriginalURL @@ -358,7 +369,7 @@ func (repo *Repository) LoadAttributes(ctx context.Context) error { // FullName returns the repository full name func (repo *Repository) FullName() string { - return repo.OwnerName + "/" + repo.Name + return repo.OwnerName + "/" + groupSegmentWithTrailingSlash(repo.GroupID) + repo.Name } // HTMLURL returns the repository HTML URL @@ -600,18 +611,24 @@ func (repo *Repository) IsGenerated() bool { } // RepoPath returns repository path by given user and repository name. -func RepoPath(userName, repoName string) string { //revive:disable-line:exported - return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git") +func RepoPath(userName, repoName string, groupID int64) string { //revive:disable-line:exported + var joinArgs []string + joinArgs = append(joinArgs, user_model.UserPath(userName)) + if groupID > 0 { + joinArgs = append(joinArgs, strconv.FormatInt(groupID, 10)) + } + joinArgs = append(joinArgs, strings.ToLower(repoName)+".git") + return filepath.Join(joinArgs...) } // RepoPath returns the repository path func (repo *Repository) RepoPath() string { - return RepoPath(repo.OwnerName, repo.Name) + return RepoPath(repo.OwnerName, repo.Name, repo.GroupID) } // Link returns the repository relative url func (repo *Repository) Link() string { - return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) + return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + groupSegmentWithTrailingSlash(repo.GroupID) + url.PathEscape(repo.Name) } // ComposeCompareURL returns the repository comparison URL @@ -679,13 +696,25 @@ type CloneLink struct { Tea string } +func getGroupSegment(gid int64) string { + var groupSegment string + if gid > 0 { + groupSegment = fmt.Sprintf("group/%d", gid) + } + return groupSegment +} + +func groupSegmentWithTrailingSlash(gid int64) string { + return getGroupSegment(gid) + "/" +} + // ComposeHTTPSCloneURL returns HTTPS clone URL based on the given owner and repository name. -func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string { - return fmt.Sprintf("%s%s/%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), url.PathEscape(repo)) +func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string, groupID int64) string { + return fmt.Sprintf("%s%s/%s%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), groupSegmentWithTrailingSlash(groupID), url.PathEscape(repo)) } // ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name. -func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string { +func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string, groupID int64) string { sshUser := setting.SSH.User sshDomain := setting.SSH.Domain @@ -704,7 +733,7 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin // non-standard port, it must use full URI if setting.SSH.Port != 22 { sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port)) - return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) + return fmt.Sprintf("ssh://%s@%s/%s/%s%s.git", sshUser, sshHost, url.PathEscape(ownerName), groupSegmentWithTrailingSlash(groupID), url.PathEscape(repoName)) } // for standard port, it can use a shorter URI (without the port) @@ -713,31 +742,31 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets } if setting.Repository.UseCompatSSHURI { - return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) + return fmt.Sprintf("ssh://%s@%s/%s/%s%s.git", sshUser, sshHost, url.PathEscape(ownerName), groupSegmentWithTrailingSlash(groupID), url.PathEscape(repoName)) } - return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) + return fmt.Sprintf("%s@%s:%s/%s%s.git", sshUser, sshHost, url.PathEscape(ownerName), groupSegmentWithTrailingSlash(groupID), url.PathEscape(repoName)) } // ComposeTeaCloneCommand returns Tea CLI clone command based on the given owner and repository name. -func ComposeTeaCloneCommand(ctx context.Context, owner, repo string) string { - return fmt.Sprintf("tea clone %s/%s", url.PathEscape(owner), url.PathEscape(repo)) +func ComposeTeaCloneCommand(ctx context.Context, owner, repo string, groupID int64) string { + return fmt.Sprintf("tea clone %s/%s%s", url.PathEscape(owner), url.PathEscape(repo), groupSegmentWithTrailingSlash(groupID)) } -func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink { +func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string, groupID int64) *CloneLink { return &CloneLink{ - SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName), - HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName), - Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName), + SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName, groupID), + HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName, groupID), + Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName, groupID), } } // CloneLink returns clone URLs of repository. func (repo *Repository) CloneLink(ctx context.Context, doer *user_model.User) (cl *CloneLink) { - return repo.cloneLink(ctx, doer, repo.Name) + return repo.cloneLink(ctx, doer, repo.Name, repo.GroupID) } func (repo *Repository) CloneLinkGeneral(ctx context.Context) (cl *CloneLink) { - return repo.cloneLink(ctx, nil /* no doer, use a general git user */, repo.Name) + return repo.cloneLink(ctx, nil /* no doer, use a general git user */, repo.Name, repo.GroupID) } // GetOriginalURLHostname returns the hostname of a URL or the URL @@ -802,11 +831,12 @@ func (err ErrRepoNotExist) Unwrap() error { } // GetRepositoryByOwnerAndName returns the repository by given owner name and repo name -func GetRepositoryByOwnerAndName(ctx context.Context, ownerName, repoName string) (*Repository, error) { +func GetRepositoryByOwnerAndName(ctx context.Context, ownerName, repoName string, groupID int64) (*Repository, error) { var repo Repository has, err := db.GetEngine(ctx).Table("repository").Select("repository.*"). Join("INNER", "`user`", "`user`.id = repository.owner_id"). Where("repository.lower_name = ?", strings.ToLower(repoName)). + And("`repository`.group_id = ?", groupID). And("`user`.lower_name = ?", strings.ToLower(ownerName)). Get(&repo) if err != nil { @@ -818,10 +848,11 @@ func GetRepositoryByOwnerAndName(ctx context.Context, ownerName, repoName string } // GetRepositoryByName returns the repository by given name under user if exists. -func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repository, error) { +func GetRepositoryByName(ctx context.Context, ownerID, groupID int64, name string) (*Repository, error) { var repo Repository has, err := db.GetEngine(ctx). Where("`owner_id`=?", ownerID). + And("`group_id`=?", groupID). And("`lower_name`=?", strings.ToLower(name)). NoAutoCondition(). Get(&repo) @@ -839,7 +870,7 @@ func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error if err != nil || ret.OwnerName == "" { return nil, errors.New("unknown or malformed repository URL") } - return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName) + return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName, ret.GroupID) } // GetRepositoryByURLRelax also accepts an SSH clone URL without user part @@ -876,19 +907,20 @@ func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repos } // IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed. -func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { - has, err := IsRepositoryModelExist(ctx, u, repoName) +func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string, groupID int64) (bool, error) { + has, err := IsRepositoryModelExist(ctx, u, repoName, groupID) if err != nil { return false, err } - isDir, err := util.IsDir(RepoPath(u.Name, repoName)) + isDir, err := util.IsDir(RepoPath(u.Name, repoName, groupID)) return has || isDir, err } -func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { +func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string, groupID int64) (bool, error) { return db.GetEngine(ctx).Get(&Repository{ OwnerID: u.ID, LowerName: strings.ToLower(repoName), + GroupID: groupID, }) } diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index f2cdd2f284673..8c4bea55f4ed5 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -158,6 +158,7 @@ type SearchRepoOptions struct { OwnerID int64 PriorityOwnerID int64 TeamID int64 + GroupID int64 OrderBy db.SearchOrderBy Private bool // Include private repositories in results StarredByID int64 @@ -289,9 +290,9 @@ func UserCollaborationRepoCond(idStr string, userID int64) builder.Cond { ) } -// UserOrgTeamRepoCond selects repos that the given user has access to through team membership +// UserOrgTeamRepoCond selects repos that the given user has access to through team membership and/or group permissions func UserOrgTeamRepoCond(idStr string, userID int64) builder.Cond { - return builder.In(idStr, userOrgTeamRepoBuilder(userID)) + return builder.In(idStr, userOrgTeamRepoBuilder(userID), userOrgTeamRepoGroupBuilder(userID)) } // userOrgTeamRepoBuilder returns repo ids where user's teams can access. @@ -302,6 +303,12 @@ func userOrgTeamRepoBuilder(userID int64) *builder.Builder { Where(builder.Eq{"`team_user`.uid": userID}) } +// userOrgTeamRepoGroupBuilder selects repos that the given user has access to through team membership and group permissions +func userOrgTeamRepoGroupBuilder(userID int64) *builder.Builder { + return userOrgTeamRepoBuilder(userID). + Join("INNER", "repo_group_team", "`repo_group_team`.team_id=`team_repo`.team_id") +} + // userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit. func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder { return userOrgTeamRepoBuilder(userID). @@ -310,9 +317,18 @@ func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Build And(builder.Gt{"`team_unit`.`access_mode`": int(perm.AccessModeNone)}) } +func userOrgTeamUnitRepoGroupBuilder(userID int64, unitType unit.Type) *builder.Builder { + return userOrgTeamRepoGroupBuilder(userID). + Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). + Where(builder.Eq{"`team_unit`.`type`": unitType}). + And(builder.Gt{"`team_unit`.`access_mode`": int(perm.AccessModeNone)}) +} + // userOrgTeamUnitRepoCond returns a condition to select repo ids where user's teams can access the special unit. func userOrgTeamUnitRepoCond(idStr string, userID int64, unitType unit.Type) builder.Cond { - return builder.In(idStr, userOrgTeamUnitRepoBuilder(userID, unitType)) + return builder.Or(builder.In( + idStr, userOrgTeamUnitRepoBuilder(userID, unitType)), + builder.In(idStr, userOrgTeamUnitRepoGroupBuilder(userID, unitType))) } // UserOrgUnitRepoCond selects repos that the given user has access to through org and the special unit @@ -320,7 +336,18 @@ func UserOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) return builder.In(idStr, userOrgTeamUnitRepoBuilder(userID, unitType). And(builder.Eq{"`team_unit`.org_id": orgID}), - ) + userOrgTeamUnitRepoGroupBuilder(userID, unitType).And(builder.Eq{"`team_unit`.org_id": orgID})) +} + +// ReposAccessibleByGroupTeamBuilder returns repositories that are accessible by a team via group permissions +func ReposAccessibleByGroupTeamBuilder(teamID int64) *builder.Builder { + innerGroupCond := builder.Select("`repo_group`.id"). + From("repo_group"). + InnerJoin("repo_group_team", "`repo_group_team`.group_id = `repo_group`.id"). + Where(builder.Eq{"`repo_group_team`.team_id": teamID}) + return builder.Select("`repository`.id"). + From("repository"). + Where(builder.In("`repository`.group_id", innerGroupCond)) } // userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations @@ -445,6 +472,11 @@ func SearchRepositoryCondition(opts SearchRepoOptions) builder.Cond { if opts.TeamID > 0 { cond = cond.And(builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").From("team_repo").Where(builder.Eq{"`team_repo`.team_id": opts.TeamID}))) } + if opts.GroupID > 0 { + cond = cond.And(builder.Eq{"`repository`.group_id": opts.GroupID}) + } else if opts.GroupID == -1 { + cond = cond.And(builder.Lt{"`repository`.group_id": 1}) + } if opts.Keyword != "" { // separate keyword diff --git a/models/repo/transfer.go b/models/repo/transfer.go index 3fb8cb69abdaa..f611da1d237a0 100644 --- a/models/repo/transfer.go +++ b/models/repo/transfer.go @@ -254,7 +254,7 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m } // Check if new owner has repository with same name. - if has, err := IsRepositoryModelExist(ctx, newOwner, repo.Name); err != nil { + if has, err := IsRepositoryModelExist(ctx, newOwner, repo.Name, repo.GroupID); err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { return ErrRepoAlreadyExist{ diff --git a/models/repo/update.go b/models/repo/update.go index 3228ae11a4eb3..bd7b6546702b9 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -116,14 +116,14 @@ func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, na return err } - has, err := IsRepositoryModelOrDirExist(ctx, owner, name) + has, err := IsRepositoryModelOrDirExist(ctx, owner, name, 0) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { return ErrRepoAlreadyExist{owner.Name, name} } - repoPath := RepoPath(owner.Name, name) + repoPath := RepoPath(owner.Name, name, 0) isExist, err := util.IsExist(repoPath) if err != nil { log.Error("Unable to check if %s exists. Error: %v", repoPath, err) diff --git a/models/repo/wiki.go b/models/repo/wiki.go index 832e15ae0d932..ec259587e846d 100644 --- a/models/repo/wiki.go +++ b/models/repo/wiki.go @@ -5,14 +5,11 @@ package repo import ( - "context" - "fmt" - "path/filepath" - "strings" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" + "context" + "fmt" ) // ErrWikiAlreadyExist represents a "WikiAlreadyExist" kind of error. @@ -74,17 +71,17 @@ func (err ErrWikiInvalidFileName) Unwrap() error { // WikiCloneLink returns clone URLs of repository wiki. func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User) *CloneLink { - return repo.cloneLink(ctx, doer, repo.Name+".wiki") + return repo.cloneLink(ctx, doer, repo.Name+".wiki", repo.GroupID) } // WikiPath returns wiki data path by given user and repository name. -func WikiPath(userName, repoName string) string { - return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".wiki.git") +func WikiPath(userName, repoName string, groupID int64) string { + return RepoPath(userName, repoName+".wiki", groupID) } // WikiPath returns wiki data path for given repository. func (repo *Repository) WikiPath() string { - return WikiPath(repo.OwnerName, repo.Name) + return WikiPath(repo.OwnerName, repo.Name, repo.GroupID) } // HasWiki returns true if repository has wiki. diff --git a/models/shared/group/org_group.go b/models/shared/group/org_group.go new file mode 100644 index 0000000000000..5fb4fe35616fc --- /dev/null +++ b/models/shared/group/org_group.go @@ -0,0 +1,71 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + organization_model "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + + "xorm.io/builder" +) + +// FindGroupMembers finds all users who have access to a group via team membership +func FindGroupMembers(ctx context.Context, groupID int64, opts *organization_model.FindOrgMembersOpts) (user_model.UserList, error) { + cond := builder. + Select("`team_user`.uid"). + From("team_user"). + InnerJoin("org_user", "`org_user`.uid = team_user.uid"). + InnerJoin("repo_group_team", "`repo_group_team`.team_id = team_user.team_id"). + Where(builder.Eq{"`org_user`.org_id": opts.OrgID}). + And(group_model.ParentGroupCond(context.TODO(), "`repo_group_team`.group_id", groupID)) + if opts.PublicOnly() { + cond = cond.And(builder.Eq{"`org_user`.is_public": true}) + } + sess := db.GetEngine(ctx).Where(builder.In("`user`.id", cond)) + if opts.ListOptions.PageSize > 0 { + sess = db.SetSessionPagination(sess, opts) + users := make([]*user_model.User, 0, opts.ListOptions.PageSize) + return users, sess.Find(&users) + } + + var users []*user_model.User + err := sess.Find(&users) + return users, err +} + +func GetGroupTeams(ctx context.Context, groupID int64) ([]*organization_model.Team, error) { + var teams []*organization_model.Team + return teams, db.GetEngine(ctx). + Where("`repo_group_team`.group_id = ?", groupID). + Join("INNER", "repo_group_team", "`repo_group_team`.team_id = `team`.id"). + Asc("`team`.name"). + Find(&teams) +} + +func IsGroupMember(ctx context.Context, groupID int64, user *user_model.User) (bool, error) { + if user == nil { + return false, nil + } + return db.GetEngine(ctx). + Where("`repo_group_team`.group_id = ?", groupID). + Join("INNER", "repo_group_team", "`repo_group_team`.team_id = `team_user`.team_id"). + And("`team_user`.uid = ?", user.ID). + Table("team_user"). + Exist() +} + +func GetGroupRepos(ctx context.Context, groupID int64, doer *user_model.User) ([]*repo_model.Repository, error) { + sess := db.GetEngine(ctx) + repos := make([]*repo_model.Repository, 0) + return repos, sess.Table("repository"). + Where("group_id = ?", groupID). + And(builder.In("id", repo_model.AccessibleRepoIDsQuery(doer))). + OrderBy("group_sort_order"). + Find(&repos) +} diff --git a/models/user/user.go b/models/user/user.go index 9cad1cc7c9d7d..70690be99d197 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -589,6 +589,7 @@ var ( "avatar", // avatar by email hash "avatars", // user avatars by file name "repo-avatars", + "group-avatars", "captcha", "login", // oauth2 login @@ -599,6 +600,8 @@ var ( "explore", "issues", "pulls", + "groups", + "group", "milestones", "notifications", diff --git a/modules/container/filter.go b/modules/container/filter.go index 37ec7c3d56552..3e27552f1e0d6 100644 --- a/modules/container/filter.go +++ b/modules/container/filter.go @@ -19,3 +19,16 @@ func FilterSlice[E any, T comparable](s []E, include func(E) (T, bool)) []T { } return slices.Clip(filtered) } + +func DedupeBy[E any, I comparable](s []E, id func(E) I) []E { + filtered := make([]E, 0, len(s)) // slice will be clipped before returning + seen := make(map[I]bool, len(s)) + for i := range s { + itemID := id(s[i]) + if _, ok := seen[itemID]; !ok { + filtered = append(filtered, s[i]) + seen[itemID] = true + } + } + return slices.Clip(filtered) +} diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go index be9fc5f823787..87fcac4fe38eb 100644 --- a/modules/csv/csv_test.go +++ b/modules/csv/csv_test.go @@ -256,7 +256,7 @@ a,"quoted ""text"" with new lines in second column",c`, expectedText: `col1,col2,col3 -a,,c`, +a,c`, }, // case 2 - quoted text with escaped quotes in last column { @@ -276,9 +276,9 @@ a,bb,c,d,ee ,"f f" a,b,"c "" c",d,e,f`, - expectedText: `a,,c,d,,f + expectedText: `a,c,d,f a,bb,c,d,ee , -a,b,,d,e,f`, +a,b,d,e,f`, }, // case 4 - csv with pipes and quotes { @@ -391,17 +391,17 @@ f" | 4.56 | 789`, // In the previous bestScore algorithm, this would have picked comma as the delimiter, but now it should guess tab { csv: `c1 c2 c3 c4 c5 c6 -v,k,x,v ym,f,oa,qn,uqijh,n,s,wvygpo uj,kt,j,w,i,fvv,tm,f,ddt,b,mwt,e,t,teq,rd,p,a e,wfuae,t,h,q,im,ix,y h,mrlu,l,dz,ff,zi,af,emh ,gov,bmfelvb,axp,f,u,i,cni,x,z,v,sh,w,jo,,m,h -k,ohf,pgr,tde,m,s te,ek,,v,,ic,kqc,dv,w,oi,j,w,gojjr,ug,,l,j,zl g,qziq,bcajx,zfow,ka,j,re,ohbc k,nzm,qm,ts,auf th,elb,lx,l,q,e,qf asbr,z,k,y,tltobga -g,m,bu,el h,l,jwi,o,wge,fy,rure,c,g,lcxu,fxte,uns,cl,s,o,t,h,rsoy,f bq,s,uov,z,ikkhgyg,,sabs,c,hzue mc,b,,j,t,n sp,mn,,m,t,dysi,eq,pigb,rfa,z w,rfli,sg,,o,wjjjf,f,wxdzfk,x,t,p,zy,p,mg,r,l,h -e,ewbkc,nugd,jj,sf,ih,i,n,jo,b,poem,kw,q,i,x,t,e,uug,k j,xm,sch,ux,h,,fb,f,pq,,mh,,f,v,,oba,w,h,v,eiz,yzd,o,a,c,e,dhp,q a,pbef,epc,k,rdpuw,cw k,j,e,d xf,dz,sviv,w,sqnzew,t,b v,yg,f,cq,ti,g,m,ta,hm,ym,ii,hxy,p,z,r,e,ga,sfs,r,p,l,aar,w,kox,j +v,k,x,v ym,f,oa,qn,uqijh,n,s,wvygpo uj,kt,j,w,i,fvv,tm,f,ddt,b,mwt,e,t,teq,rd,p,a e,wfuae,t,h,q,im,ix,y h,mrlu,l,dz,ff,zi,af,emh ,gov,bmfelvb,axp,f,u,i,cni,x,z,v,sh,w,jo,m,h +k,ohf,pgr,tde,m,s te,ek,v,ic,kqc,dv,w,oi,j,w,gojjr,ug,l,j,zl g,qziq,bcajx,zfow,ka,j,re,ohbc k,nzm,qm,ts,auf th,elb,lx,l,q,e,qf asbr,z,k,y,tltobga +g,m,bu,el h,l,jwi,o,wge,fy,rure,c,g,lcxu,fxte,uns,cl,s,o,t,h,rsoy,f bq,s,uov,z,ikkhgyg,sabs,c,hzue mc,b,j,t,n sp,mn,m,t,dysi,eq,pigb,rfa,z w,rfli,sg,o,wjjjf,f,wxdzfk,x,t,p,zy,p,mg,r,l,h +e,ewbkc,nugd,jj,sf,ih,i,n,jo,b,poem,kw,q,i,x,t,e,uug,k j,xm,sch,ux,h,fb,f,pq,mh,f,v,oba,w,h,v,eiz,yzd,o,a,c,e,dhp,q a,pbef,epc,k,rdpuw,cw k,j,e,d xf,dz,sviv,w,sqnzew,t,b v,yg,f,cq,ti,g,m,ta,hm,ym,ii,hxy,p,z,r,e,ga,sfs,r,p,l,aar,w,kox,j l,d,v,pp,q,j,bxip,w,i,im,qa,o e,o h,w,a,a,qzj,nt,qfn,ut,fvhu,ts hu,q,g,p,q,ofpje,fsqa,frp,p,vih,j,w,k,jx, ln,th,ka,l,b,vgk,rv,hkx rj,v,y,cwm,rao,e,l,wvr,ptc,lm,yg,u,k,i,b,zk,b,gv,fls velxtnhlyuysbnlchosqlhkozkdapjaueexjwrndwb nglvnv kqiv pbshwlmcexdzipopxjyrxhvjalwp pydvipwlkkpdvbtepahskwuornbsb qwbacgq -l,y,u,bf,y,m,eals,n,cop,h,g,vs,jga,opt x,b,zwmn,hh,b,n,pdj,t,d px yn,vtd,u,y,b,ps,yo,qqnem,mxg,m,al,rd,c,k,d,q,f ilxdxa,m,y,,p,p,y,prgmg,q,n,etj,k,ns b,pl,z,jq,hk -p,gc jn,mzr,bw sb,e,r,dy,ur,wzy,r,c,n,yglr,jbdu,r,pqk,k q,d,,,p,l,euhl,dc,rwh,t,tq,z,h,p,s,t,x,fugr,h wi,zxb,jcig,o,t,k mfh,ym,h,e,p,cnvx,uv,zx,x,pq,blt,v,r,u,tr,g,g,xt -nri,p,,t,if,,y,ptlqq a,i w,ovli,um,w,f,re,k,sb,w,jy,zf i,g,p,q,mii,nr,jm,cc i,szl,k,eg,l,d ,ah,w,b,vh -,,sh,wx,mn,xm,u,d,yy,u,t,m,j,s,b ogadq,g,y,y,i,h,ln,jda,g,cz,s,rv,r,s,s,le,r, y,nu,f,nagj o,h,,adfy,o,nf,ns,gvsvnub,k,b,xyz v,h,g,ef,y,gb c,x,cw,x,go,h,t,x,cu,u,qgrqzrcmn,kq,cd,g,rejp,zcq -skxg,t,vay,d,wug,d,xg,sexc rt g,ag,mjq,fjnyji,iwa,m,ml,b,ua,b,qjxeoc be,s,sh,n,jbzxs,g,n,i,h,y,r,be,mfo,u,p cw,r,,u,zn,eg,r,yac,m,l,edkr,ha,x,g,b,c,tg,c j,ye,u,ejd,maj,ea,bm,u,iy`, +l,y,u,bf,y,m,eals,n,cop,h,g,vs,jga,opt x,b,zwmn,hh,b,n,pdj,t,d px yn,vtd,u,y,b,ps,yo,qqnem,mxg,m,al,rd,c,k,d,q,f ilxdxa,m,y,p,p,y,prgmg,q,n,etj,k,ns b,pl,z,jq,hk +p,gc jn,mzr,bw sb,e,r,dy,ur,wzy,r,c,n,yglr,jbdu,r,pqk,k q,d,,p,l,euhl,dc,rwh,t,tq,z,h,p,s,t,x,fugr,h wi,zxb,jcig,o,t,k mfh,ym,h,e,p,cnvx,uv,zx,x,pq,blt,v,r,u,tr,g,g,xt +nri,p,t,if,y,ptlqq a,i w,ovli,um,w,f,re,k,sb,w,jy,zf i,g,p,q,mii,nr,jm,cc i,szl,k,eg,l,d ,ah,w,b,vh +,sh,wx,mn,xm,u,d,yy,u,t,m,j,s,b ogadq,g,y,y,i,h,ln,jda,g,cz,s,rv,r,s,s,le,r, y,nu,f,nagj o,h,adfy,o,nf,ns,gvsvnub,k,b,xyz v,h,g,ef,y,gb c,x,cw,x,go,h,t,x,cu,u,qgrqzrcmn,kq,cd,g,rejp,zcq +skxg,t,vay,d,wug,d,xg,sexc rt g,ag,mjq,fjnyji,iwa,m,ml,b,ua,b,qjxeoc be,s,sh,n,jbzxs,g,n,i,h,y,r,be,mfo,u,p cw,r,u,zn,eg,r,yac,m,l,edkr,ha,x,g,b,c,tg,c j,ye,u,ejd,maj,ea,bm,u,iy`, expectedDelimiter: '\t', }, // case 13 - a CSV with more than 10 lines and since we only use the first 10 lines, it should still get the delimiter as semicolon diff --git a/modules/git/url/url.go b/modules/git/url/url.go index aa6fa31c5e357..d137de8f5bbcc 100644 --- a/modules/git/url/url.go +++ b/modules/git/url/url.go @@ -8,6 +8,7 @@ import ( "fmt" "net" stdurl "net/url" + "strconv" "strings" "code.gitea.io/gitea/modules/httplib" @@ -102,6 +103,7 @@ type RepositoryURL struct { // if the URL belongs to current Gitea instance, then the below fields have values OwnerName string + GroupID int64 RepoName string RemainingPath string } @@ -121,16 +123,25 @@ func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, er ret := &RepositoryURL{} ret.GitURL = parsed - fillPathParts := func(s string) { + fillPathParts := func(s string) error { s = strings.TrimPrefix(s, "/") - fields := strings.SplitN(s, "/", 3) + fields := strings.SplitN(s, "/", 4) + var pathErr error if len(fields) >= 2 { ret.OwnerName = fields[0] - ret.RepoName = strings.TrimSuffix(fields[1], ".git") - if len(fields) == 3 { + if len(fields) >= 3 { + ret.GroupID, pathErr = strconv.ParseInt(fields[1], 10, 64) + if pathErr != nil { + return pathErr + } + ret.RepoName = strings.TrimSuffix(fields[2], ".git") + ret.RemainingPath = "/" + fields[3] + } else { + ret.RepoName = strings.TrimSuffix(fields[1], ".git") ret.RemainingPath = "/" + fields[2] } } + return nil } switch parsed.URL.Scheme { @@ -138,7 +149,9 @@ func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, er if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) { return ret, nil } - fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL)) + if err = fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL)); err != nil { + return nil, err + } case "ssh", "git+ssh": domainSSH := setting.SSH.Domain domainCur := httplib.GuessCurrentHostDomain(ctx) @@ -152,7 +165,9 @@ func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, er // check whether URL domain is current domain from context domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain) if domainMatches { - fillPathParts(parsed.URL.Path) + if err = fillPathParts(parsed.URL.Path); err != nil { + return nil, err + } } } return ret, nil @@ -161,7 +176,11 @@ func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, er // MakeRepositoryWebLink generates a web link (http/https) for a git repository (by guessing sometimes) func MakeRepositoryWebLink(repoURL *RepositoryURL) string { if repoURL.OwnerName != "" { - return setting.AppSubURL + "/" + repoURL.OwnerName + "/" + repoURL.RepoName + var groupSegment string + if repoURL.GroupID > 0 { + groupSegment = strconv.FormatInt(repoURL.GroupID, 10) + "/" + } + return setting.AppSubURL + "/" + repoURL.OwnerName + "/" + groupSegment + repoURL.RepoName } // now, let's guess, for example: diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index f925ce396a321..1b54157ca99ab 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -316,7 +316,7 @@ func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int) // and tags? If elastic search has handled that? startIndex, endIndex = contentMatchIndexPos(c[0], "", "") if startIndex == -1 { - panic(fmt.Sprintf("1===%s,,,%#v,,,%s", kw, hit.Highlight, c[0])) + panic(fmt.Sprintf("1===%s,,%#v,,%s", kw, hit.Highlight, c[0])) } } else { panic(fmt.Sprintf("2===%#v", hit.Highlight)) diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go index 7fec9431b6d46..75e616975fe2c 100644 --- a/modules/issue/template/template_test.go +++ b/modules/issue/template/template_test.go @@ -662,7 +662,7 @@ body: name: Name title: Title about: About -labels: label1,label2,,label3 ,, +labels: label1,label2,label3 , ref: Ref body: - type: markdown @@ -731,7 +731,7 @@ body: name: Name title: Title about: About -labels: label1,label2,,label3 ,, +labels: label1,label2,label3 , ref: Ref --- Content diff --git a/modules/markup/html.go b/modules/markup/html.go index 51afd4be00719..985b67322fa81 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -60,10 +60,10 @@ var globalVars = sync.OnceValue(func() *globalVarsType { v.shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`) // anyHashPattern splits url containing SHA into parts - v.anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?`) + v.anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,6}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?`) // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash" - v.comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`) + v.comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,6}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`) // fullURLPattern matches full URL like "mailto:...", "https://..." and "ssh+git://..." v.fullURLPattern = regexp.MustCompile(`^[a-z][-+\w]+:`) @@ -79,13 +79,13 @@ var globalVars = sync.OnceValue(func() *globalVarsType { v.emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`) // example: https://domain/org/repo/pulls/27#hash - v.issueFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`) + v.issueFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/([\w_.-]+/)?[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`) // example: https://domain/org/repo/pulls/27/files#hash - v.filesChangedFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b`) + v.filesChangedFullPattern = regexp.MustCompile(`https?://(?:\S+/)[\w_.-]+/([\w_.-]+/)?[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b`) // codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20" - v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`) + v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+/)?([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`) // cleans: "= 12 { + opts.GroupID, _ = strconv.ParseInt(node.Data[m[4]:m[5]], 10, 64) + opts.RepoName, opts.CommitID, opts.FilePath = node.Data[m[6]:m[7]], node.Data[m[8]:m[9]], node.Data[m[10]:m[11]] + } else { + opts.RepoName, opts.CommitID, opts.FilePath = node.Data[m[4]:m[5]], node.Data[m[6]:m[7]], node.Data[m[8]:m[9]] + } + if !httplib.IsCurrentGiteaSiteURL(ctx, opts.FullURL) { return 0, 0, "", nil } diff --git a/modules/markup/html_issue.go b/modules/markup/html_issue.go index 85bec5db20c6b..dded67092cafe 100644 --- a/modules/markup/html_issue.go +++ b/modules/markup/html_issue.go @@ -22,6 +22,7 @@ import ( type RenderIssueIconTitleOptions struct { OwnerName string RepoName string + GroupID int64 LinkHref string IssueIndex int64 } diff --git a/modules/private/hook.go b/modules/private/hook.go index 215996b9b9936..85f58c15ee97c 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -82,8 +82,16 @@ type HookProcReceiveRefResult struct { HeadBranch string } -func newInternalRequestAPIForHooks(ctx context.Context, hookName, ownerName, repoName string, opts HookOptions) *httplib.Request { - reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/%s/%s/%s", hookName, url.PathEscape(ownerName), url.PathEscape(repoName)) +func genGroupSegment(groupID int64) string { + var groupSegment string + if groupID > 0 { + groupSegment = fmt.Sprintf("group/%d/", groupID) + } + return groupSegment +} + +func newInternalRequestAPIForHooks(ctx context.Context, hookName, ownerName, repoName string, groupID int64, opts HookOptions) *httplib.Request { + reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/%s/%s/%s%s", hookName, url.PathEscape(ownerName), genGroupSegment(groupID), url.PathEscape(repoName)) req := newInternalRequestAPI(ctx, reqURL, "POST", opts) // This "timeout" applies to http.Client's timeout: A Timeout of zero means no timeout. // This "timeout" was previously set to `time.Duration(60+len(opts.OldCommitIDs))` seconds, but it caused unnecessary timeout failures. @@ -93,28 +101,29 @@ func newInternalRequestAPIForHooks(ctx context.Context, hookName, ownerName, rep } // HookPreReceive check whether the provided commits are allowed -func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra { - req := newInternalRequestAPIForHooks(ctx, "pre-receive", ownerName, repoName, opts) +func HookPreReceive(ctx context.Context, ownerName, repoName string, groupID int64, opts HookOptions) ResponseExtra { + req := newInternalRequestAPIForHooks(ctx, "pre-receive", ownerName, repoName, groupID, opts) _, extra := requestJSONResp(req, &ResponseText{}) return extra } // HookPostReceive updates services and users -func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) { - req := newInternalRequestAPIForHooks(ctx, "post-receive", ownerName, repoName, opts) +func HookPostReceive(ctx context.Context, ownerName, repoName string, groupID int64, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) { + req := newInternalRequestAPIForHooks(ctx, "post-receive", ownerName, repoName, groupID, opts) return requestJSONResp(req, &HookPostReceiveResult{}) } // HookProcReceive proc-receive hook -func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) { - req := newInternalRequestAPIForHooks(ctx, "proc-receive", ownerName, repoName, opts) +func HookProcReceive(ctx context.Context, ownerName, repoName string, groupID int64, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) { + req := newInternalRequestAPIForHooks(ctx, "proc-receive", ownerName, repoName, groupID, opts) return requestJSONResp(req, &HookProcReceiveResult{}) } // SetDefaultBranch will set the default branch to the provided branch for the provided repository -func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) ResponseExtra { - reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s", +func SetDefaultBranch(ctx context.Context, ownerName, repoName string, groupID int64, branch string) ResponseExtra { + reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s%s", url.PathEscape(ownerName), + genGroupSegment(groupID), url.PathEscape(repoName), url.PathEscape(branch), ) diff --git a/modules/references/references.go b/modules/references/references.go index 592bd4cbe4483..1f9e0d8ef88b1 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -35,7 +35,7 @@ var ( issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\'|,)`) // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. org/repo#12345 - crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) + crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/(?:\d+/)?[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) // crossReferenceCommitPattern matches a string that references a commit in a different repository // e.g. go-gitea/gitea@d8a994ef, go-gitea/gitea@d8a994ef243349f321568f9e36d5c3f444b99cae (7-40 characters) crossReferenceCommitPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)@([0-9a-f]{7,64})(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) @@ -81,6 +81,7 @@ func (a XRefAction) String() string { type IssueReference struct { Index int64 Owner string + GroupID int64 Name string Action XRefAction TimeLog string @@ -93,6 +94,7 @@ type IssueReference struct { type RenderizableReference struct { Issue string Owner string + GroupID int64 Name string CommitSha string IsPull bool @@ -104,6 +106,7 @@ type RenderizableReference struct { type rawReference struct { index int64 owner string + groupID int64 name string isPull bool action XRefAction @@ -119,6 +122,7 @@ func rawToIssueReferenceList(reflist []*rawReference) []IssueReference { refarr[i] = IssueReference{ Index: r.index, Owner: r.owner, + GroupID: r.groupID, Name: r.name, Action: r.action, TimeLog: r.timeLog, @@ -545,10 +549,19 @@ func getCrossReference(content []byte, start, end int, fromLink, prOnly bool) *r } } parts := strings.Split(strings.ToLower(repo), "/") - if len(parts) != 2 { + var owner, rawGroup, name string + var gid int64 + if len(parts) > 3 { return nil } - owner, name := parts[0], parts[1] + if len(parts) == 3 { + owner, rawGroup, name = parts[0], parts[1], parts[2] + } else { + owner, name = parts[0], parts[1] + } + if rawGroup != "" { + gid, _ = strconv.ParseInt(rawGroup, 10, 64) + } if !validNamePattern.MatchString(owner) || !validNamePattern.MatchString(name) { return nil } @@ -556,6 +569,7 @@ func getCrossReference(content []byte, start, end int, fromLink, prOnly bool) *r return &rawReference{ index: index, owner: owner, + groupID: gid, name: name, action: action, issue: issue, diff --git a/modules/repository/env.go b/modules/repository/env.go index 78e06f86fb614..c663e82235cf2 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -17,6 +17,7 @@ import ( const ( EnvRepoName = "GITEA_REPO_NAME" EnvRepoUsername = "GITEA_REPO_USER_NAME" + EnvRepoGroupID = "GITEA_REPO_GROUP_ID" EnvRepoID = "GITEA_REPO_ID" EnvRepoIsWiki = "GITEA_REPO_IS_WIKI" EnvPusherName = "GITEA_PUSHER_NAME" @@ -76,6 +77,7 @@ func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model EnvRepoID+"="+strconv.FormatInt(repo.ID, 10), EnvPRID+"="+strconv.FormatInt(prID, 10), EnvAppURL+"="+setting.AppURL, + EnvRepoGroupID+"="+strconv.FormatInt(repo.GroupID, 10), "SSH_ORIGINAL_COMMAND=gitea-internal", ) diff --git a/modules/repository/push.go b/modules/repository/push.go index cf047847b6ca9..ddbcf9065f4d6 100644 --- a/modules/repository/push.go +++ b/modules/repository/push.go @@ -12,6 +12,7 @@ type PushUpdateOptions struct { PusherID int64 PusherName string RepoUserName string + RepoGroupID int64 RepoName string RefFullName git.RefName // branch, tag or other name to push OldCommitID string diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 404718def0f81..a2ad757e811b8 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -119,6 +119,9 @@ type Repository struct { RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` Topics []string `json:"topics"` Licenses []string `json:"licenses"` + + GroupID int64 `json:"group_id"` + GroupSortOrder int `json:"group_sort_order"` } // CreateRepoOption options when creating repository @@ -153,6 +156,8 @@ type CreateRepoOption struct { // ObjectFormatName of the underlying git repository // enum: sha1,sha256 ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"` + // GroupID of the group which will contain this repository. ignored if the repo owner is not an organization. + GroupID int64 `json:"group_id"` } // EditRepoOption options when editing a repository's properties diff --git a/modules/structs/repo_group.go b/modules/structs/repo_group.go new file mode 100644 index 0000000000000..3e0f8fdbc0135 --- /dev/null +++ b/modules/structs/repo_group.go @@ -0,0 +1,53 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Group represents a group of repositories and subgroups in an organization +type Group struct { + ID int64 `json:"id"` + Owner *User `json:"owner"` + Name string `json:"name"` + Description string `json:"description"` + ParentGroupID int64 `json:"parentGroupID"` + NumRepos int64 `json:"num_repos"` + NumSubgroups int64 `json:"num_subgroups"` + Link string `json:"link"` + SortOrder int `json:"sort_order"` + AvatarURL string `json:"avatar_url"` +} + +// NewGroupOption represents options for creating a new group in an organization +// swagger:model +type NewGroupOption struct { + // the name for the newly created group + // + // required: true + Name string `json:"name" binding:"Required"` + // the description of the newly created group + Description string `json:"description"` + // the visibility of the newly created group + Visibility VisibleType `json:"visibility"` +} + +// MoveGroupOption represents options for changing a group or repo's parent and sort order +// swagger:model +type MoveGroupOption struct { + // the new parent group. can be 0 to specify no parent + // + // required: true + NewParent int64 `json:"newParent" binding:"Required"` + // the position of this group in its new parent + NewPos *int `json:"newPos,omitempty"` +} + +// EditGroupOption represents options for editing a repository group +// swagger:model +type EditGroupOption struct { + // the new name of the group + Name *string `json:"name,omitempty"` + // the new description of the group + Description *string `json:"description,omitempty"` + // the new visibility of the group + Visibility *VisibleType `json:"visibility,omitempty"` +} diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go index ee9994ab0b887..42f1a7caba28d 100644 --- a/modules/templates/util_avatar.go +++ b/modules/templates/util_avatar.go @@ -11,6 +11,7 @@ import ( activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/avatars" + group_model "code.gitea.io/gitea/models/group" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -58,6 +59,11 @@ func (au *AvatarUtils) Avatar(item any, others ...any) template.HTML { if src != "" { return AvatarHTML(src, size, class, t.AsUser().DisplayName()) } + case *group_model.Group: + src := t.AvatarLinkWithSize(size * setting.Avatar.RenderedSizeFactor) + if src != "" { + return AvatarHTML(src, size, class, t.Name) + } } return AvatarHTML(avatars.DefaultAvatarLink(), size, class, "") diff --git a/modules/util/slice.go b/modules/util/slice.go index aaa729c1c9b3c..a1ebd89b99f9e 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -77,3 +77,11 @@ func SliceNilAsEmpty[T any](a []T) []T { } return a } + +func SliceMap[T, R any](slice []T, mapper func(it T) R) []R { + ret := make([]R, 0) + for _, it := range slice { + ret = append(ret, mapper(it)) + } + return ret +} diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go index c2efed7490c25..42338a1b83438 100644 --- a/routers/api/v1/admin/adopt.go +++ b/routers/api/v1/admin/adopt.go @@ -55,33 +55,10 @@ func ListUnadoptedRepositories(ctx *context.APIContext) { ctx.JSON(http.StatusOK, repoNames) } -// AdoptRepository will adopt an unadopted repository -func AdoptRepository(ctx *context.APIContext) { - // swagger:operation POST /admin/unadopted/{owner}/{repo} admin adminAdoptRepository - // --- - // summary: Adopt unadopted files as a repository - // produces: - // - application/json - // parameters: - // - name: owner - // in: path - // description: owner of the repo - // type: string - // required: true - // - name: repo - // in: path - // description: name of the repo - // type: string - // required: true - // responses: - // "204": - // "$ref": "#/responses/empty" - // "404": - // "$ref": "#/responses/notFound" - // "403": - // "$ref": "#/responses/forbidden" +func commonAdoptRepository(ctx *context.APIContext) { ownerName := ctx.PathParam("username") repoName := ctx.PathParam("reponame") + groupID := ctx.PathParamInt64("group_id") ctxUser, err := user_model.GetUserByName(ctx, ownerName) if err != nil { @@ -94,12 +71,12 @@ func AdoptRepository(ctx *context.APIContext) { } // check not a repo - has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName, groupID) if err != nil { ctx.APIErrorInternal(err) return } - isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName)) + isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName, groupID)) if err != nil { ctx.APIErrorInternal(err) return @@ -119,11 +96,38 @@ func AdoptRepository(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } -// DeleteUnadoptedRepository will delete an unadopted repository -func DeleteUnadoptedRepository(ctx *context.APIContext) { - // swagger:operation DELETE /admin/unadopted/{owner}/{repo} admin adminDeleteUnadoptedRepository +// AdoptRepository will adopt an unadopted repository +func AdoptRepository(ctx *context.APIContext) { + // swagger:operation POST /admin/unadopted/{owner}/{repo} admin adminAdoptRepository // --- - // summary: Delete unadopted files + // summary: Adopt unadopted files as a repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" + // "403": + // "$ref": "#/responses/forbidden" + commonAdoptRepository(ctx) +} + +func AdoptGroupRepository(ctx *context.APIContext) { + // swagger:operation POST /admin/unadopted/{owner}/{group_id}/{repo} admin adminAdoptRepository + // --- + // summary: Adopt unadopted files as a repository // produces: // - application/json // parameters: @@ -140,10 +144,17 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) { // responses: // "204": // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" // "403": // "$ref": "#/responses/forbidden" + commonAdoptRepository(ctx) +} + +func commonDeleteUnadoptedRepo(ctx *context.APIContext) { ownerName := ctx.PathParam("username") repoName := ctx.PathParam("reponame") + groupID := ctx.PathParamInt64("group_id") ctxUser, err := user_model.GetUserByName(ctx, ownerName) if err != nil { @@ -156,12 +167,12 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) { } // check not a repo - has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName, groupID) if err != nil { ctx.APIErrorInternal(err) return } - isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName)) + isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName, groupID)) if err != nil { ctx.APIErrorInternal(err) return @@ -171,10 +182,61 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) { return } - if err := repo_service.DeleteUnadoptedRepository(ctx, ctx.Doer, ctxUser, repoName); err != nil { + if err := repo_service.DeleteUnadoptedRepository(ctx, ctx.Doer, ctxUser, repoName, groupID); err != nil { ctx.APIErrorInternal(err) return } ctx.Status(http.StatusNoContent) } + +// DeleteUnadoptedRepository will delete an unadopted repository +func DeleteUnadoptedRepository(ctx *context.APIContext) { + // swagger:operation DELETE /admin/unadopted/{owner}/{repo} admin adminDeleteUnadoptedRepository + // --- + // summary: Delete unadopted files + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + commonDeleteUnadoptedRepo(ctx) +} + +func DeleteUnadoptedRepositoryInGroup(ctx *context.APIContext) { + // swagger:operation DELETE /admin/unadopted/{owner}/{group_id}/{repo} admin adminDeleteUnadoptedRepository + // --- + // summary: Delete unadopted files + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + commonDeleteUnadoptedRepo(ctx) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index f412e8a06caca..588a2aafe4707 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -68,15 +68,18 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + shared_group_model "code.gitea.io/gitea/models/shared/group" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -85,6 +88,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/activitypub" "code.gitea.io/gitea/routers/api/v1/admin" + "code.gitea.io/gitea/routers/api/v1/group" "code.gitea.io/gitea/routers/api/v1/misc" "code.gitea.io/gitea/routers/api/v1/notify" "code.gitea.io/gitea/routers/api/v1/org" @@ -138,7 +142,16 @@ func repoAssignment() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { userName := ctx.PathParam("username") repoName := ctx.PathParam("reponame") - + var gid int64 + group := ctx.PathParam("group_id") + if group != "" { + gid, _ = strconv.ParseInt(group, 10, 64) + if gid == 0 { + ctx.Redirect(strings.Replace(ctx.Req.URL.RequestURI(), "/0/", "/", 1), 307) + return + } + group += "/" + } var ( owner *user_model.User err error @@ -168,7 +181,7 @@ func repoAssignment() func(ctx *context.APIContext) { ctx.ContextUser = owner // Get repository. - repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName) + repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, gid, repoName) if err != nil { if repo_model.IsErrRepoNotExist(err) { redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, repoName) @@ -184,6 +197,10 @@ func repoAssignment() func(ctx *context.APIContext) { } return } + if repo.GroupID != gid { + ctx.APIErrorNotFound() + return + } repo.Owner = owner ctx.Repo.Repository = repo @@ -206,7 +223,7 @@ func repoAssignment() func(ctx *context.APIContext) { ctx.Repo.Permission.AccessMode = perm.AccessModeWrite } - if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil { + if err = ctx.Repo.Repository.LoadUnits(ctx); err != nil { ctx.APIErrorInternal(err) return } @@ -508,6 +525,55 @@ func reqOrgOwnership() func(ctx *context.APIContext) { } } +// reqGroupMembership user should be organization owner, +// a member of a team with access to the group, or site admin +func reqGroupMembership(mode perm.AccessMode, needsCreatePerm bool) func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + if ctx.IsUserSiteAdmin() { + return + } + gid := ctx.PathParamInt64("group_id") + g, err := group_model.GetGroupByID(ctx, gid) + if err != nil { + ctx.APIErrorInternal(err) + return + } + err = g.LoadOwner(ctx) + if err != nil { + ctx.APIErrorInternal(err) + return + } + canAccess, err := g.CanAccessAtLevel(ctx, ctx.Doer, mode) + if err != nil { + ctx.APIErrorInternal(err) + return + } + igm, err := shared_group_model.IsGroupMember(ctx, gid, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + if !igm && !canAccess { + ctx.APIErrorNotFound() + return + } + if needsCreatePerm { + canCreateIn := false + if ctx.IsSigned { + canCreateIn, err = g.CanCreateIn(ctx, ctx.Doer.ID) + if err != nil { + ctx.APIErrorInternal(err) + return + } + } + if !canCreateIn { + ctx.APIError(http.StatusForbidden, fmt.Sprintf("User[%d] does not have permission to create new items in group[%d]", ctx.Doer.ID, gid)) + return + } + } + } +} + // reqTeamMembership user should be an team member, or a site admin func reqTeamMembership() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -1130,11 +1196,13 @@ func Routes() *web.Router { // (repo scope) m.Group("/starred", func() { m.Get("", user.GetMyStarredRepos) - m.Group("/{username}/{reponame}", func() { + fn := func() { m.Get("", user.IsStarring) m.Put("", user.Star) m.Delete("", user.Unstar) - }, repoAssignment(), checkTokenPublicOnly()) + } + m.Group("/{username}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) + m.Group("/{username}/group/{group_id}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) }, reqStarsEnabled(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) m.Get("/times", repo.ListMyTrackedTimes) m.Get("/stopwatches", repo.GetStopwatches) @@ -1181,13 +1249,13 @@ func Routes() *web.Router { // (repo scope) m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) - - m.Group("/{username}/{reponame}", func() { + fn := func() { m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff) m.Combo("").Get(reqAnyRepoReader(), repo.Get). Delete(reqToken(), reqOwner(), repo.Delete). Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit) + m.Post("/groups/move", reqToken(), bind(api.MoveGroupOption{}), reqOrgMembership(), reqGroupMembership(perm.AccessModeWrite, false), repo.MoveRepoToGroup) m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate) m.Group("/transfer", func() { m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer) @@ -1467,27 +1535,31 @@ func Routes() *web.Router { }, reqAdmin(), reqToken()) m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive) - }, repoAssignment(), checkTokenPublicOnly()) + } + m.Group("/{username}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) + m.Group("/{username}/group/{group_id}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) // Artifacts direct download endpoint authenticates via signed url // it is protected by the "sig" parameter (to help to access private repo), so no need to use other middlewares m.Get("/repos/{username}/{reponame}/actions/artifacts/{artifact_id}/zip/raw", repo.DownloadArtifactRaw) + m.Get("/repos/{username}/group/{group_id}/{reponame}/actions/artifacts/{artifact_id}/zip/raw", repo.DownloadArtifactRaw) // Notifications (requires notifications scope) m.Group("/repos", func() { - m.Group("/{username}/{reponame}", func() { + fn := func() { m.Combo("/notifications", reqToken()). Get(notify.ListRepoNotifications). Put(notify.ReadRepoNotifications) - }, repoAssignment(), checkTokenPublicOnly()) + } + m.Group("/{username}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) + m.Group("/{username}/group/{group_id}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification)) // Issue (requires issue scope) m.Group("/repos", func() { m.Get("/issues/search", repo.SearchIssues) - - m.Group("/{username}/{reponame}", func() { + fn := func() { m.Group("/issues", func() { m.Combo("").Get(repo.ListIssues). Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), reqRepoReader(unit.TypeIssues), repo.CreateIssue) @@ -1599,7 +1671,9 @@ func Routes() *web.Router { Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone). Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone) }) - }, repoAssignment(), checkTokenPublicOnly()) + } + m.Group("/{username}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) + m.Group("/{username}/group/{group_id}/{reponame}", fn, repoAssignment(), checkTokenPublicOnly()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue)) // NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs @@ -1687,6 +1761,9 @@ func Routes() *web.Router { m.Delete("", org.UnblockUser) }) }, reqToken(), reqOrgOwnership()) + m.Group("/groups", func() { + m.Post("/new", reqToken(), reqGroupMembership(perm.AccessModeWrite, true), group.NewGroup) + }) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly()) m.Group("/teams/{teamid}", func() { m.Combo("").Get(reqToken(), org.GetTeam). @@ -1740,8 +1817,13 @@ func Routes() *web.Router { }) m.Group("/unadopted", func() { m.Get("", admin.ListUnadoptedRepositories) - m.Post("/{username}/{reponame}", admin.AdoptRepository) - m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository) + m.Group("/{username}", func() { + m.Post("/{reponame}", admin.AdoptRepository) + m.Delete("/{reponame}", admin.DeleteUnadoptedRepository) + m.Post("/group/{group_id}/{reponame}", admin.AdoptGroupRepository) + m.Delete("/group/{group_id}/{reponame}", admin.DeleteUnadoptedRepositoryInGroup) + }) + }) m.Group("/hooks", func() { m.Combo("").Get(admin.ListHooks). @@ -1769,7 +1851,18 @@ func Routes() *web.Router { m.Get("/search", repo.TopicSearch) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) }, sudo()) - + m.Group("/groups", func() { + m.Group("/{group_id}", func() { + m.Combo(""). + Get(reqGroupMembership(perm.AccessModeRead, false), group.GetGroup). + Patch(reqToken(), reqGroupMembership(perm.AccessModeWrite, false), bind(api.EditGroupOption{}), group.EditGroup). + Delete(reqToken(), reqGroupMembership(perm.AccessModeAdmin, false), group.DeleteGroup) + m.Post("/move", reqToken(), reqGroupMembership(perm.AccessModeWrite, false), bind(api.MoveGroupOption{}), group.MoveGroup) + m.Post("/new", reqToken(), reqGroupMembership(perm.AccessModeWrite, true), bind(api.NewGroupOption{}), group.NewSubGroup) + m.Get("/subgroups", reqGroupMembership(perm.AccessModeRead, false), group.GetGroupSubGroups) + m.Get("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqGroupMembership(perm.AccessModeRead, false), group.GetGroupRepos) + }, checkTokenPublicOnly()) + }) return m } diff --git a/routers/api/v1/group/group.go b/routers/api/v1/group/group.go new file mode 100644 index 0000000000000..abcb33d53cbc5 --- /dev/null +++ b/routers/api/v1/group/group.go @@ -0,0 +1,413 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "errors" + "net/http" + "strings" + + group_model "code.gitea.io/gitea/models/group" + access_model "code.gitea.io/gitea/models/perm/access" + shared_group_model "code.gitea.io/gitea/models/shared/group" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" + group_service "code.gitea.io/gitea/services/group" +) + +func createCommonGroup(ctx *context.APIContext, parentGroupID, ownerID int64) (*api.Group, error) { + if ownerID < 1 { + if parentGroupID < 1 { + return nil, errors.New("cannot determine new group's owner") + } + npg, err := group_model.GetGroupByID(ctx, parentGroupID) + if err != nil { + return nil, err + } + ownerID = npg.OwnerID + } + form := web.GetForm(ctx).(*api.NewGroupOption) + group := &group_model.Group{ + Name: form.Name, + Description: form.Description, + OwnerID: ownerID, + LowerName: strings.ToLower(form.Name), + Visibility: form.Visibility, + ParentGroupID: parentGroupID, + } + if err := group_service.NewGroup(ctx, group); err != nil { + return nil, err + } + return convert.ToAPIGroup(ctx, group, ctx.Doer) +} + +// NewGroup create a new root-level group in an organization +func NewGroup(ctx *context.APIContext) { + // swagger:operation POST /orgs/{org}/groups/new repository-group groupNew + // --- + // summary: create a root-level repository group for an organization + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/NewGroupOption" + // responses: + // "201": + // "$ref": "#/responses/Group" + // "404": + // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" + ag, err := createCommonGroup(ctx, 0, ctx.Org.Organization.ID) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusCreated, ag) +} + +// NewSubGroup create a new subgroup inside a group +func NewSubGroup(ctx *context.APIContext) { + // swagger:operation POST /groups/{group_id}/new repository-group groupNewSubGroup + // --- + // summary: create a subgroup inside a group + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the group to create a subgroup in + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/NewGroupOption" + // responses: + // "201": + // "$ref": "#/responses/Group" + // "404": + // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" + var ( + group *api.Group + err error + ) + gid := ctx.PathParamInt64("group_id") + group, err = createCommonGroup(ctx, gid, 0) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusCreated, group) +} + +// MoveGroup - move a group to a different group in the same organization, or to the root level if +func MoveGroup(ctx *context.APIContext) { + // swagger:operation POST /groups/{group_id}/move repository-group groupMove + // --- + // summary: move a group to a different parent group + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the group to move + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/MoveGroupOption" + // responses: + // "200": + // "$ref": "#/responses/Group" + // "404": + // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" + form := web.GetForm(ctx).(*api.MoveGroupOption) + id := ctx.PathParamInt64("group_id") + var err error + npos := -1 + if form.NewPos != nil { + npos = *form.NewPos + } + err = group_service.MoveGroupItem(ctx, group_service.MoveGroupOptions{ + NewParent: form.NewParent, + ItemID: id, + IsGroup: true, + NewPos: npos, + }, ctx.Doer) + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + return + } + if err != nil { + ctx.APIErrorInternal(err) + return + } + var ( + ng *group_model.Group + apiGroup *api.Group + ) + ng, err = group_model.GetGroupByID(ctx, id) + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + return + } + if err != nil { + ctx.APIErrorInternal(err) + return + } + apiGroup, err = convert.ToAPIGroup(ctx, ng, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + } + ctx.JSON(http.StatusOK, apiGroup) +} + +// EditGroup - update a group in an organization +func EditGroup(ctx *context.APIContext) { + // swagger:operation PATCH /groups/{group_id} repository-group groupEdit + // --- + // summary: edits a group in an organization. only fields that are set will be changed. + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the group to edit + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/EditGroupOption" + // responses: + // "200": + // "$ref": "#/responses/Group" + // "404": + // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" + var ( + err error + group *group_model.Group + ) + form := web.GetForm(ctx).(*api.EditGroupOption) + gid := ctx.PathParamInt64("group_id") + group, err = group_model.GetGroupByID(ctx, gid) + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + return + } + if err != nil { + ctx.APIErrorInternal(err) + return + } + if form.Visibility != nil { + group.Visibility = *form.Visibility + } + if form.Description != nil { + group.Description = *form.Description + } + if form.Name != nil { + group.Name = *form.Name + } + err = group_model.UpdateGroup(ctx, group) + if err != nil { + ctx.APIErrorInternal(err) + return + } + var newAPIGroup *api.Group + newAPIGroup, err = convert.ToAPIGroup(ctx, group, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusOK, newAPIGroup) +} + +func GetGroup(ctx *context.APIContext) { + // swagger:operation GET /groups/{group_id} repository-group groupGet + // --- + // summary: gets a group in an organization + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the group to retrieve + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/Group" + // "404": + // "$ref": "#/responses/notFound" + var ( + err error + group *group_model.Group + ) + group, err = group_model.GetGroupByID(ctx, ctx.PathParamInt64("group_id")) + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + return + } + if err != nil { + ctx.APIErrorInternal(err) + return + } + apiGroup, err := convert.ToAPIGroup(ctx, group, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusOK, apiGroup) +} + +func DeleteGroup(ctx *context.APIContext) { + // swagger:operation DELETE /groups/{group_id} repository-group groupDelete + // --- + // summary: Delete a repository group + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the group to delete + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + err := group_service.DeleteGroup(ctx, ctx.PathParamInt64("group_id")) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.Status(http.StatusNoContent) +} + +func GetGroupRepos(ctx *context.APIContext) { + // swagger:operation GET /groups/{group_id}/repos repository-group groupGetRepos + // --- + // summary: gets the repos contained within a group + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the group containing the repositories + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/RepositoryList" + // "404": + // "$ref": "#/responses/notFound" + gid := ctx.PathParamInt64("group_id") + _, err := group_model.GetGroupByID(ctx, gid) + if err != nil { + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + } else { + ctx.APIErrorInternal(err) + } + return + } + + groupRepos, err := shared_group_model.GetGroupRepos(ctx, gid, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + repos := make([]*api.Repository, len(groupRepos)) + for i, repo := range groupRepos { + permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + repos[i] = convert.ToRepo(ctx, repo, permission) + } + ctx.SetTotalCountHeader(int64(len(repos))) + ctx.JSON(http.StatusOK, repos) +} + +func GetGroupSubGroups(ctx *context.APIContext) { + // swagger:operation GET /groups/{group_id}/subgroups repository-group groupGetSubGroups + // --- + // summary: gets the subgroups contained within a group + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the parent group + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/GroupList" + // "404": + // "$ref": "#/responses/notFound" + g, err := group_model.GetGroupByID(ctx, ctx.PathParamInt64("group_id")) + if err != nil { + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + } else { + ctx.APIErrorInternal(err) + } + return + } + err = g.LoadAccessibleSubgroups(ctx, false, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + groups := make([]*api.Group, len(g.Subgroups)) + for i, group := range g.Subgroups { + groups[i], err = convert.ToAPIGroup(ctx, group, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + } + ctx.SetTotalCountHeader(int64(len(groups))) + ctx.JSON(http.StatusOK, groups) +} diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 1a1710750a282..5f3f96c3146bf 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -631,7 +631,7 @@ func GetTeamRepo(ctx *context.APIContext) { // getRepositoryByParams get repository by a team's organization ID and repo name func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository { - repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.PathParam("reponame")) + repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.PathParamInt64("group_id"), ctx.PathParam("reponame")) if err != nil { if repo_model.IsErrRepoNotExist(err) { ctx.APIErrorNotFound() diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 41b7f2a43f67b..6083535f0763e 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -355,7 +355,7 @@ func LinkPackage(ctx *context.APIContext) { return } - repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParam("repo_name")) + repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParamInt64("group_id"), ctx.PathParam("repo_name")) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.APIError(http.StatusNotFound, err) diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 25aabe6dd2792..7a8e1523f7b17 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -1549,7 +1549,7 @@ func buildSignature(endp string, expires, artifactID int64) []byte { } func buildDownloadRawEndpoint(repo *repo_model.Repository, artifactID int64) string { - return fmt.Sprintf("api/v1/repos/%s/%s/actions/artifacts/%d/zip/raw", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), artifactID) + return fmt.Sprintf("api/v1/repos/%s/%d/%s/actions/artifacts/%d/zip/raw", url.PathEscape(repo.OwnerName), repo.GroupID, url.PathEscape(repo.Name), artifactID) } func buildSigURL(ctx go_context.Context, endPoint string, artifactID int64) string { @@ -1623,7 +1623,7 @@ func DownloadArtifact(ctx *context.APIContext) { // DownloadArtifactRaw Downloads a specific artifact for a workflow run directly. func DownloadArtifactRaw(ctx *context.APIContext) { // it doesn't use repoAssignment middleware, so it needs to prepare the repo and check permission (sig) by itself - repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ctx.PathParam("username"), ctx.PathParam("reponame")) + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ctx.PathParam("username"), ctx.PathParam("reponame"), ctx.PathParamInt64("group_id")) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.APIErrorNotFound() diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go index 1b58beb7b6e92..aaa490f6bdca6 100644 --- a/routers/api/v1/repo/issue_dependency.go +++ b/routers/api/v1/repo/issue_dependency.go @@ -514,7 +514,7 @@ func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Is return nil } var err error - repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, form.Owner, form.Name) + repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, form.Owner, form.Name, ctx.PathParamInt64("group_id")) if err != nil { if repo_model.IsErrRepoNotExist(err) { ctx.APIErrorNotFound("IsErrRepoNotExist", err) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 09729200d5248..0a77dfc087c99 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -251,15 +251,23 @@ func GetPullRequestByBaseHead(ctx *context.APIContext) { split := strings.SplitN(head, ":", 2) headBranch = split[1] var owner, name string + var gid int64 if strings.Contains(split[0], "/") { split = strings.Split(split[0], "/") - owner = split[0] - name = split[1] + if len(split) == 3 { + owner = split[0] + gid, _ = strconv.ParseInt(split[1], 10, 64) + name = split[2] + } else { + owner, name = split[0], split[1] + } + } else { owner = split[0] + gid = ctx.Repo.Repository.GroupID name = ctx.Repo.Repository.Name } - repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name) + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name, gid) if err != nil { if repo_model.IsErrRepoNotExist(err) { ctx.APIErrorNotFound() @@ -1201,7 +1209,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) return nil, nil } - compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseRef.ShortName(), headRef.ShortName(), false, false) + compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name, baseRepo.GroupID), baseRef.ShortName(), headRef.ShortName(), false, false) if err != nil { ctx.APIErrorInternal(err) return nil, nil diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index e69b7729a0fb0..d183bf26b20a2 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -35,6 +35,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" feed_service "code.gitea.io/gitea/services/feed" + group_service "code.gitea.io/gitea/services/group" "code.gitea.io/gitea/services/issue" repo_service "code.gitea.io/gitea/services/repository" ) @@ -262,6 +263,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre TrustModel: repo_model.ToTrustModel(opt.TrustModel), IsTemplate: opt.Template, ObjectFormatName: opt.ObjectFormatName, + GroupID: opt.GroupID, }) if err != nil { if repo_model.IsErrRepoAlreadyExist(err) { @@ -1314,3 +1316,52 @@ func ListRepoActivityFeeds(ctx *context.APIContext) { ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer)) } + +func MoveRepoToGroup(ctx *context.APIContext) { + // swagger:operation POST /repo/{owner}/{repo}/move + // --- + // summary: move a repository to another group + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true // - name: body + // in: body + // schema: + // "$ref": "#/definitions/MoveGroupOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" + form := web.GetForm(ctx).(*api.MoveGroupOption) + npos := -1 + if form.NewPos != nil { + npos = *form.NewPos + } + err := group_service.MoveGroupItem(ctx, group_service.MoveGroupOptions{ + IsGroup: false, + NewPos: npos, + ItemID: ctx.Repo.Repository.ID, + NewParent: form.NewParent, + }, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index bafd5e04a2af3..4ed5177dc9d3e 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -222,4 +222,13 @@ type swaggerParameterBodies struct { // in:body LockIssueOption api.LockIssueOption + + // in:body + NewGroupOption api.NewGroupOption + + // in:body + EditGroupOption api.EditGroupOption + + // in:body + MoveGroupOption api.MoveGroupOption } diff --git a/routers/api/v1/swagger/repo_group.go b/routers/api/v1/swagger/repo_group.go new file mode 100644 index 0000000000000..65d0fb452bbd7 --- /dev/null +++ b/routers/api/v1/swagger/repo_group.go @@ -0,0 +1,20 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package swagger + +import api "code.gitea.io/gitea/modules/structs" + +// Group +// swagger:response Group +type swaggerResponseGroup struct { + // in:body + Body api.Group `json:"body"` +} + +// GroupList +// swagger:response GroupList +type swaggerResponseGroupList struct { + // in:body + Body []api.Group `json:"body"` +} diff --git a/routers/common/lfs.go b/routers/common/lfs.go index 1d2b71394bf0e..5a6b8456e792e 100644 --- a/routers/common/lfs.go +++ b/routers/common/lfs.go @@ -14,7 +14,7 @@ const RouterMockPointCommonLFS = "common-lfs" func AddOwnerRepoGitLFSRoutes(m *web.Router, middlewares ...any) { // shared by web and internal routers - m.Group("/{username}/{reponame}/info/lfs", func() { + fn := func() { m.Post("/objects/batch", lfs.CheckAcceptMediaType, lfs.BatchHandler) m.Put("/objects/{oid}/{size}", lfs.UploadHandler) m.Get("/objects/{oid}/{filename}", lfs.DownloadHandler) @@ -27,5 +27,7 @@ func AddOwnerRepoGitLFSRoutes(m *web.Router, middlewares ...any) { m.Post("/{lid}/unlock", lfs.UnLockHandler) }, lfs.CheckAcceptMediaType) m.Any("/*", http.NotFound) - }, append([]any{web.RouterMockPoint(RouterMockPointCommonLFS)}, middlewares...)...) + } + m.Group("/{username}/{reponame}/info/lfs", fn, append([]any{web.RouterMockPoint(RouterMockPointCommonLFS)}, middlewares...)...) + m.Group("/{username}/group/{group_id}/{reponame}/info/lfs", fn, append([]any{web.RouterMockPoint(RouterMockPointCommonLFS)}, middlewares...)...) } diff --git a/routers/private/actions.go b/routers/private/actions.go index 696634b5e757d..06934128dc26f 100644 --- a/routers/private/actions.go +++ b/routers/private/actions.go @@ -83,7 +83,7 @@ func parseScope(ctx *context.PrivateContext, scope string) (ownerID, repoID int6 return ownerID, repoID, nil } - r, err := repo_model.GetRepositoryByName(ctx, u.ID, repoName) + r, err := repo_model.GetRepositoryByName(ctx, u.ID, ctx.PathParamInt64("group_id"), repoName) if err != nil { return ownerID, repoID, err } diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index e8bef7d6c14bb..502ee5f94036b 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -41,6 +41,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { ownerName := ctx.PathParam("owner") repoName := ctx.PathParam("repo") + groupID := ctx.PathParamInt64("group_id") // defer getting the repository at this point - as we should only retrieve it if we're going to call update var ( @@ -61,7 +62,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { // may be a very large number of them). if refFullName.IsBranch() || refFullName.IsTag() { if repo == nil { - repo = loadRepository(ctx, ownerName, repoName) + repo = loadRepository(ctx, ownerName, repoName, groupID) if ctx.Written() { // Error handled in loadRepository return @@ -75,6 +76,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { NewCommitID: opts.NewCommitIDs[i], PusherID: opts.UserID, PusherName: opts.UserName, + RepoGroupID: groupID, RepoUserName: ownerName, RepoName: repoName, } @@ -98,7 +100,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { continue } if repo == nil { - repo = loadRepository(ctx, ownerName, repoName) + repo = loadRepository(ctx, ownerName, repoName, groupID) if ctx.Written() { return } @@ -176,7 +178,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { if isPrivate.Has() || isTemplate.Has() { // load the repository if repo == nil { - repo = loadRepository(ctx, ownerName, repoName) + repo = loadRepository(ctx, ownerName, repoName, groupID) if ctx.Written() { // Error handled in loadRepository return @@ -239,7 +241,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { if !git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() { // First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo if repo == nil { - repo = loadRepository(ctx, ownerName, repoName) + repo = loadRepository(ctx, ownerName, repoName, groupID) if ctx.Written() { return } diff --git a/routers/private/internal.go b/routers/private/internal.go index 55a11aa3dda83..c7053433fd341 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -62,9 +62,13 @@ func Routes() *web.Router { r.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent) r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo) r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog) + r.Post("/hook/pre-receive/{owner}/group/{group_id}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookPreReceive) r.Post("/hook/pre-receive/{owner}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookPreReceive) + r.Post("/hook/post-receive/{owner}/group/{group_id}/{repo}", context.OverrideContext(), bind(private.HookOptions{}), HookPostReceive) r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext(), bind(private.HookOptions{}), HookPostReceive) + r.Post("/hook/proc-receive/{owner}/group/{group_id}/{repo}", context.OverrideContext(), RepoAssignment, bind(private.HookOptions{}), HookProcReceive) r.Post("/hook/proc-receive/{owner}/{repo}", context.OverrideContext(), RepoAssignment, bind(private.HookOptions{}), HookProcReceive) + r.Post("/hook/set-default-branch/{owner}/group/{group_id}/{repo}/{branch}", RepoAssignment, SetDefaultBranch) r.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", RepoAssignment, SetDefaultBranch) r.Get("/serv/none/{keyid}", ServNoCommand) r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand) diff --git a/routers/private/internal_repo.go b/routers/private/internal_repo.go index e111d6689e0ab..5f7f166944de0 100644 --- a/routers/private/internal_repo.go +++ b/routers/private/internal_repo.go @@ -20,8 +20,9 @@ import ( func RepoAssignment(ctx *gitea_context.PrivateContext) { ownerName := ctx.PathParam("owner") repoName := ctx.PathParam("repo") + gid := ctx.PathParamInt64("group_id") - repo := loadRepository(ctx, ownerName, repoName) + repo := loadRepository(ctx, ownerName, repoName, gid) if ctx.Written() { // Error handled in loadRepository return @@ -41,8 +42,8 @@ func RepoAssignment(ctx *gitea_context.PrivateContext) { } } -func loadRepository(ctx *gitea_context.PrivateContext, ownerName, repoName string) *repo_model.Repository { - repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName) +func loadRepository(ctx *gitea_context.PrivateContext, ownerName, repoName string, groupID int64) *repo_model.Repository { + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName, groupID) if err != nil { log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) ctx.JSON(http.StatusInternalServerError, private.Response{ diff --git a/routers/private/serv.go b/routers/private/serv.go index b879be0dc2067..8a05d86931093 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -133,7 +133,7 @@ func ServCommand(ctx *context.PrivateContext) { // Now get the Repository and set the results section repoExist := true - repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, results.RepoName) + repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, ctx.PathParamInt64("group_id"), results.RepoName) if err != nil { if repo_model.IsErrRepoNotExist(err) { repoExist = false diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go index 1bc8abb88cc2e..40e344bdcd8de 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -6,6 +6,7 @@ package admin import ( "net/http" "net/url" + "strconv" "strings" "code.gitea.io/gitea/models/db" @@ -127,14 +128,18 @@ func AdoptOrDeleteRepository(ctx *context.Context) { } repoName := dirSplit[1] + var groupID int64 + if len(dirSplit) >= 3 { + groupID, _ = strconv.ParseInt(dirSplit[2], 10, 64) + } // check not a repo - has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName, groupID) if err != nil { ctx.ServerError("IsRepositoryExist", err) return } - isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName)) + isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName, groupID)) if err != nil { ctx.ServerError("IsDir", err) return @@ -151,7 +156,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir)) } else if action == "delete" { - if err := repo_service.DeleteUnadoptedRepository(ctx, ctx.Doer, ctxUser, dirSplit[1]); err != nil { + if err := repo_service.DeleteUnadoptedRepository(ctx, ctx.Doer, ctxUser, dirSplit[1], groupID); err != nil { ctx.ServerError("repository.AdoptRepository", err) return } diff --git a/routers/web/githttp.go b/routers/web/githttp.go index 06de811f16e11..7a172ca3ce153 100644 --- a/routers/web/githttp.go +++ b/routers/web/githttp.go @@ -10,7 +10,7 @@ import ( ) func addOwnerRepoGitHTTPRouters(m *web.Router) { - m.Group("/{username}/{reponame}", func() { + fn := func() { m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack) m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack) m.Methods("GET,OPTIONS", "/info/refs", repo.GetInfoRefs) @@ -22,5 +22,7 @@ func addOwnerRepoGitHTTPRouters(m *web.Router) { m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject) m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile) m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile) - }, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb()) + } + m.Group("/{username}/{reponame}", fn, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb()) + m.Group("/{username}/group/{group_id}/{reponame}", fn, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb()) } diff --git a/routers/web/goget.go b/routers/web/goget.go index 67e0bee866c12..9321375c00c14 100644 --- a/routers/web/goget.go +++ b/routers/web/goget.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "path" + "strconv" "strings" repo_model "code.gitea.io/gitea/models/repo" @@ -22,14 +23,17 @@ func goGet(ctx *context.Context) { return } - parts := strings.SplitN(ctx.Req.URL.EscapedPath(), "/", 4) + parts := strings.SplitN(ctx.Req.URL.EscapedPath(), "/", 5) if len(parts) < 3 { return } - + var group string ownerName := parts[1] repoName := parts[2] + if len(parts) > 3 { + group = parts[3] + } // Quick responses appropriate go-get meta with status 200 // regardless of if user have access to the repository, @@ -51,12 +55,16 @@ func goGet(ctx *context.Context) { return } branchName := setting.Repository.DefaultBranch - - repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName) + gid, _ := strconv.ParseInt(group, 10, 64) + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName, gid) if err == nil && len(repo.DefaultBranch) > 0 { branchName = repo.DefaultBranch } - prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName)) + prefix := setting.AppURL + url.PathEscape(ownerName) + if group != "" { + prefix = path.Join(prefix, group) + } + prefix = path.Join(prefix, url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName)) appURL, _ := url.Parse(setting.AppURL) @@ -69,9 +77,9 @@ func goGet(ctx *context.Context) { var cloneURL string if setting.Repository.GoGetCloneURLProtocol == "ssh" { - cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, ownerName, repoName) + cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, ownerName, repoName, gid) } else { - cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, ownerName, repoName) + cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, ownerName, repoName, gid) } goImportContent := fmt.Sprintf("%s git %s", goGetImport, cloneURL /*CloneLink*/) goSourceContent := fmt.Sprintf("%s _ %s %s", goGetImport, prefix+"{/dir}" /*GoDocDirectory*/, prefix+"{/dir}/{file}#L{line}" /*GoDocFile*/) diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 0ec7cfddc5f55..5f121e8ad8275 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -236,7 +236,7 @@ func TeamsRepoAction(ctx *context.Context) { case "add": repoName := path.Base(ctx.FormString("repo_name")) var repo *repo_model.Repository - repo, err = repo_model.GetRepositoryByName(ctx, ctx.Org.Organization.ID, repoName) + repo, err = repo_model.GetRepositoryByName(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("group_id"), repoName) if err != nil { if repo_model.IsErrRepoNotExist(err) { ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo")) diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 96d1d87836287..d823f246c9747 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -160,6 +160,7 @@ func RestoreBranchPost(ctx *context.Context) { PusherName: ctx.Doer.Name, RepoUserName: ctx.Repo.Owner.Name, RepoName: ctx.Repo.Repository.Name, + RepoGroupID: ctx.Repo.Repository.GroupID, }); err != nil { log.Error("RestoreBranch: Update: %v", err) } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index c771b30e5ff85..f1a39cc50f75b 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -14,6 +14,7 @@ import ( "net/http" "net/url" "path/filepath" + "strconv" "strings" "code.gitea.io/gitea/models/db" @@ -271,7 +272,15 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { ci.HeadRepo = baseRepo } } else { - ci.HeadRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, headInfosSplit[0], headInfosSplit[1]) + var headOwner, headRepo string + var headGID int64 + if len(headInfosSplit) == 2 { + headOwner, headRepo = headInfosSplit[0], headInfosSplit[1] + } else if len(headInfosSplit) >= 3 { + headOwner, headRepo = headInfosSplit[0], headInfosSplit[2] + headGID, _ = strconv.ParseInt(headInfosSplit[1], 10, 64) + } + ci.HeadRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, headOwner, headRepo, headGID) if err != nil { if repo_model.IsErrRepoNotExist(err) { ctx.NotFound(nil) diff --git a/routers/web/repo/editor_fork.go b/routers/web/repo/editor_fork.go index b78a634c00551..19fb42f0f5f3d 100644 --- a/routers/web/repo/editor_fork.go +++ b/routers/web/repo/editor_fork.go @@ -20,7 +20,7 @@ func ForkToEdit(ctx *context.Context) { func ForkToEditPost(ctx *context.Context) { ForkRepoTo(ctx, ctx.Doer, repo_service.ForkRepoOptions{ BaseRepo: ctx.Repo.Repository, - Name: getUniqueRepositoryName(ctx, ctx.Doer.ID, ctx.Repo.Repository.Name), + Name: getUniqueRepositoryName(ctx, ctx.Doer.ID, 0, ctx.Repo.Repository.Name), Description: ctx.Repo.Repository.Description, SingleBranch: ctx.Repo.Repository.DefaultBranch, // maybe we only need the default branch in the fork? }) diff --git a/routers/web/repo/editor_util.go b/routers/web/repo/editor_util.go index f910f0bd40729..de198ce54ccab 100644 --- a/routers/web/repo/editor_util.go +++ b/routers/web/repo/editor_util.go @@ -88,10 +88,10 @@ func getParentTreeFields(treePath string) (treeNames, treePaths []string) { // getUniqueRepositoryName Gets a unique repository name for a user // It will append a - postfix if the name is already taken -func getUniqueRepositoryName(ctx context.Context, ownerID int64, name string) string { +func getUniqueRepositoryName(ctx context.Context, ownerID, groupID int64, name string) string { uniqueName := name for i := 1; i < 1000; i++ { - _, err := repo_model.GetRepositoryByName(ctx, ownerID, uniqueName) + _, err := repo_model.GetRepositoryByName(ctx, ownerID, groupID, uniqueName) if err != nil || repo_model.IsErrRepoNotExist(err) { return uniqueName } diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index deb3ae4f3a6f8..d9c68980bbd95 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -104,7 +104,7 @@ func httpBase(ctx *context.Context) *serviceHandler { } repoExist := true - repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, reponame) + repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, ctx.PathParamInt64("group_id"), reponame) if err != nil { if !repo_model.IsErrRepoNotExist(err) { ctx.ServerError("GetRepositoryByName", err) diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index af6708e841f46..b0b346e6b9b08 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -271,7 +271,7 @@ func LFSFileGet(ctx *context.Context) { ctx.Data["IsTextFile"] = st.IsText() ctx.Data["FileSize"] = meta.Size // FIXME: the last field is the URL-base64-encoded filename, it should not be "direct" - ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s/%s.git/info/lfs/objects/%s/%s", setting.AppURL, url.PathEscape(ctx.Repo.Repository.OwnerName), url.PathEscape(ctx.Repo.Repository.Name), url.PathEscape(meta.Oid), "direct") + ctx.Data["RawFileLink"] = fmt.Sprintf("%s/%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, url.PathEscape(ctx.Repo.Repository.OwnerName), url.PathEscape(ctx.Repo.Repository.Name), url.PathEscape(meta.Oid), "direct") switch { case st.IsRepresentableAsText(): if meta.Size >= setting.UI.MaxDisplayFileSize { diff --git a/routers/web/repo/star.go b/routers/web/repo/star.go index 00c06b7d02d84..f9301e15d0192 100644 --- a/routers/web/repo/star.go +++ b/routers/web/repo/star.go @@ -21,7 +21,7 @@ func ActionStar(ctx *context.Context) { } ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) - ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.Name) + ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.GroupID, ctx.Repo.Repository.Name) if err != nil { ctx.ServerError("GetRepositoryByName", err) return diff --git a/routers/web/repo/watch.go b/routers/web/repo/watch.go index 70c548b8cea7f..c00b1d1b1fcfa 100644 --- a/routers/web/repo/watch.go +++ b/routers/web/repo/watch.go @@ -21,7 +21,7 @@ func ActionWatch(ctx *context.Context) { } ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) - ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.Name) + ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.GroupID, ctx.Repo.Repository.Name) if err != nil { ctx.ServerError("GetRepositoryByName", err) return diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 2bd0abc4c03f7..4f928b233925b 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -93,7 +93,7 @@ func prepareContextForProfileBigAvatar(ctx *context.Context) { func FindOwnerProfileReadme(ctx *context.Context, doer *user_model.User, optProfileRepoName ...string) (profileDbRepo *repo_model.Repository, profileReadmeBlob *git.Blob) { profileRepoName := util.OptionalArg(optProfileRepoName, RepoNameProfile) - profileDbRepo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, profileRepoName) + profileDbRepo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParamInt64("group_id"), profileRepoName) if err != nil { if !repo_model.IsErrRepoNotExist(err) { log.Error("FindOwnerProfileReadme failed to GetRepositoryByName: %v", err) diff --git a/routers/web/user/setting/adopt.go b/routers/web/user/setting/adopt.go index 171c1933d4f49..08d6db7c31157 100644 --- a/routers/web/user/setting/adopt.go +++ b/routers/web/user/setting/adopt.go @@ -5,6 +5,8 @@ package setting import ( "path/filepath" + "strconv" + "strings" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -24,13 +26,17 @@ func AdoptOrDeleteRepository(ctx *context.Context) { ctx.Data["allowDelete"] = allowDelete dir := ctx.FormString("id") + var gid int64 + if len(strings.Split(dir, "/")) > 1 { + gid, _ = strconv.ParseInt(strings.Split(dir, "/")[1], 10, 64) + } action := ctx.FormString("action") ctxUser := ctx.Doer root := user_model.UserPath(ctxUser.LowerName) // check not a repo - has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, dir) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, dir, 0) if err != nil { ctx.ServerError("IsRepositoryExist", err) return @@ -53,7 +59,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir)) } else if action == "delete" && allowDelete { - if err := repo_service.DeleteUnadoptedRepository(ctx, ctxUser, ctxUser, dir); err != nil { + if err := repo_service.DeleteUnadoptedRepository(ctx, ctxUser, ctxUser, dir, gid); err != nil { ctx.ServerError("repository.AdoptRepository", err) return } diff --git a/routers/web/web.go b/routers/web/web.go index 4a274c171a3a0..ba7546945a145 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1071,14 +1071,16 @@ func registerWebRoutes(m *web.Router) { }, optSignIn, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{})) // end "/{username}/-": packages, projects, code - m.Group("/{username}/{reponame}/-", func() { + repoDashFn := func() { m.Group("/migrate", func() { m.Get("/status", repo.MigrateStatus) }) - }, optSignIn, context.RepoAssignment, reqUnitCodeReader) - // end "/{username}/{reponame}/-": migrate + } + m.Group("/{username}/{reponame}/-", repoDashFn, optSignIn, context.RepoAssignment, reqUnitCodeReader) + m.Group("/{username}/group/{group_id}/{reponame}/-", repoDashFn, optSignIn, context.RepoAssignment, reqUnitCodeReader) + // end "/{username}/{group_id}/{reponame}/-": migrate - m.Group("/{username}/{reponame}/settings", func() { + settingsFn := func() { m.Group("", func() { m.Combo("").Get(repo_setting.Settings). Post(web.Bind(forms.RepoSettingForm{}), repo_setting.SettingsPost) @@ -1166,18 +1168,24 @@ func registerWebRoutes(m *web.Router) { m.Post("/retry", repo.MigrateRetryPost) m.Post("/cancel", repo.MigrateCancelPost) }) - }, + } + m.Group("/{username}/{reponame}/settings", settingsFn, + reqSignIn, context.RepoAssignment, reqRepoAdmin, + ctxDataSet("PageIsRepoSettings", true, "LFSStartServer", setting.LFS.StartServer), + ) + m.Group("/{username}/group/{group_id}/{reponame}/settings", settingsFn, reqSignIn, context.RepoAssignment, reqRepoAdmin, ctxDataSet("PageIsRepoSettings", true, "LFSStartServer", setting.LFS.StartServer), ) - // end "/{username}/{reponame}/settings" + // end "/{username}/{group_id}/{reponame}/settings" - // user/org home, including rss feeds like "/{username}/{reponame}.rss" + // user/org home, including rss feeds like "/{username}/{group_id}/{reponame}.rss" m.Get("/{username}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch), repo.SetEditorconfigIfExists, repo.Home) + m.Get("/{username}/group/{group_id}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch), repo.SetEditorconfigIfExists, repo.Home) m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup) - - m.Group("/{username}/{reponame}", func() { + m.Post("/{username}/group/{group_id}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup) + rootRepoFn := func() { m.Get("/find/*", repo.FindFiles) m.Group("/tree-list", func() { m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.TreeList) @@ -1194,11 +1202,13 @@ func registerWebRoutes(m *web.Router) { Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff). Post(reqSignIn, context.RepoMustNotBeArchived(), reqUnitPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost) m.Get("/pulls/new/*", repo.PullsNewRedirect) - }, optSignIn, context.RepoAssignment, reqUnitCodeReader) - // end "/{username}/{reponame}": repo code: find, compare, list + } + m.Group("/{username}/{reponame}", rootRepoFn, optSignIn, context.RepoAssignment, reqUnitCodeReader) + m.Group("/{username}/group/{group_id}/{reponame}", rootRepoFn, optSignIn, context.RepoAssignment, reqUnitCodeReader) + // end "/{username}/{group_id}/{reponame}": repo code: find, compare, list addIssuesPullsViewRoutes := func() { - // for /{username}/{reponame}/issues" or "/{username}/{reponame}/pulls" + // for /{username}/{group_id}/{reponame}/issues" or "/{username}/{group_id}/{reponame}/pulls" m.Get("/posters", repo.IssuePullPosters) m.Group("/{index}", func() { m.Get("/info", repo.GetIssueInfo) @@ -1212,25 +1222,32 @@ func registerWebRoutes(m *web.Router) { }) } // FIXME: many "pulls" requests are sent to "issues" endpoints correctly, so the issue endpoints have to tolerate pull request permissions at the moment + m.Group("/{username}/group/{group_id}/{reponame}/{type:issues}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests)) m.Group("/{username}/{reponame}/{type:issues}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests)) + m.Group("/{username}/group/{group_id}/{reponame}/{type:pulls}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitPullsReader) m.Group("/{username}/{reponame}/{type:pulls}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitPullsReader) - m.Group("/{username}/{reponame}", func() { + repoIssueAttachmentFn := func() { m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/milestones", repo.Milestones) m.Get("/milestone/{id}", repo.MilestoneIssuesAndPulls) m.Get("/issues/suggestions", repo.IssueSuggestions) - }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones - // end "/{username}/{reponame}": view milestone, label, issue, pull, etc + } - m.Group("/{username}/{reponame}/{type:issues}", func() { + m.Group("/{username}/group/{group_id}/{reponame}", repoIssueAttachmentFn, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones + m.Group("/{username}/{reponame}", repoIssueAttachmentFn, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones + // end "/{username}/{group_id}/{reponame}": view milestone, label, issue, pull, etc + + issueViewFn := func() { m.Get("", repo.Issues) m.Get("/{index}", repo.ViewIssue) - }, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypeExternalTracker)) - // end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker + } + m.Group("/{username}/group/{group_id}/{reponame}/{type:issues}", issueViewFn, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypeExternalTracker)) + m.Group("/{username}/{reponame}/{type:issues}", issueViewFn, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypeExternalTracker)) + // end "/{username}/{group_id}/{reponame}": issue/pull list, issue/pull view, external tracker - m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc + editIssueFn := func() { // edit issues, pulls, labels, milestones, etc m.Group("/issues", func() { m.Group("/new", func() { m.Combo("").Get(repo.NewIssue). @@ -1241,7 +1258,7 @@ func registerWebRoutes(m *web.Router) { }, reqUnitIssuesReader) addIssuesPullsUpdateRoutes := func() { - // for "/{username}/{reponame}/issues" or "/{username}/{reponame}/pulls" + // for "/{username}/{group_id}/{reponame}/issues" or "/{username}/{group_id}/{reponame}/pulls" m.Group("/{index}", func() { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) @@ -1317,10 +1334,12 @@ func registerWebRoutes(m *web.Router) { m.Post("/resolve_conversation", repo.SetShowOutdatedComments, repo.UpdateResolveConversation) }, reqUnitPullsReader) m.Post("/pull/{index}/target_branch", reqUnitPullsReader, repo.UpdatePullRequestTarget) - }, reqSignIn, context.RepoAssignment, context.RepoMustNotBeArchived()) - // end "/{username}/{reponame}": create or edit issues, pulls, labels, milestones + } + m.Group("/{username}/group/{group_id}/{reponame}", editIssueFn, reqSignIn, context.RepoAssignment, context.RepoMustNotBeArchived()) + m.Group("/{username}/{reponame}", editIssueFn, reqSignIn, context.RepoAssignment, context.RepoMustNotBeArchived()) + // end "/{username}/{group_id}/{reponame}": create or edit issues, pulls, labels, milestones - m.Group("/{username}/{reponame}", func() { // repo code (at least "code reader") + codeFn := func() { // repo code (at least "code reader") m.Group("", func() { m.Group("", func() { // "GET" requests only need "code reader" permission, "POST" requests need "code writer" permission. @@ -1368,10 +1387,12 @@ func registerWebRoutes(m *web.Router) { }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost) - }, reqSignIn, context.RepoAssignment, reqUnitCodeReader) - // end "/{username}/{reponame}": repo code + } + m.Group("/{username}/group/{group_id}/{reponame}", codeFn, reqSignIn, context.RepoAssignment, reqUnitCodeReader) + m.Group("/{username}/{reponame}", codeFn, reqSignIn, context.RepoAssignment, reqUnitCodeReader) + // end "/{username}/{group_id}/{reponame}": repo code - m.Group("/{username}/{reponame}", func() { // repo tags + repoTagFn := func() { // repo tags m.Group("/tags", func() { m.Get("", context.RepoRefByDefaultBranch() /* for the "commits" tab */, repo.TagsList) m.Get(".rss", feedEnabled, repo.TagsListFeedRSS) @@ -1379,10 +1400,12 @@ func registerWebRoutes(m *web.Router) { m.Get("/list", repo.GetTagList) }, ctxDataSet("EnableFeed", setting.Other.EnableFeed)) m.Post("/tags/delete", reqSignIn, reqRepoCodeWriter, context.RepoMustNotBeArchived(), repo.DeleteTag) - }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqUnitCodeReader) - // end "/{username}/{reponame}": repo tags + } + m.Group("/{username}/group/{group_id}/{reponame}", repoTagFn, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqUnitCodeReader) + m.Group("/{username}/{reponame}", repoTagFn, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqUnitCodeReader) + // end "/{username}/{group_id}/{reponame}": repo tags - m.Group("/{username}/{reponame}", func() { // repo releases + repoReleaseFn := func() { // repo releases m.Group("/releases", func() { m.Get("", repo.Releases) m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS) @@ -1403,25 +1426,33 @@ func registerWebRoutes(m *web.Router) { m.Get("/edit/*", repo.EditRelease) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache) - }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoReleaseReader) - // end "/{username}/{reponame}": repo releases + } + m.Group("/{username}/group/{group_id}/{reponame}", repoReleaseFn, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoReleaseReader) + m.Group("/{username}/{reponame}", repoReleaseFn, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoReleaseReader) + // end "/{username}/{group_id}/{reponame}": repo releases - m.Group("/{username}/{reponame}", func() { // to maintain compatibility with old attachments + repoAttachmentsFn := func() { // to maintain compatibility with old attachments m.Get("/attachments/{uuid}", repo.GetAttachment) - }, optSignIn, context.RepoAssignment) - // end "/{username}/{reponame}": compatibility with old attachments + } + m.Group("/{username}/group/{group_id}/{reponame}", repoAttachmentsFn, optSignIn, context.RepoAssignment) + m.Group("/{username}/{reponame}", repoAttachmentsFn, optSignIn, context.RepoAssignment) + // end "/{username}/{group_id}/{reponame}": compatibility with old attachments - m.Group("/{username}/{reponame}", func() { + repoTopicFn := func() { m.Post("/topics", repo.TopicsPost) - }, context.RepoAssignment, reqRepoAdmin, context.RepoMustNotBeArchived()) + } + m.Group("/{username}/group/{group_id}/{reponame}", repoTopicFn, context.RepoAssignment, reqRepoAdmin, context.RepoMustNotBeArchived()) + m.Group("/{username}/{reponame}", repoTopicFn, context.RepoAssignment, reqRepoAdmin, context.RepoMustNotBeArchived()) - m.Group("/{username}/{reponame}", func() { + repoPackageFn := func() { if setting.Packages.Enabled { m.Get("/packages", repo.Packages) } - }, optSignIn, context.RepoAssignment) + } + m.Group("/{username}/group/{group_id}/{reponame}", repoPackageFn, optSignIn, context.RepoAssignment) + m.Group("/{username}/{reponame}", repoPackageFn, optSignIn, context.RepoAssignment) - m.Group("/{username}/{reponame}/projects", func() { + repoProjectsFn := func() { m.Get("", repo.Projects) m.Get("/{id}", repo.ViewProject) m.Group("", func() { //nolint:dupl // duplicates lines 1034-1054 @@ -1445,10 +1476,12 @@ func registerWebRoutes(m *web.Router) { }) }) }, reqRepoProjectsWriter, context.RepoMustNotBeArchived()) - }, optSignIn, context.RepoAssignment, reqRepoProjectsReader, repo.MustEnableRepoProjects) - // end "/{username}/{reponame}/projects" + } + m.Group("/{username}/group/{group_id}/{reponame}/projects", repoProjectsFn, optSignIn, context.RepoAssignment, reqRepoProjectsReader, repo.MustEnableRepoProjects) + m.Group("/{username}/{reponame}/projects", repoProjectsFn, optSignIn, context.RepoAssignment, reqRepoProjectsReader, repo.MustEnableRepoProjects) + // end "/{username}/{group_id}/{reponame}/projects" - m.Group("/{username}/{reponame}/actions", func() { + repoActionsFn := func() { m.Get("", actions.List) m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile) m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) @@ -1477,10 +1510,12 @@ func registerWebRoutes(m *web.Router) { m.Group("/workflows/{workflow_name}", func() { m.Get("/badge.svg", actions.GetWorkflowBadge) }) - }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoActionsReader, actions.MustEnableActions) - // end "/{username}/{reponame}/actions" + } + m.Group("/{username}/group/{group_id}/{reponame}/actions", repoActionsFn, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoActionsReader, actions.MustEnableActions) + m.Group("/{username}/{reponame}/actions", repoActionsFn, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoActionsReader, actions.MustEnableActions) + // end "/{username}/{group_id}/{reponame}/actions" - m.Group("/{username}/{reponame}/wiki", func() { + repoWikiFn := func() { m.Combo(""). Get(repo.Wiki). Post(context.RepoMustNotBeArchived(), reqSignIn, reqUnitWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) @@ -1491,13 +1526,18 @@ func registerWebRoutes(m *web.Router) { m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff) m.Get("/raw/*", repo.WikiRaw) - }, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqUnitWikiReader, func(ctx *context.Context) { + } + m.Group("/{username}/group/{group_id}/{reponame}/wiki", repoWikiFn, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqUnitWikiReader, func(ctx *context.Context) { + ctx.Data["PageIsWiki"] = true + ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer) + }) + m.Group("/{username}/{reponame}/wiki", repoWikiFn, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqUnitWikiReader, func(ctx *context.Context) { ctx.Data["PageIsWiki"] = true ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer) }) - // end "/{username}/{reponame}/wiki" + // end "/{username}/{group_id}/{reponame}/wiki" - m.Group("/{username}/{reponame}/activity", func() { + activityFn := func() { // activity has its own permission checks m.Get("", repo.Activity) m.Get("/{period}", repo.Activity) @@ -1516,13 +1556,18 @@ func registerWebRoutes(m *web.Router) { m.Get("/data", repo.CodeFrequencyData) // "recent-commits" also uses the same data as "code-frequency" }) }, reqUnitCodeReader) - }, + } + m.Group("/{username}/group/{group_id}/{reponame}/activity", activityFn, + optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, + context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases), + ) + m.Group("/{username}/{reponame}/activity", activityFn, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases), ) - // end "/{username}/{reponame}/activity" + // end "/{username}/{group_id}/{reponame}/activity" - m.Group("/{username}/{reponame}", func() { + repoPullFn := func() { m.Get("/{type:pulls}", repo.Issues) m.Group("/{type:pulls}/{index}", func() { m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue) @@ -1549,10 +1594,12 @@ func registerWebRoutes(m *web.Router) { }, context.RepoMustNotBeArchived()) }) }) - }, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqUnitPullsReader) - // end "/{username}/{reponame}/pulls/{index}": repo pull request + } + m.Group("/{username}/group/{group_id}/{reponame}", repoPullFn, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqUnitPullsReader) + m.Group("/{username}/{reponame}", repoPullFn, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqUnitPullsReader) + // end "/{username}/{group_id}/{reponame}/pulls/{index}": repo pull request - m.Group("/{username}/{reponame}", func() { + repoCodeFn := func() { m.Group("/activity_author_data", func() { m.Get("", repo.ActivityAuthors) m.Get("/{period}", repo.ActivityAuthors) @@ -1631,21 +1678,25 @@ func registerWebRoutes(m *web.Router) { m.Get("/forks", repo.Forks) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) m.Post("/lastcommit/*", context.RepoRefByType(git.RefTypeCommit), repo.LastCommit) - }, optSignIn, context.RepoAssignment, reqUnitCodeReader) - // end "/{username}/{reponame}": repo code + } + m.Group("/{username}/group/{group_id}/{reponame}", repoCodeFn, optSignIn, context.RepoAssignment, reqUnitCodeReader) + m.Group("/{username}/{reponame}", repoCodeFn, optSignIn, context.RepoAssignment, reqUnitCodeReader) + // end "/{username}/{group_id}/{reponame}": repo code - m.Group("/{username}/{reponame}", func() { + fn := func() { m.Get("/stars", starsEnabled, repo.Stars) m.Get("/watchers", repo.Watchers) m.Get("/search", reqUnitCodeReader, repo.Search) m.Post("/action/{action:star|unstar}", reqSignIn, starsEnabled, repo.ActionStar) m.Post("/action/{action:watch|unwatch}", reqSignIn, repo.ActionWatch) m.Post("/action/{action:accept_transfer|reject_transfer}", reqSignIn, repo.ActionTransfer) - }, optSignIn, context.RepoAssignment) + } + m.Group("/{username}/group/{group_id}/{reponame}", fn, optSignIn, context.RepoAssignment) + m.Group("/{username}/{reponame}", fn, optSignIn, context.RepoAssignment) - common.AddOwnerRepoGitLFSRoutes(m, optSignInIgnoreCsrf, lfsServerEnabled) // "/{username}/{reponame}/{lfs-paths}": git-lfs support + common.AddOwnerRepoGitLFSRoutes(m, optSignInIgnoreCsrf, lfsServerEnabled) // "/{username}/{group_id}/{reponame}/{lfs-paths}": git-lfs support - addOwnerRepoGitHTTPRouters(m) // "/{username}/{reponame}/{git-paths}": git http support + addOwnerRepoGitHTTPRouters(m) // "/{username}/{group_id}/{reponame}/{git-paths}": git http support m.Group("/notifications", func() { m.Get("", user.Notifications) diff --git a/services/context/repo.go b/services/context/repo.go index afc6de9b1666d..e3d1d9ac5d039 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -12,6 +12,8 @@ import ( "net/http" "net/url" "path" + "regexp" + "strconv" "strings" "code.gitea.io/gitea/models/db" @@ -328,6 +330,7 @@ func ComposeGoGetImport(ctx context.Context, owner, repo string) string { func EarlyResponseForGoGetMeta(ctx *Context) { username := ctx.PathParam("username") reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git") + groupID := ctx.PathParamInt64("group_id") if username == "" || reponame == "" { ctx.PlainText(http.StatusBadRequest, "invalid repository path") return @@ -335,15 +338,17 @@ func EarlyResponseForGoGetMeta(ctx *Context) { var cloneURL string if setting.Repository.GoGetCloneURLProtocol == "ssh" { - cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, username, reponame) + cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, username, reponame, groupID) } else { - cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, username, reponame) + cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, username, reponame, groupID) } goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), cloneURL) htmlMeta := fmt.Sprintf(``, html.EscapeString(goImportContent)) ctx.PlainText(http.StatusOK, htmlMeta) } +var pathRegex = regexp.MustCompile(`(?i).*/[a-z\-0-9_]+/(\d+/)?[a-z\-0-9_]`) + // RedirectToRepo redirect to a differently-named repository func RedirectToRepo(ctx *Base, redirectRepoID int64) { ownerName := ctx.PathParam("username") @@ -355,6 +360,8 @@ func RedirectToRepo(ctx *Base, redirectRepoID int64) { ctx.HTTPError(http.StatusInternalServerError, "GetRepositoryByID") return } + pathRegex.ReplaceAllString(ctx.Req.URL.EscapedPath(), + url.PathEscape(repo.OwnerName)+"/$1"+url.PathEscape(repo.Name)) redirectPath := strings.Replace( ctx.Req.URL.EscapedPath(), @@ -421,6 +428,20 @@ func RepoAssignment(ctx *Context) { var err error userName := ctx.PathParam("username") repoName := ctx.PathParam("reponame") + group := ctx.PathParam("group_id") + var gid int64 + if group != "" { + gid, _ = strconv.ParseInt(group, 10, 64) + if gid == 0 { + q := ctx.Req.URL.RawQuery + if q != "" { + q = "?" + q + } + ctx.Redirect(strings.Replace(ctx.Link, "/0/", "/", 1)+q, 307) + return + } + group += "/" + } repoName = strings.TrimSuffix(repoName, ".git") if setting.Other.EnableFeed { ctx.Data["EnableFeed"] = true @@ -467,7 +488,7 @@ func RepoAssignment(ctx *Context) { redirectRepoName += originalRepoName[len(redirectRepoName)+5:] redirectPath := strings.Replace( ctx.Req.URL.EscapedPath(), - url.PathEscape(userName)+"/"+url.PathEscape(originalRepoName), + url.PathEscape(userName)+"/"+group+url.PathEscape(originalRepoName), url.PathEscape(userName)+"/"+url.PathEscape(redirectRepoName)+"/wiki", 1, ) @@ -479,7 +500,7 @@ func RepoAssignment(ctx *Context) { } // Get repository. - repo, err := repo_model.GetRepositoryByName(ctx, ctx.Repo.Owner.ID, repoName) + repo, err := repo_model.GetRepositoryByName(ctx, ctx.Repo.Owner.ID, gid, repoName) if err != nil { if repo_model.IsErrRepoNotExist(err) { redirectRepoID, err := repo_model.LookupRedirect(ctx, ctx.Repo.Owner.ID, repoName) @@ -499,6 +520,9 @@ func RepoAssignment(ctx *Context) { } return } + if repo.GroupID != gid { + ctx.NotFound(nil) + } repo.Owner = ctx.Repo.Owner repoAssignment(ctx, repo) diff --git a/services/convert/repo_group.go b/services/convert/repo_group.go new file mode 100644 index 0000000000000..25d39ffbec79e --- /dev/null +++ b/services/convert/repo_group.go @@ -0,0 +1,44 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package convert + +import ( + "context" + + group_model "code.gitea.io/gitea/models/group" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" +) + +func ToAPIGroup(ctx context.Context, g *group_model.Group, actor *user_model.User) (*api.Group, error) { + err := g.LoadAttributes(ctx) + if err != nil { + return nil, err + } + apiGroup := &api.Group{ + ID: g.ID, + Owner: ToUser(ctx, g.Owner, actor), + Name: g.Name, + Description: g.Description, + ParentGroupID: g.ParentGroupID, + Link: g.GroupLink(), + SortOrder: g.SortOrder, + AvatarURL: g.AvatarLink(ctx), + } + if apiGroup.NumSubgroups, err = group_model.CountGroups(ctx, &group_model.FindGroupsOptions{ + ParentGroupID: g.ID, + }); err != nil { + return nil, err + } + if _, apiGroup.NumRepos, err = repo_model.SearchRepositoryByCondition(ctx, repo_model.SearchRepoOptions{ + GroupID: g.ID, + Actor: actor, + OwnerID: g.OwnerID, + }, repo_model.AccessibleRepositoryCondition(actor, unit.TypeInvalid), true); err != nil { + return nil, err + } + return apiGroup, nil +} diff --git a/services/convert/repository.go b/services/convert/repository.go index a364591bb8f9b..d7bea22f20cf9 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -252,6 +252,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR Topics: util.SliceNilAsEmpty(repo.Topics), ObjectFormatName: repo.ObjectFormatName, Licenses: util.SliceNilAsEmpty(repoLicenses.StringList()), + GroupID: repo.GroupID, + GroupSortOrder: repo.GroupSortOrder, } } diff --git a/services/group/avatar.go b/services/group/avatar.go new file mode 100644 index 0000000000000..0d5eaf35959a4 --- /dev/null +++ b/services/group/avatar.go @@ -0,0 +1,71 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + "code.gitea.io/gitea/modules/avatar" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/storage" +) + +// UploadAvatar saves custom icon for group. +func UploadAvatar(ctx context.Context, g *group_model.Group, data []byte) error { + avatarData, err := avatar.ProcessAvatarImage(data) + if err != nil { + return err + } + + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + g.Avatar = avatar.HashAvatar(g.ID, data) + if err = UpdateGroup(ctx, g, &UpdateOptions{}); err != nil { + return fmt.Errorf("updateGroup: %w", err) + } + + if err = storage.SaveFrom(storage.Avatars, g.CustomAvatarRelativePath(), func(w io.Writer) error { + _, err = w.Write(avatarData) + return err + }); err != nil { + return fmt.Errorf("Failed to create dir %s: %w", g.CustomAvatarRelativePath(), err) + } + + return committer.Commit() +} + +// DeleteAvatar deletes the user's custom avatar. +func DeleteAvatar(ctx context.Context, g *group_model.Group) error { + aPath := g.CustomAvatarRelativePath() + log.Trace("DeleteAvatar[%d]: %s", g.ID, aPath) + + return db.WithTx(ctx, func(ctx context.Context) error { + hasAvatar := len(g.Avatar) > 0 + g.Avatar = "" + if _, err := db.GetEngine(ctx).ID(g.ID).Cols("avatar, use_custom_avatar").Update(g); err != nil { + return fmt.Errorf("DeleteAvatar: %w", err) + } + + if hasAvatar { + if err := storage.Avatars.Delete(aPath); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to remove %s: %w", aPath, err) + } + log.Warn("Deleting avatar %s but it doesn't exist", aPath) + } + } + + return nil + }) +} diff --git a/services/group/delete.go b/services/group/delete.go new file mode 100644 index 0000000000000..1a8b133bcb241 --- /dev/null +++ b/services/group/delete.go @@ -0,0 +1,87 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + repo_model "code.gitea.io/gitea/models/repo" +) + +func DeleteGroup(ctx context.Context, gid int64) error { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + sess := db.GetEngine(ctx) + + toDelete, err := group_model.GetGroupByID(ctx, gid) + if err != nil { + return err + } + + // remove team permissions and units for deleted group + if _, err = sess.Where("group_id = ?", gid).Delete(new(group_model.RepoGroupTeam)); err != nil { + return err + } + if _, err = sess.Where("group_id = ?", gid).Delete(new(group_model.RepoGroupUnit)); err != nil { + return err + } + + // move all repos in the deleted group to its immediate parent + repos, cnt, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{ + GroupID: gid, + }) + if err != nil { + return err + } + _, inParent, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{ + GroupID: toDelete.ParentGroupID, + }) + if err != nil { + return err + } + if cnt > 0 { + for i, repo := range repos { + repo.GroupID = toDelete.ParentGroupID + repo.GroupSortOrder = int(inParent + int64(i) + 1) + } + if _, err = sess.Where("group_id = ?", gid).Update(&repos); err != nil { + return err + } + } + + // move all child groups to the deleted group's immediate parent + childGroups, err := group_model.FindGroups(ctx, &group_model.FindGroupsOptions{ + ParentGroupID: gid, + }) + if err != nil { + return err + } + if len(childGroups) > 0 { + inParent, err = group_model.CountGroups(ctx, &group_model.FindGroupsOptions{ + ParentGroupID: toDelete.ParentGroupID, + }) + if err != nil { + return err + } + for i, group := range childGroups { + group.ParentGroupID = toDelete.ParentGroupID + group.SortOrder = int(inParent) + i + 1 + } + if _, err = sess.Where("parent_group_id = ?", gid).Update(&childGroups); err != nil { + return err + } + } + + // finally, delete the group itself + if _, err = sess.ID(gid).Delete(new(group_model.Group)); err != nil { + return err + } + return committer.Commit() +} diff --git a/services/group/group.go b/services/group/group.go new file mode 100644 index 0000000000000..0f2e9d7ce913e --- /dev/null +++ b/services/group/group.go @@ -0,0 +1,149 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "code.gitea.io/gitea/modules/gitrepo" + "context" + "errors" + "fmt" + "strings" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" +) + +func NewGroup(ctx context.Context, g *group_model.Group) error { + var err error + if len(g.Name) == 0 { + return util.NewInvalidArgumentErrorf("empty group name") + } + has, err := db.ExistByID[user_model.User](ctx, g.OwnerID) + if err != nil { + return err + } + if !has { + return organization.ErrOrgNotExist{ID: g.OwnerID} + } + g.LowerName = strings.ToLower(g.Name) + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + if err = db.Insert(ctx, g); err != nil { + return err + } + + if err = RecalculateGroupAccess(ctx, g, true); err != nil { + return err + } + + return committer.Commit() +} + +func MoveRepositoryToGroup(ctx context.Context, repo *repo_model.Repository, newGroupID int64, groupSortOrder int) error { + sess := db.GetEngine(ctx) + if newGroupID > 0 { + newGroup, err := group_model.GetGroupByID(ctx, newGroupID) + if err != nil { + return err + } + if newGroup.OwnerID != repo.OwnerID { + return fmt.Errorf("repo[%d]'s ownerID is not equal to new parent group[%d]'s owner ID", repo.ID, newGroup.ID) + } + } + repo.GroupID = newGroupID + repo.GroupSortOrder = groupSortOrder + cnt, err := sess. + Table("repository"). + ID(repo.ID). + MustCols("group_id"). + Update(repo) + log.Info("updated %d rows?", cnt) + return err +} + +type MoveGroupOptions struct { + NewParent, ItemID int64 + IsGroup bool + NewPos int +} + +func MoveGroupItem(ctx context.Context, opts MoveGroupOptions, doer *user_model.User) (err error) { + var committer db.Committer + ctx, committer, err = db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + var parentGroup *group_model.Group + if opts.NewParent > 0 { + parentGroup, err = group_model.GetGroupByID(ctx, opts.NewParent) + if err != nil { + return err + } + canAccessNewParent, err := parentGroup.CanAccess(ctx, doer) + if err != nil { + return err + } + if !canAccessNewParent { + return errors.New("cannot access new parent group") + } + + err = parentGroup.LoadSubgroups(ctx, false) + if err != nil { + return err + } + } + if opts.IsGroup { + var group *group_model.Group + group, err = group_model.GetGroupByID(ctx, opts.ItemID) + if err != nil { + return err + } + if opts.NewPos < 0 && parentGroup != nil { + opts.NewPos = len(parentGroup.Subgroups) + } + if group.ParentGroupID != opts.NewParent || group.SortOrder != opts.NewPos { + if err = group_model.MoveGroup(ctx, group, opts.NewParent, opts.NewPos); err != nil { + return err + } + if err = RecalculateGroupAccess(ctx, group, false); err != nil { + return err + } + } + } else { + var repo *repo_model.Repository + repo, err = repo_model.GetRepositoryByID(ctx, opts.ItemID) + if err != nil { + return err + } + if opts.NewPos < 0 { + var repoCount int64 + repoCount, err = repo_model.CountRepository(ctx, repo_model.SearchRepoOptions{ + GroupID: opts.NewParent, + }) + if err != nil { + return err + } + opts.NewPos = int(repoCount) + } + if repo.GroupID != opts.NewParent || repo.GroupSortOrder != opts.NewPos { + if err = gitrepo.RenameRepository(ctx, repo, repo_model.StorageRepo(repo_model.RelativePath(repo.OwnerName, repo.Name, opts.NewParent))); err != nil { + return err + } + if err = MoveRepositoryToGroup(ctx, repo, opts.NewParent, opts.NewPos); err != nil { + return err + } + } + } + return committer.Commit() +} diff --git a/services/group/group_test.go b/services/group/group_test.go new file mode 100644 index 0000000000000..d98ee68f39bc9 --- /dev/null +++ b/services/group/group_test.go @@ -0,0 +1,78 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +// group 12 is private +// team 23 are owners + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} + +func TestNewGroup(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + const groupName = "group x" + group := &group_model.Group{ + Name: groupName, + OwnerID: 3, + } + assert.NoError(t, NewGroup(db.DefaultContext, group)) + unittest.AssertExistsAndLoadBean(t, &group_model.Group{Name: groupName}) +} + +func TestMoveGroup(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + ID: 28, + }) + testfn := func(gid int64) { + cond := &group_model.FindGroupsOptions{ + ParentGroupID: 123, + OwnerID: 3, + } + origCount := unittest.GetCount(t, new(group_model.Group), cond.ToConds()) + + assert.NoError(t, MoveGroupItem(t.Context(), MoveGroupOptions{ + NewParent: 123, + ItemID: gid, + IsGroup: true, + NewPos: -1, + }, doer)) + unittest.AssertCountByCond(t, "repo_group", cond.ToConds(), origCount+1) + } + testfn(124) + testfn(132) + testfn(150) +} + +func TestMoveRepo(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + ID: 28, + }) + cond := repo_model.SearchRepositoryCondition(repo_model.SearchRepoOptions{ + GroupID: 123, + }) + origCount := unittest.GetCount(t, new(repo_model.Repository), cond) + + assert.NoError(t, MoveGroupItem(db.DefaultContext, MoveGroupOptions{ + NewParent: 123, + ItemID: 32, + IsGroup: false, + NewPos: -1, + }, doer)) + unittest.AssertCountByCond(t, "repository", cond, origCount+1) +} diff --git a/services/group/search.go b/services/group/search.go new file mode 100644 index 0000000000000..5194c7394c1fc --- /dev/null +++ b/services/group/search.go @@ -0,0 +1,174 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + "slices" + + "code.gitea.io/gitea/models/git" + group_model "code.gitea.io/gitea/models/group" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/services/convert" + repo_service "code.gitea.io/gitea/services/repository" + commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus" +) + +type WebSearchGroup struct { + Group *structs.Group `json:"group,omitempty"` + LatestCommitStatus *git.CommitStatus `json:"latest_commit_status"` + LocaleLatestCommitStatus string `json:"locale_latest_commit_status"` + Subgroups []*WebSearchGroup `json:"subgroups"` + Repos []*repo_service.WebSearchRepository `json:"repos"` +} + +type WebSearchResult struct { + OK bool `json:"ok"` + Data *WebSearchGroup `json:"data"` +} + +type WebSearchOptions struct { + Ctx context.Context + Locale translation.Locale + Recurse bool + Actor *user_model.User + RepoOpts repo_model.SearchRepoOptions + GroupOpts *group_model.FindGroupsOptions + OrgID int64 +} + +// results for root-level queries // + +type WebSearchGroupRoot struct { + Groups []*WebSearchGroup + Repos []*repo_service.WebSearchRepository +} + +type WebSearchGroupRootResult struct { + OK bool `json:"ok"` + Data *WebSearchGroupRoot `json:"data"` +} + +func ToWebSearchRepo(ctx context.Context, repo *repo_model.Repository) *repo_service.WebSearchRepository { + return &repo_service.WebSearchRepository{ + Repository: &structs.Repository{ + ID: repo.ID, + FullName: repo.FullName(), + Fork: repo.IsFork, + Private: repo.IsPrivate, + Template: repo.IsTemplate, + Mirror: repo.IsMirror, + Stars: repo.NumStars, + HTMLURL: repo.HTMLURL(ctx), + Link: repo.Link(), + Internal: !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePrivate, + GroupSortOrder: repo.GroupSortOrder, + GroupID: repo.GroupID, + }, + } +} + +func (w *WebSearchGroup) doLoadChildren(opts *WebSearchOptions) error { + opts.RepoOpts.OwnerID = opts.OrgID + opts.RepoOpts.GroupID = 0 + opts.GroupOpts.OwnerID = opts.OrgID + opts.GroupOpts.ParentGroupID = 0 + + if w.Group != nil { + opts.RepoOpts.GroupID = w.Group.ID + opts.RepoOpts.ListAll = true + opts.GroupOpts.ParentGroupID = w.Group.ID + opts.GroupOpts.ListAll = true + } + repos, _, err := repo_model.SearchRepository(opts.Ctx, opts.RepoOpts) + if err != nil { + return err + } + slices.SortStableFunc(repos, func(a, b *repo_model.Repository) int { + return a.GroupSortOrder - b.GroupSortOrder + }) + latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(opts.Ctx, repos) + if err != nil { + log.Error("FindReposLastestCommitStatuses: %v", err) + return err + } + latestIdx := -1 + for i, r := range repos { + wsr := ToWebSearchRepo(opts.Ctx, r) + if latestCommitStatuses[i] != nil { + wsr.LatestCommitStatus = latestCommitStatuses[i] + wsr.LocaleLatestCommitStatus = latestCommitStatuses[i].LocaleString(opts.Locale) + if latestIdx > -1 { + if latestCommitStatuses[i].UpdatedUnix.AsLocalTime().Unix() > latestCommitStatuses[latestIdx].UpdatedUnix.AsLocalTime().Unix() { + latestIdx = i + } + } else { + latestIdx = i + } + } + w.Repos = append(w.Repos, wsr) + } + if w.Group != nil && latestIdx > -1 { + w.LatestCommitStatus = latestCommitStatuses[latestIdx] + } + w.Subgroups = make([]*WebSearchGroup, 0) + groups, err := group_model.FindGroupsByCond(opts.Ctx, opts.GroupOpts, group_model.AccessibleGroupCondition(opts.Actor, unit.TypeInvalid, perm.AccessModeRead)) + if err != nil { + return err + } + for _, g := range groups { + toAppend, err := ToWebSearchGroup(g, opts) + if err != nil { + return err + } + w.Subgroups = append(w.Subgroups, toAppend) + } + + if opts.Recurse { + for _, sg := range w.Subgroups { + err = sg.doLoadChildren(opts) + if err != nil { + return err + } + } + } + return nil +} + +func ToWebSearchGroup(group *group_model.Group, opts *WebSearchOptions) (*WebSearchGroup, error) { + res := new(WebSearchGroup) + + res.Repos = make([]*repo_service.WebSearchRepository, 0) + res.Subgroups = make([]*WebSearchGroup, 0) + var err error + if group != nil { + if res.Group, err = convert.ToAPIGroup(opts.Ctx, group, opts.Actor); err != nil { + return nil, err + } + } + return res, nil +} + +func SearchRepoGroupWeb(group *group_model.Group, opts *WebSearchOptions) (*WebSearchResult, error) { + var res *WebSearchGroup + var err error + res, err = ToWebSearchGroup(group, opts) + if err != nil { + return nil, err + } + err = res.doLoadChildren(opts) + if err != nil { + return nil, err + } + return &WebSearchResult{ + Data: res, + OK: true, + }, nil +} diff --git a/services/group/team.go b/services/group/team.go new file mode 100644 index 0000000000000..691752a0e20af --- /dev/null +++ b/services/group/team.go @@ -0,0 +1,147 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + org_model "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/perm" + + "xorm.io/builder" +) + +func AddTeamToGroup(ctx context.Context, group *group_model.Group, tname string) error { + t, err := org_model.GetTeam(ctx, group.OwnerID, tname) + if err != nil { + return err + } + has := group_model.HasTeamGroup(ctx, group.OwnerID, t.ID, group.ID) + if has { + return fmt.Errorf("team '%s' already exists in group[%d]", tname, group.ID) + } + parentGroup, err := group_model.FindGroupTeamByTeamID(ctx, group.ID, t.ID) + if err != nil { + return err + } + mode := t.AccessMode + canCreateIn := t.CanCreateOrgRepo + if parentGroup != nil { + mode = max(t.AccessMode, parentGroup.AccessMode) + canCreateIn = parentGroup.CanCreateIn || t.CanCreateOrgRepo + } + if err = group.LoadParentGroup(ctx); err != nil { + return err + } + err = group_model.AddTeamGroup(ctx, group.ID, t.ID, group.ID, mode, canCreateIn) + if err != nil { + return err + } + + return nil +} + +func DeleteTeamFromGroup(ctx context.Context, group *group_model.Group, org int64, teamName string) error { + team, err := org_model.GetTeam(ctx, org, teamName) + if err != nil { + return err + } + return group_model.RemoveTeamGroup(ctx, org, team.ID, group.ID) +} + +func UpdateGroupTeam(ctx context.Context, gt *group_model.RepoGroupTeam) (err error) { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + sess := db.GetEngine(ctx) + + if _, err = sess.ID(gt.ID).AllCols().Update(gt); err != nil { + return fmt.Errorf("update: %w", err) + } + for _, unit := range gt.Units { + unit.TeamID = gt.TeamID + if _, err = sess. + Where("team_id=?", gt.TeamID). + And("group_id=?", gt.GroupID). + And("type = ?", unit.Type). + Update(unit); err != nil { + return err + } + } + return committer.Commit() +} + +// RecalculateGroupAccess recalculates team access to a group. +// should only be called if and only if a group was moved from another group. +func RecalculateGroupAccess(ctx context.Context, g *group_model.Group, isNew bool) error { + var err error + sess := db.GetEngine(ctx) + if err = g.LoadParentGroup(ctx); err != nil { + return err + } + var teams []*org_model.Team + if g.ParentGroup == nil { + teams, err = org_model.FindOrgTeams(ctx, g.OwnerID) + if err != nil { + return err + } + } else { + teams, err = org_model.GetTeamsWithAccessToGroup(ctx, g.OwnerID, g.ParentGroupID, perm.AccessModeRead) + } + for _, t := range teams { + var gt *group_model.RepoGroupTeam + if gt, err = group_model.FindGroupTeamByTeamID(ctx, g.ParentGroupID, t.ID); err != nil { + return err + } + if gt != nil { + if err = group_model.UpdateTeamGroup(ctx, g.OwnerID, t.ID, g.ID, gt.AccessMode, gt.CanCreateIn, isNew); err != nil { + return err + } + } else { + if err = group_model.UpdateTeamGroup(ctx, g.OwnerID, t.ID, g.ID, t.AccessMode, t.IsOwnerTeam() || t.AccessMode >= perm.AccessModeAdmin || t.CanCreateOrgRepo, isNew); err != nil { + return err + } + } + + if err = t.LoadUnits(ctx); err != nil { + return err + } + for _, u := range t.Units { + newAccessMode := u.AccessMode + if g.ParentGroup == nil { + gu, err := group_model.GetGroupUnit(ctx, g.ID, t.ID, u.Type) + if err != nil { + return err + } + newAccessMode = min(newAccessMode, gu.AccessMode) + } + if isNew { + if _, err = sess.Table("repo_group_unit").Insert(&group_model.RepoGroupUnit{ + Type: u.Type, + TeamID: t.ID, + GroupID: g.ID, + AccessMode: newAccessMode, + }); err != nil { + return err + } + } else { + if _, err = sess.Table("repo_group_unit").Where(builder.Eq{ + "type": u.Type, + "team_id": t.ID, + "group_id": g.ID, + }).Cols("access_mode").Update(&group_model.RepoGroupUnit{ + AccessMode: newAccessMode, + }); err != nil { + return err + } + } + } + } + return err +} diff --git a/services/group/update.go b/services/group/update.go new file mode 100644 index 0000000000000..9e64d48dbd3d8 --- /dev/null +++ b/services/group/update.go @@ -0,0 +1,35 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package group + +import ( + "context" + "strings" + + "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" + "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/structs" +) + +type UpdateOptions struct { + Name optional.Option[string] + Description optional.Option[string] + Visibility optional.Option[structs.VisibleType] +} + +func UpdateGroup(ctx context.Context, g *group_model.Group, opts *UpdateOptions) error { + if opts.Name.Has() { + g.Name = opts.Name.Value() + g.LowerName = strings.ToLower(g.Name) + } + if opts.Description.Has() { + g.Description = opts.Description.Value() + } + if opts.Visibility.Has() { + g.Visibility = opts.Visibility.Value() + } + _, err := db.GetEngine(ctx).ID(g.ID).Update(g) + return err +} diff --git a/services/issue/commit.go b/services/issue/commit.go index 963d0359fd35d..becaa33c7cb14 100644 --- a/services/issue/commit.go +++ b/services/issue/commit.go @@ -120,7 +120,7 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m for _, ref := range references.FindAllIssueReferences(c.Message) { // issue is from another repo if len(ref.Owner) > 0 && len(ref.Name) > 0 { - refRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ref.Owner, ref.Name) + refRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ref.Owner, ref.Name, ref.GroupID) if err != nil { if repo_model.IsErrRepoNotExist(err) { log.Warn("Repository referenced in commit but does not exist: %v", err) diff --git a/services/lfs/locks.go b/services/lfs/locks.go index 264001f0f984f..149ff612ec511 100644 --- a/services/lfs/locks.go +++ b/services/lfs/locks.go @@ -48,7 +48,7 @@ func handleLockListOut(ctx *context.Context, repo *repo_model.Repository, lock * func GetListLockHandler(ctx *context.Context) { rv := getRequestContext(ctx) - repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, rv.User, rv.Repo) + repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, rv.User, rv.Repo, rv.GroupID) if err != nil { log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err) ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`) @@ -135,9 +135,10 @@ func GetListLockHandler(ctx *context.Context) { func PostLockHandler(ctx *context.Context) { userName := ctx.PathParam("username") repoName := strings.TrimSuffix(ctx.PathParam("reponame"), ".git") + groupID := ctx.PathParamInt64("group_id") authorization := ctx.Req.Header.Get("Authorization") - repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName) + repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName, groupID) if err != nil { log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err) ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`) @@ -207,9 +208,10 @@ func PostLockHandler(ctx *context.Context) { func VerifyLockHandler(ctx *context.Context) { userName := ctx.PathParam("username") repoName := strings.TrimSuffix(ctx.PathParam("reponame"), ".git") + groupID := ctx.PathParamInt64("group_id") authorization := ctx.Req.Header.Get("Authorization") - repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName) + repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName, groupID) if err != nil { log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err) ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`) @@ -275,9 +277,10 @@ func VerifyLockHandler(ctx *context.Context) { func UnLockHandler(ctx *context.Context) { userName := ctx.PathParam("username") repoName := strings.TrimSuffix(ctx.PathParam("reponame"), ".git") + groupID := ctx.PathParamInt64("group_id") authorization := ctx.Req.Header.Get("Authorization") - repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName) + repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, userName, repoName, groupID) if err != nil { log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err) ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`) diff --git a/services/lfs/server.go b/services/lfs/server.go index 9f2e532f23ae8..800859d4c74e1 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -42,6 +42,7 @@ import ( type requestContext struct { User string Repo string + GroupID int64 Authorization string Method string } @@ -397,6 +398,7 @@ func getRequestContext(ctx *context.Context) *requestContext { return &requestContext{ User: ctx.PathParam("username"), Repo: strings.TrimSuffix(ctx.PathParam("reponame"), ".git"), + GroupID: ctx.PathParamInt64("group_id"), Authorization: ctx.Req.Header.Get("Authorization"), Method: ctx.Req.Method, } @@ -425,7 +427,7 @@ func getAuthenticatedMeta(ctx *context.Context, rc *requestContext, p lfs_module } func getAuthenticatedRepository(ctx *context.Context, rc *requestContext, requireWrite bool) *repo_model.Repository { - repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, rc.User, rc.Repo) + repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, rc.User, rc.Repo, rc.GroupID) if err != nil { log.Error("Unable to get repository: %s/%s Error: %v", rc.User, rc.Repo, err) writeStatus(ctx, http.StatusNotFound) diff --git a/services/markup/renderhelper_codepreview.go b/services/markup/renderhelper_codepreview.go index fa1eb824a2f54..58e4b44ec771c 100644 --- a/services/markup/renderhelper_codepreview.go +++ b/services/markup/renderhelper_codepreview.go @@ -31,7 +31,7 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie opts.LineStop = opts.LineStart + lineCount } - dbRepo, err := repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName) + dbRepo, err := repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName, opts.GroupID) if err != nil { return "", err } diff --git a/services/markup/renderhelper_issueicontitle.go b/services/markup/renderhelper_issueicontitle.go index 27b5595fa9998..103411976ddd2 100644 --- a/services/markup/renderhelper_issueicontitle.go +++ b/services/markup/renderhelper_issueicontitle.go @@ -27,7 +27,7 @@ func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTi textIssueIndex := fmt.Sprintf("(#%d)", opts.IssueIndex) dbRepo := webCtx.Repo.Repository if opts.OwnerName != "" { - dbRepo, err = repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName) + dbRepo, err = repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName, opts.GroupID) if err != nil { return "", err } diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index 605335d0f171a..59b70348f8035 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -109,7 +109,7 @@ func UpdatePackageIndexIfExists(ctx context.Context, doer, owner *user_model.Use // We do not want to force the creation of the repo here // cargo http index does not rely on the repo itself, // so if the repo does not exist, we just do nothing. - repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName) + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName, 0) if err != nil { if errors.Is(err, util.ErrNotExist) { return nil @@ -208,7 +208,7 @@ func addOrUpdatePackageIndex(ctx context.Context, t *files_service.TemporaryUplo } func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.User) (*repo_model.Repository, error) { - repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName) + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName, 0) if err != nil { if errors.Is(err, util.ErrNotExist) { repo, err = repo_service.CreateRepositoryDirectly(ctx, doer, owner, repo_service.CreateRepoOptions{ diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 2bd1c55de48ae..56e91ec12b24a 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -209,12 +209,12 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr } // DeleteUnadoptedRepository deletes unadopted repository files from the filesystem -func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, repoName string) error { +func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, repoName string, groupID int64) error { if err := repo_model.IsUsableRepoName(repoName); err != nil { return err } - repoPath := repo_model.RepoPath(u.Name, repoName) + repoPath := repo_model.RepoPath(u.Name, repoName, groupID) isExist, err := util.IsExist(repoPath) if err != nil { log.Error("Unable to check if %s exists. Error: %v", repoPath, err) @@ -227,7 +227,7 @@ func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, re } } - if exist, err := repo_model.IsRepositoryModelExist(ctx, u, repoName); err != nil { + if exist, err := repo_model.IsRepositoryModelExist(ctx, u, repoName, groupID); err != nil { return err } else if exist { return repo_model.ErrRepoAlreadyExist{ diff --git a/services/repository/branch.go b/services/repository/branch.go index 6e0065b2776d5..e7cb906a48c76 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -568,6 +568,7 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R PusherID: doer.ID, PusherName: doer.Name, RepoUserName: repo.OwnerName, + RepoGroupID: repo.GroupID, RepoName: repo.Name, }); err != nil { log.Error("Update: %v", err) diff --git a/services/repository/create.go b/services/repository/create.go index c415a24353894..29e4abee3f523 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -13,6 +13,7 @@ import ( "time" "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -50,6 +51,7 @@ type CreateRepoOptions struct { TrustModel repo_model.TrustModelType MirrorInterval string ObjectFormatName string + GroupID int64 } func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir string, opts CreateRepoOptions) error { @@ -227,6 +229,24 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User, if opts.ObjectFormatName == "" { opts.ObjectFormatName = git.Sha1ObjectFormat.Name() } + if opts.GroupID < 0 { + opts.GroupID = 0 + } + + // ensure that the parent group is owned by same user + if opts.GroupID > 0 { + newGroup, err := group_model.GetGroupByID(ctx, opts.GroupID) + if err != nil { + if group_model.IsErrGroupNotExist(err) { + opts.GroupID = 0 + } else { + return nil, err + } + } + if newGroup.OwnerID != owner.ID { + return nil, fmt.Errorf("group[%d] is not owned by user[%d]", newGroup.ID, owner.ID) + } + } repo := &repo_model.Repository{ OwnerID: owner.ID, @@ -248,6 +268,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User, DefaultBranch: opts.DefaultBranch, DefaultWikiBranch: setting.Repository.DefaultBranch, ObjectFormatName: opts.ObjectFormatName, + GroupID: opts.GroupID, } // 1 - create the repository database operations first @@ -339,7 +360,7 @@ func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *r return err } - has, err := repo_model.IsRepositoryModelExist(ctx, u, repo.Name) + has, err := repo_model.IsRepositoryModelExist(ctx, u, repo.Name, repo.GroupID) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { diff --git a/services/repository/lfs_test.go b/services/repository/lfs_test.go index 78ff8c853ee8f..f403a9dd2a9bb 100644 --- a/services/repository/lfs_test.go +++ b/services/repository/lfs_test.go @@ -27,7 +27,7 @@ func TestGarbageCollectLFSMetaObjects(t *testing.T) { err := storage.Init() assert.NoError(t, err) - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1") + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1", 0) assert.NoError(t, err) // add lfs object diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 0a3dc45339fd8..5243ae8b18ffd 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -27,7 +27,7 @@ import ( ) func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) { - wikiPath := repo_model.WikiPath(u.Name, opts.RepoName) + wikiPath := repo_model.WikiPath(u.Name, opts.RepoName, 0) wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr) if wikiRemotePath == "" { return "", nil @@ -72,7 +72,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repo *repo_model.Repository, opts migration.MigrateOptions, httpTransport *http.Transport, ) (*repo_model.Repository, error) { - repoPath := repo_model.RepoPath(u.Name, opts.RepoName) + repoPath := repo_model.RepoPath(u.Name, opts.RepoName, 0) if u.IsOrganization() { t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx) diff --git a/services/repository/push.go b/services/repository/push.go index 7c68a7f176308..7bf0eba1bf2e4 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -82,7 +82,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PushUpdates: %s/%s", optsList[0].RepoUserName, optsList[0].RepoName)) defer finished() - repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, optsList[0].RepoUserName, optsList[0].RepoName) + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, optsList[0].RepoUserName, optsList[0].RepoName, optsList[0].RepoGroupID) if err != nil { return fmt.Errorf("GetRepositoryByOwnerAndName failed: %w", err) } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 5ad63cca6763d..53952cf29d370 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -107,16 +107,16 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName } if repoRenamed { - if err := util.Rename(repo_model.RepoPath(newOwnerName, repo.Name), repo_model.RepoPath(oldOwnerName, repo.Name)); err != nil { + if err := util.Rename(repo_model.RepoPath(newOwnerName, repo.Name, 0), repo_model.RepoPath(oldOwnerName, repo.Name, repo.GroupID)); err != nil { log.Critical("Unable to move repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, - repo_model.RepoPath(newOwnerName, repo.Name), repo_model.RepoPath(oldOwnerName, repo.Name), err) + repo_model.RepoPath(newOwnerName, repo.Name, 0), repo_model.RepoPath(oldOwnerName, repo.Name, repo.GroupID), err) } } if wikiRenamed { - if err := util.Rename(repo_model.WikiPath(newOwnerName, repo.Name), repo_model.WikiPath(oldOwnerName, repo.Name)); err != nil { + if err := util.Rename(repo_model.WikiPath(newOwnerName, repo.Name, 0), repo_model.WikiPath(oldOwnerName, repo.Name, repo.GroupID)); err != nil { log.Critical("Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, - repo_model.WikiPath(newOwnerName, repo.Name), repo_model.WikiPath(oldOwnerName, repo.Name), err) + repo_model.WikiPath(newOwnerName, repo.Name, 0), repo_model.WikiPath(oldOwnerName, repo.Name, repo.GroupID), err) } } @@ -141,7 +141,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName newOwnerName = newOwner.Name // ensure capitalisation matches // Check if new owner has repository with same name. - if has, err := repo_model.IsRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil { + if has, err := repo_model.IsRepositoryModelOrDirExist(ctx, newOwner, repo.Name, 0); err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { return repo_model.ErrRepoAlreadyExist{ @@ -283,18 +283,18 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName return fmt.Errorf("Failed to create dir %s: %w", dir, err) } - if err := util.Rename(repo_model.RepoPath(oldOwner.Name, repo.Name), repo_model.RepoPath(newOwner.Name, repo.Name)); err != nil { + if err := util.Rename(repo_model.RepoPath(oldOwner.Name, repo.Name, 0), repo_model.RepoPath(newOwner.Name, repo.Name, 0)); err != nil { return fmt.Errorf("rename repository directory: %w", err) } repoRenamed = true // Rename remote wiki repository to new path and delete local copy. - wikiPath := repo_model.WikiPath(oldOwner.Name, repo.Name) + wikiPath := repo_model.WikiPath(oldOwner.Name, repo.Name, repo.GroupID) if isExist, err := util.IsExist(wikiPath); err != nil { log.Error("Unable to check if %s exists. Error: %v", wikiPath, err) return err } else if isExist { - if err := util.Rename(wikiPath, repo_model.WikiPath(newOwner.Name, repo.Name)); err != nil { + if err := util.Rename(wikiPath, repo_model.WikiPath(newOwner.Name, repo.Name, repo.GroupID)); err != nil { return fmt.Errorf("rename repository wiki: %w", err) } wikiRenamed = true @@ -343,7 +343,7 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR return err } - has, err := repo_model.IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName) + has, err := repo_model.IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName, repo.GroupID) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { @@ -354,7 +354,7 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR } if err = gitrepo.RenameRepository(ctx, repo, - repo_model.StorageRepo(repo_model.RelativePath(repo.OwnerName, newRepoName))); err != nil { + repo_model.StorageRepo(repo_model.RelativePath(repo.OwnerName, newRepoName, 0))); err != nil { return fmt.Errorf("rename repository directory: %w", err) } @@ -365,7 +365,7 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR return err } if isExist { - if err = util.Rename(wikiPath, repo_model.WikiPath(repo.Owner.Name, newRepoName)); err != nil { + if err = util.Rename(wikiPath, repo_model.WikiPath(repo.Owner.Name, newRepoName, repo.GroupID)); err != nil { return fmt.Errorf("rename repository wiki: %w", err) } } diff --git a/services/user/user.go b/services/user/user.go index c7252430dea03..f6ac3ed5a0e2c 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models/db" + group_model "code.gitea.io/gitea/models/group" "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" @@ -80,6 +81,10 @@ func RenameUser(ctx context.Context, u *user_model.User, newUserName string) err return err } + if err = group_model.UpdateGroupOwnerName(ctx, oldUserName, newUserName); err != nil { + return err + } + if err = user_model.NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil { return err } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 749d86901de93..b4a60870c6c56 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -1279,6 +1279,256 @@ } } }, + "/groups/{group_id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "gets a group in an organization", + "operationId": "groupGet", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the group to retrieve", + "name": "group_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Group" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "repositoryGroup" + ], + "summary": "Delete a repository group", + "operationId": "groupDelete", + "parameters": [ + { + "type": "string", + "description": "id of the group to delete", + "name": "group_id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "edits a group in an organization. only fields that are set will be changed.", + "operationId": "groupEdit", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the group to edit", + "name": "group_id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/EditGroupOption" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/Group" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, + "/groups/{group_id}/move": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "move a group to a different parent group", + "operationId": "groupMove", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the group to move", + "name": "group_id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/MoveGroupOption" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/Group" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, + "/groups/{group_id}/new": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "create a subgroup inside a group", + "operationId": "groupNewSubGroup", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the group to create a subgroup in", + "name": "group_id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/NewGroupOption" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/Group" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, + "/groups/{group_id}/repos": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "gets the repos contained within a group", + "operationId": "groupGetRepos", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the group containing the repositories", + "name": "group_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/RepositoryList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/groups/{group_id}/subgroups": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "gets the subgroups contained within a group", + "operationId": "groupGetSubGroups", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the parent group", + "name": "group_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/GroupList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/label/templates": { "get": { "produces": [ @@ -2806,6 +3056,49 @@ } } }, + "/orgs/{org}/groups/new": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "create a root-level repository group for an organization", + "operationId": "groupNew", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/NewGroupOption" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/Group" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, "/orgs/{org}/hooks": { "get": { "produces": [ @@ -23313,6 +23606,12 @@ "type": "string", "x-go-name": "Gitignores" }, + "group_id": { + "description": "GroupID of the group which will contain this repository. ignored if the repo owner is not an organization.", + "type": "integer", + "format": "int64", + "x-go-name": "GroupID" + }, "issue_labels": { "description": "Label-Set to use", "type": "string", @@ -23950,6 +24249,26 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "EditGroupOption": { + "description": "EditGroupOption represents options for editing a repository group", + "type": "object", + "properties": { + "description": { + "description": "the new description of the group", + "type": "string", + "x-go-name": "Description" + }, + "name": { + "description": "the new name of the group", + "type": "string", + "x-go-name": "Name" + }, + "visibility": { + "$ref": "#/definitions/VisibleType" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "EditHookOption": { "description": "EditHookOption options when modify one hook", "type": "object", @@ -25215,6 +25534,57 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "Group": { + "description": "Group represents a group of repositories and subgroups in an organization", + "type": "object", + "properties": { + "avatar_url": { + "type": "string", + "x-go-name": "AvatarURL" + }, + "description": { + "type": "string", + "x-go-name": "Description" + }, + "id": { + "type": "integer", + "format": "int64", + "x-go-name": "ID" + }, + "link": { + "type": "string", + "x-go-name": "Link" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "num_repos": { + "type": "integer", + "format": "int64", + "x-go-name": "NumRepos" + }, + "num_subgroups": { + "type": "integer", + "format": "int64", + "x-go-name": "NumSubgroups" + }, + "owner": { + "$ref": "#/definitions/User" + }, + "parentGroupID": { + "type": "integer", + "format": "int64", + "x-go-name": "ParentGroupID" + }, + "sort_order": { + "type": "integer", + "format": "int64", + "x-go-name": "SortOrder" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Hook": { "description": "Hook a hook is a web hook when one repository changed", "type": "object", @@ -26019,6 +26389,51 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "MoveGroupOption": { + "description": "MoveGroupOption represents options for changing a group or repo's parent and sort order", + "type": "object", + "required": [ + "newParent" + ], + "properties": { + "newParent": { + "description": "the new parent group. can be 0 to specify no parent", + "type": "integer", + "format": "int64", + "x-go-name": "NewParent" + }, + "newPos": { + "description": "the position of this group in its new parent", + "type": "integer", + "format": "int64", + "x-go-name": "NewPos" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "NewGroupOption": { + "description": "NewGroupOption represents options for creating a new group in an organization", + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "description": "the description of the newly created group", + "type": "string", + "x-go-name": "Description" + }, + "name": { + "description": "the name for the newly created group", + "type": "string", + "x-go-name": "Name" + }, + "visibility": { + "$ref": "#/definitions/VisibleType" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "NewIssuePinsAllowed": { "description": "NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed", "type": "object", @@ -27376,6 +27791,16 @@ "type": "string", "x-go-name": "FullName" }, + "group_id": { + "type": "integer", + "format": "int64", + "x-go-name": "GroupID" + }, + "group_sort_order": { + "type": "integer", + "format": "int64", + "x-go-name": "GroupSortOrder" + }, "has_actions": { "type": "boolean", "x-go-name": "HasActions" @@ -28455,6 +28880,12 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "VisibleType": { + "description": "VisibleType defines the visibility of user and org", + "type": "integer", + "format": "int64", + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "WatchInfo": { "description": "WatchInfo represents an API watch status of one repository", "type": "object", @@ -28987,6 +29418,21 @@ } } }, + "Group": { + "description": "Group", + "schema": { + "$ref": "#/definitions/Group" + } + }, + "GroupList": { + "description": "GroupList", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Group" + } + } + }, "Hook": { "description": "Hook", "schema": { @@ -29692,7 +30138,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/LockIssueOption" + "$ref": "#/definitions/MoveGroupOption" } }, "redirect": { diff --git a/tests/integration/actions_delete_run_test.go b/tests/integration/actions_delete_run_test.go index 22f9a1f7409da..0879dcabf878f 100644 --- a/tests/integration/actions_delete_run_test.go +++ b/tests/integration/actions_delete_run_test.go @@ -119,7 +119,7 @@ jobs: runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+testCase.treePath, testCase.fileContent) - createWorkflowFile(t, token, user2.Name, apiRepo.Name, testCase.treePath, opts) + createWorkflowFile(t, token, user2.Name, apiRepo.Name, testCase.treePath, apiRepo.GroupID, opts) runIndex := "" for i := 0; i < len(testCase.outcomes); i++ { diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index 4f4456a4e5041..c5a56f3149f8d 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -141,7 +141,7 @@ jobs: t.Run("test "+tc.treePath, func(t *testing.T) { // create the workflow file opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+tc.treePath, tc.fileContent) - fileResp := createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) + fileResp := createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, apiRepo.GroupID, opts) // fetch and execute task for i := 0; i < len(tc.outcomes); i++ { @@ -153,7 +153,7 @@ jobs: } // check result - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/tasks", user2.Name, apiRepo.Name)). + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/actions/tasks", user2.Name, apiRepo.GroupID, apiRepo.Name)). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var actionTaskRespAfter api.ActionTaskResponse @@ -323,7 +323,7 @@ jobs: for _, tc := range testCases { t.Run("test "+tc.treePath, func(t *testing.T) { opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+tc.treePath, tc.fileContent) - createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) + createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, apiRepo.GroupID, opts) for i := 0; i < len(tc.outcomes); i++ { task := runner.fetchTask(t) @@ -357,7 +357,7 @@ func TestActionsGiteaContext(t *testing.T) { apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, baseRepo.GroupID, auth_model.AccessTokenScopeWriteRepository) runner := newMockRunner() runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) @@ -373,7 +373,7 @@ jobs: - run: echo 'test the pull' ` opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, baseRepo.GroupID, opts) // user2 creates a pull request doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ FileOptions: api.FileOptions{ @@ -441,7 +441,7 @@ func TestActionsGiteaContextEphemeral(t *testing.T) { apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, baseRepo.GroupID, auth_model.AccessTokenScopeWriteRepository) runner := newMockRunner() runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, true) @@ -465,7 +465,7 @@ jobs: - run: echo 'test the pull' ` opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, baseRepo.GroupID, opts) // user2 creates a pull request doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ FileOptions: api.FileOptions{ @@ -617,8 +617,8 @@ func getWorkflowCreateFileOptions(u *user_model.User, branch, msg, content strin } } -func createWorkflowFile(t *testing.T, authToken, ownerName, repoName, treePath string, opts *api.CreateFileOptions) *api.FileResponse { - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", ownerName, repoName, treePath), opts). +func createWorkflowFile(t *testing.T, authToken, ownerName, repoName, treePath string, groupID int64, opts *api.CreateFileOptions) *api.FileResponse { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", ownerName, groupID, repoName, treePath), opts). AddTokenAuth(authToken) resp := MakeRequest(t, req, http.StatusCreated) var fileResponse api.FileResponse diff --git a/tests/integration/actions_log_test.go b/tests/integration/actions_log_test.go index 503bda97c93fa..a88056cc59558 100644 --- a/tests/integration/actions_log_test.go +++ b/tests/integration/actions_log_test.go @@ -168,7 +168,7 @@ jobs: // create the workflow file opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+tc.treePath, tc.fileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts) + createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, repo.GroupID, opts) // fetch and execute tasks for jobIndex, outcome := range tc.outcome { @@ -206,7 +206,7 @@ jobs: jobID := jobs[jobIndex].ID // download task logs from API and check content - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, jobID)). + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/actions/jobs/%d/logs", user2.Name, repo.GroupID, repo.Name, jobID)). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) logTextLines = strings.Split(strings.TrimSpace(resp.Body.String()), "\n") diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 088491d5705f4..9dd439603e7b4 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -61,7 +61,7 @@ func TestPullRequestTargetEvent(t *testing.T) { assert.NotEmpty(t, baseRepo) // add user4 as the collaborator - ctx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + ctx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, baseRepo.GroupID, auth_model.AccessTokenScopeWriteRepository) t.Run("AddUser4AsCollaboratorWithReadAccess", doAPIAddCollaborator(ctx, "user4", perm.AccessModeRead)) // create the forked repo @@ -487,7 +487,7 @@ func TestPullRequestCommitStatusEvent(t *testing.T) { assert.NotEmpty(t, repo) // add user4 as the collaborator - ctx := NewAPITestContext(t, repo.OwnerName, repo.Name, auth_model.AccessTokenScopeWriteRepository) + ctx := NewAPITestContext(t, repo.OwnerName, repo.Name, repo.GroupID, auth_model.AccessTokenScopeWriteRepository) t.Run("AddUser4AsCollaboratorWithReadAccess", doAPIAddCollaborator(ctx, "user4", perm.AccessModeRead)) // add the workflow file to the repo @@ -1385,7 +1385,7 @@ func TestClosePullRequestWithPath(t *testing.T) { // create the base repo apiBaseRepo := createActionsTestRepo(t, user2Token, "close-pull-request-with-path", false) baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, baseRepo.GroupID, auth_model.AccessTokenScopeWriteRepository) // init the workflow wfTreePath := ".gitea/workflows/pull.yml" @@ -1403,10 +1403,10 @@ jobs: - run: echo 'Hello World' ` opts1 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts1) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, baseRepo.GroupID, opts1) // user4 forks the repo - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name), + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/forks", baseRepo.OwnerName, baseRepo.GroupID, baseRepo.Name), &api.CreateForkOption{ Name: util.ToPointer("close-pull-request-with-path-fork"), }).AddTokenAuth(user4Token) @@ -1414,7 +1414,7 @@ jobs: var apiForkRepo api.Repository DecodeJSON(t, resp, &apiForkRepo) forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID}) - user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository) + user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, forkRepo.GroupID, auth_model.AccessTokenScopeWriteRepository) // user4 creates a pull request to add file "app/main.go" doAPICreateFile(user4APICtx, "app/main.go", &api.CreateFileOptions{ diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 16e1f2812e596..f4e4eb6264d81 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -4,6 +4,7 @@ package integration import ( + "fmt" "net/http" "net/http/httptest" "net/url" @@ -113,7 +114,7 @@ func TestAPICreateBranch(t *testing.T) { func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { username := "user2" - ctx := NewAPITestContext(t, username, "my-noo-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + ctx := NewAPITestContext(t, username, "my-noo-repo", 0, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() t.Run("CreateRepo", doAPICreateRepository(ctx, false)) @@ -164,14 +165,18 @@ func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { for _, test := range testCases { session := ctx.Session t.Run(test.NewBranch, func(t *testing.T) { - testAPICreateBranch(t, session, "user2", "my-noo-repo", test.OldBranch, test.NewBranch, test.ExpectedHTTPStatus) + testAPICreateBranch(t, session, 0, "user2", "my-noo-repo", test.OldBranch, test.NewBranch, test.ExpectedHTTPStatus) }) } } -func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBranch, newBranch string, status int) bool { +func testAPICreateBranch(t testing.TB, session *TestSession, groupID int64, user, repo, oldBranch, newBranch string, status int) bool { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+user+"/"+repo+"/branches", &api.CreateBranchRepoOption{ + var groupSegment string + if groupID > 0 { + groupSegment = fmt.Sprintf("%d/", groupID) + } + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+user+"/"+groupSegment+repo+"/branches", &api.CreateBranchRepoOption{ BranchName: newBranch, OldBranchName: oldBranch, }).AddTokenAuth(token) @@ -310,10 +315,10 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) { assert.NoError(t, err) onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + ctx := NewAPITestContext(t, "user2", "repo1", 0, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() - testAPICreateBranch(t, ctx.Session, "user2", "repo1", "", "new_branch", http.StatusCreated) + testAPICreateBranch(t, ctx.Session, 0, "user2", "repo1", "", "new_branch", http.StatusCreated) }) branches, err = db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ diff --git a/tests/integration/api_comment_attachment_test.go b/tests/integration/api_comment_attachment_test.go index 623467938af83..29e3e1b89cafb 100644 --- a/tests/integration/api_comment_attachment_test.go +++ b/tests/integration/api_comment_attachment_test.go @@ -38,17 +38,17 @@ func TestAPIGetCommentAttachment(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID, attachment.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) }) session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID, attachment.ID). AddTokenAuth(token) session.MakeRequest(t, req, http.StatusOK) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID, attachment.ID). AddTokenAuth(token) resp := session.MakeRequest(t, req, http.StatusOK) @@ -73,7 +73,7 @@ func TestAPIListCommentAttachments(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/comments/%d/assets", repoOwner.Name, repo.GroupID, repo.Name, comment.ID). AddTokenAuth(token) resp := session.MakeRequest(t, req, http.StatusOK) @@ -109,7 +109,7 @@ func TestAPICreateCommentAttachment(t *testing.T) { err = writer.Close() assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/comments/%d/assets", repoOwner.Name, repo.GroupID, repo.Name, comment.ID), body). AddTokenAuth(token). SetHeader("Content-Type", writer.FormDataContentType()) resp := session.MakeRequest(t, req, http.StatusCreated) @@ -141,7 +141,7 @@ func TestAPICreateCommentAttachmentWithUnallowedFile(t *testing.T) { err = writer.Close() assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/comments/%d/assets", repoOwner.Name, repo.GroupID, repo.Name, comment.ID), body). AddTokenAuth(token). SetHeader("Content-Type", writer.FormDataContentType()) @@ -206,7 +206,7 @@ func TestAPIDeleteCommentAttachment(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID)). + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID, attachment.ID)). AddTokenAuth(token) session.MakeRequest(t, req, http.StatusNoContent) diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 9842c358f61a2..9abff6ea5b217 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -31,7 +31,7 @@ func TestAPIListRepoComments(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments", repoOwner.Name, repo.Name)) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/comments", repoOwner.Name, repo.GroupID, repo.Name)) req := NewRequest(t, "GET", link.String()) resp := MakeRequest(t, req, http.StatusOK) @@ -77,7 +77,7 @@ func TestAPIListIssueComments(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments", repoOwner.Name, repo.Name, issue.Index). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/%d/comments", repoOwner.Name, repo.GroupID, repo.Name, issue.Index). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -116,7 +116,7 @@ func TestAPICreateComment(t *testing.T) { issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/comments", repo.OwnerName, repo.GroupID, repo.Name, issue.Index), map[string]string{ "body": commentBody, }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) MakeRequest(t, req, http.StatusForbidden) @@ -129,7 +129,7 @@ func TestAPICreateComment(t *testing.T) { issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 13}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/comments", repo.OwnerName, repo.GroupID, repo.Name, issue.Index), map[string]string{ "body": commentBody, }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) MakeRequest(t, req, http.StatusForbidden) @@ -145,9 +145,9 @@ func TestAPIGetComment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/comments/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID) MakeRequest(t, req, http.StatusOK) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/comments/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -184,7 +184,7 @@ func TestAPIGetSystemUserComment(t *testing.T) { }) assert.NoError(t, err) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/comments/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID) resp := MakeRequest(t, req, http.StatusOK) var apiComment api.Comment @@ -252,13 +252,13 @@ func TestAPIDeleteComment(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID). + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%d/%s/issues/comments/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) }) token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID). + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%d/%s/issues/comments/%d", repoOwner.Name, repo.GroupID, repo.Name, comment.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) @@ -274,7 +274,7 @@ func TestAPIListIssueTimeline(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) // make request - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/timeline", repoOwner.Name, repo.Name, issue.Index) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/issues/%d/timeline", repoOwner.Name, repo.GroupID, repo.Name, issue.Index) resp := MakeRequest(t, req, http.StatusOK) // check if lens of list returned by API and diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index b30cdfd0fc3b1..c6266470eb5f7 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -29,9 +29,10 @@ type APITestContext struct { Token string Username string ExpectedCode int + GroupID int64 } -func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext { +func NewAPITestContext(t *testing.T, username, reponame string, groupID int64, scope ...auth.AccessTokenScope) APITestContext { session := loginUser(t, username) if len(scope) == 0 { // FIXME: legacy logic: no scope means all @@ -41,6 +42,7 @@ func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.Ac return APITestContext{ Session: session, Token: token, + GroupID: groupID, Username: username, Reponame: reponame, } @@ -60,6 +62,7 @@ func doAPICreateRepository(ctx APITestContext, empty bool, callback ...func(*tes Template: true, Gitignores: "", License: "WTFPL", + GroupID: ctx.GroupID, Readme: "Default", } req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", createRepoOption). diff --git a/tests/integration/api_issue_attachment_test.go b/tests/integration/api_issue_attachment_test.go index 6806d27df26f4..288d62e4ebfb1 100644 --- a/tests/integration/api_issue_attachment_test.go +++ b/tests/integration/api_issue_attachment_test.go @@ -33,7 +33,7 @@ func TestAPIGetIssueAttachment(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID)). + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, issue.Index, attachment.ID)). AddTokenAuth(token) resp := session.MakeRequest(t, req, http.StatusOK) apiAttachment := new(api.Attachment) @@ -53,7 +53,7 @@ func TestAPIListIssueAttachments(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index)). + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/assets", repoOwner.Name, repo.GroupID, repo.Name, issue.Index)). AddTokenAuth(token) resp := session.MakeRequest(t, req, http.StatusOK) apiAttachment := new([]api.Attachment) @@ -85,7 +85,7 @@ func TestAPICreateIssueAttachment(t *testing.T) { err = writer.Close() assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/assets", repoOwner.Name, repo.GroupID, repo.Name, issue.Index), body). AddTokenAuth(token) req.Header.Add("Content-Type", writer.FormDataContentType()) resp := session.MakeRequest(t, req, http.StatusCreated) @@ -116,7 +116,7 @@ func TestAPICreateIssueAttachmentWithUnallowedFile(t *testing.T) { err = writer.Close() assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/assets", repoOwner.Name, repo.GroupID, repo.Name, issue.Index), body). AddTokenAuth(token) req.Header.Add("Content-Type", writer.FormDataContentType()) @@ -159,7 +159,7 @@ func TestAPIEditIssueAttachmentWithUnallowedFile(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) filename := "file.bad" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, issue.Index, attachment.ID) req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ "name": filename, }).AddTokenAuth(token) @@ -178,7 +178,7 @@ func TestAPIDeleteIssueAttachment(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID)). + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, issue.Index, attachment.ID)). AddTokenAuth(token) session.MakeRequest(t, req, http.StatusNoContent) diff --git a/tests/integration/api_issue_config_test.go b/tests/integration/api_issue_config_test.go index ad399654437f8..24d650d230928 100644 --- a/tests/integration/api_issue_config_test.go +++ b/tests/integration/api_issue_config_test.go @@ -150,7 +150,7 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issue_config/validate", owner.Name, repo.GroupID, repo.Name) t.Run("Valid", func(t *testing.T) { req := NewRequest(t, "GET", urlStr) diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index 4324fd37d95c8..4899bccbc0fbc 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -26,7 +26,7 @@ func TestAPIModifyLabels(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner.Name, repo.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/labels", owner.Name, repo.GroupID, repo.Name) // CreateLabel req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ @@ -62,7 +62,7 @@ func TestAPIModifyLabels(t *testing.T) { assert.Len(t, apiLabels, 2) // GetLabel - singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner.Name, repo.Name, dbLabel.ID) + singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/labels/%d", owner.Name, repo.GroupID, repo.Name, dbLabel.ID) req = NewRequest(t, "GET", singleURLStr). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -127,7 +127,7 @@ func TestAPIAddIssueLabelsWithLabelNames(t *testing.T) { token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteIssue) // add the org label and the repo label to the issue - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner.Name, repo.Name, issue.Index) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/labels", owner.Name, repo.GroupID, repo.Name, issue.Index) req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ Labels: []any{repoLabel.Name, orgLabel.Name}, }).AddTokenAuth(token) diff --git a/tests/integration/api_issue_lock_test.go b/tests/integration/api_issue_lock_test.go index 47b1f2cf0da6f..6f8ddbf7af208 100644 --- a/tests/integration/api_issue_lock_test.go +++ b/tests/integration/api_issue_lock_test.go @@ -27,7 +27,7 @@ func TestAPILockIssue(t *testing.T) { assert.False(t, issueBefore.IsLocked) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/lock", owner.Name, repo.Name, issueBefore.Index) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/lock", owner.Name, repo.GroupID, repo.Name, issueBefore.Index) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) @@ -50,7 +50,7 @@ func TestAPILockIssue(t *testing.T) { issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/lock", owner.Name, repo.Name, issueBefore.Index) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/lock", owner.Name, repo.GroupID, repo.Name, issueBefore.Index) session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) diff --git a/tests/integration/api_issue_milestone_test.go b/tests/integration/api_issue_milestone_test.go index 1196c8d358d67..d27dc99b9e4cc 100644 --- a/tests/integration/api_issue_milestone_test.go +++ b/tests/integration/api_issue_milestone_test.go @@ -34,7 +34,7 @@ func TestAPIIssuesMilestone(t *testing.T) { // update values of issue milestoneState := "closed" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d", owner.Name, repo.Name, milestone.ID) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/milestones/%d", owner.Name, repo.GroupID, repo.Name, milestone.ID) req := NewRequestWithJSON(t, "PATCH", urlStr, structs.EditMilestoneOption{ State: &milestoneState, }).AddTokenAuth(token) @@ -50,7 +50,7 @@ func TestAPIIssuesMilestone(t *testing.T) { DecodeJSON(t, resp, &apiMilestone2) assert.EqualValues(t, "closed", apiMilestone2.State) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/milestones", owner.Name, repo.Name), structs.CreateMilestoneOption{ + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/milestones", owner.Name, repo.GroupID, repo.Name), structs.CreateMilestoneOption{ Title: "wow", Description: "closed one", State: "closed", @@ -62,27 +62,27 @@ func TestAPIIssuesMilestone(t *testing.T) { assert.Nil(t, apiMilestone.Deadline) var apiMilestones []structs.Milestone - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s", owner.Name, repo.Name, "all")). + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/milestones?state=%s", owner.Name, repo.GroupID, repo.Name, "all")). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestones) assert.Len(t, apiMilestones, 4) assert.Nil(t, apiMilestones[0].Deadline) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%s", owner.Name, repo.Name, apiMilestones[2].Title)). + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/milestones/%s", owner.Name, repo.GroupID, repo.Name, apiMilestones[2].Title)). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestone) assert.Equal(t, apiMilestones[2], apiMilestone) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&name=%s", owner.Name, repo.Name, "all", "milestone2")). + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/milestones?state=%s&name=%s", owner.Name, repo.GroupID, repo.Name, "all", "milestone2")). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestones) assert.Len(t, apiMilestones, 1) assert.Equal(t, int64(2), apiMilestones[0].ID) - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d", owner.Name, repo.Name, apiMilestone.ID)). + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/milestones/%d", owner.Name, repo.GroupID, repo.Name, apiMilestone.ID)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) } diff --git a/tests/integration/api_issue_pin_test.go b/tests/integration/api_issue_pin_test.go index c1bfa5aa0ebea..445f4d3116855 100644 --- a/tests/integration/api_issue_pin_test.go +++ b/tests/integration/api_issue_pin_test.go @@ -32,12 +32,12 @@ func TestAPIPinIssue(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/pin", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Check if the Issue is pinned - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)) resp := MakeRequest(t, req, http.StatusOK) var issueAPI api.Issue DecodeJSON(t, resp, &issueAPI) @@ -57,24 +57,24 @@ func TestAPIUnpinIssue(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/pin", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Check if the Issue is pinned - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)) resp := MakeRequest(t, req, http.StatusOK) var issueAPI api.Issue DecodeJSON(t, resp, &issueAPI) assert.Equal(t, 1, issueAPI.PinOrder) // Unpin the Issue - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/pin", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Check if the Issue is no longer pinned - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &issueAPI) assert.Equal(t, 0, issueAPI.PinOrder) @@ -94,36 +94,36 @@ func TestAPIMoveIssuePin(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the first Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/pin", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Check if the first Issue is pinned at position 1 - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)) resp := MakeRequest(t, req, http.StatusOK) var issueAPI api.Issue DecodeJSON(t, resp, &issueAPI) assert.Equal(t, 1, issueAPI.PinOrder) // Pin the second Issue - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue2.Index)). + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/pin", repo.OwnerName, repo.GroupID, repo.Name, issue2.Index)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Move the first Issue to position 2 - req = NewRequest(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin/2", repo.OwnerName, repo.Name, issue.Index)). + req = NewRequest(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/pin/2", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Check if the first Issue is pinned at position 2 - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)) resp = MakeRequest(t, req, http.StatusOK) var issueAPI3 api.Issue DecodeJSON(t, resp, &issueAPI3) assert.Equal(t, 2, issueAPI3.PinOrder) // Check if the second Issue is pinned at position 1 - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue2.Index)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d", repo.OwnerName, repo.GroupID, repo.Name, issue2.Index)) resp = MakeRequest(t, req, http.StatusOK) var issueAPI4 api.Issue DecodeJSON(t, resp, &issueAPI4) @@ -143,12 +143,12 @@ func TestAPIListPinnedIssues(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/pin", repo.OwnerName, repo.GroupID, repo.Name, issue.Index)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // Check if the Issue is in the List - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/pinned", repo.OwnerName, repo.Name)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/pinned", repo.OwnerName, repo.GroupID, repo.Name)) resp := MakeRequest(t, req, http.StatusOK) var issueList []api.Issue DecodeJSON(t, resp, &issueList) @@ -164,7 +164,7 @@ func TestAPIListPinnedPullrequests(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/pinned", repo.OwnerName, repo.Name)) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/pinned", repo.OwnerName, repo.GroupID, repo.Name)) resp := MakeRequest(t, req, http.StatusOK) var prList []api.PullRequest DecodeJSON(t, resp, &prList) @@ -178,7 +178,7 @@ func TestAPINewPinAllowed(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/new_pin_allowed", owner.Name, repo.Name)) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/new_pin_allowed", owner.Name, repo.GroupID, repo.Name)) resp := MakeRequest(t, req, http.StatusOK) var newPinsAllowed api.NewIssuePinsAllowed diff --git a/tests/integration/api_issue_reaction_test.go b/tests/integration/api_issue_reaction_test.go index 17e9f7aed5a91..4127f9d5b6e16 100644 --- a/tests/integration/api_issue_reaction_test.go +++ b/tests/integration/api_issue_reaction_test.go @@ -119,7 +119,7 @@ func TestAPICommentReactions(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", repoOwner.Name, repo.Name, comment.ID) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/comments/%d/reactions", repoOwner.Name, repo.GroupID, repo.Name, comment.ID) req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ Reaction: "+1", }).AddTokenAuth(token) diff --git a/tests/integration/api_issue_subscription_test.go b/tests/integration/api_issue_subscription_test.go index 74ba171c01970..d48e10d486836 100644 --- a/tests/integration/api_issue_subscription_test.go +++ b/tests/integration/api_issue_subscription_test.go @@ -37,7 +37,7 @@ func TestAPIIssueSubscriptions(t *testing.T) { testSubscription := func(issue *issues_model.Issue, isWatching bool) { issueRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/check", issueRepo.OwnerName, issueRepo.Name, issue.Index)). + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/subscriptions/check", issueRepo.OwnerName, issueRepo.GroupID, issueRepo.Name, issue.Index)). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) wi := new(api.WatchInfo) @@ -57,7 +57,7 @@ func TestAPIIssueSubscriptions(t *testing.T) { testSubscription(issue5, false) issue1Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue1.RepoID}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s", issue1Repo.OwnerName, issue1Repo.Name, issue1.Index, owner.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/subscriptions/%s", issue1Repo.OwnerName, issue1Repo.GroupID, issue1Repo.Name, issue1.Index, owner.Name) req := NewRequest(t, "DELETE", urlStr). AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) @@ -69,7 +69,7 @@ func TestAPIIssueSubscriptions(t *testing.T) { testSubscription(issue1, false) issue5Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue5.RepoID}) - urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s", issue5Repo.OwnerName, issue5Repo.Name, issue5.Index, owner.Name) + urlStr = fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d/subscriptions/%s", issue5Repo.OwnerName, issue5Repo.GroupID, issue5Repo.Name, issue5.Index, owner.Name) req = NewRequest(t, "PUT", urlStr). AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index 370c90a10032a..b64b51802687a 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -33,7 +33,7 @@ func TestAPIListIssues(t *testing.T) { session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name)) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues", owner.Name, repo.GroupID, repo.Name)) link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode() resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) @@ -83,7 +83,7 @@ func TestAPIListIssuesPublicOnly(t *testing.T) { session := loginUser(t, owner1.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner1.Name, repo1.Name)) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues", owner1.Name, repo1.GroupID, repo1.Name)) link.RawQuery = url.Values{"state": {"all"}}.Encode() req := NewRequest(t, "GET", link.String()).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) @@ -93,7 +93,7 @@ func TestAPIListIssuesPublicOnly(t *testing.T) { session = loginUser(t, owner2.Name) token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner2.Name, repo2.Name)) + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues", owner2.Name, repo2.GroupID, repo2.Name)) link.RawQuery = url.Values{"state": {"all"}}.Encode() req = NewRequest(t, "GET", link.String()).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) @@ -112,7 +112,7 @@ func TestAPICreateIssue(t *testing.T) { session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues", owner.Name, repoBefore.GroupID, repoBefore.Name) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{ Body: body, Title: title, @@ -163,7 +163,7 @@ func TestAPICreateIssueParallel(t *testing.T) { session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues", owner.Name, repoBefore.GroupID, repoBefore.Name) var wg sync.WaitGroup for i := range 10 { @@ -217,7 +217,7 @@ func TestAPIEditIssue(t *testing.T) { body := "new content!" title := "new title from api set" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues/%d", owner.Name, repoBefore.GroupID, repoBefore.Name, issueBefore.Index) req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{ State: &issueState, RemoveDeadline: &removeDeadline, diff --git a/tests/integration/api_keys_test.go b/tests/integration/api_keys_test.go index 3162051acc4ff..187a728a538fd 100644 --- a/tests/integration/api_keys_test.go +++ b/tests/integration/api_keys_test.go @@ -55,7 +55,7 @@ func TestCreateReadOnlyDeployKey(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) + keysURL := fmt.Sprintf("/api/v1/repos/%s/%d/%s/keys", repoOwner.Name, repo.GroupID, repo.Name) rawKeyBody := api.CreateKeyOption{ Title: "read-only", Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", @@ -76,7 +76,7 @@ func TestCreateReadOnlyDeployKey(t *testing.T) { // Using the ID of a key that does not belong to the repository must fail { - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d", repoOwner.Name, repo.Name, newDeployKey.ID)). + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/keys/%d", repoOwner.Name, repo.GroupID, repo.Name, newDeployKey.ID)). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) @@ -95,7 +95,7 @@ func TestCreateReadWriteDeployKey(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) + keysURL := fmt.Sprintf("/api/v1/repos/%s/%d/%s/keys", repoOwner.Name, repo.GroupID, repo.Name) rawKeyBody := api.CreateKeyOption{ Title: "read-write", Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index e6bc142476837..b5a7dadde44e6 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -64,7 +64,7 @@ func TestAPINotification(t *testing.T) { assert.False(t, apiNL[2].Pinned) // -- GET /repos/{owner}/{repo}/notifications -- - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread", user2.Name, repo1.Name)). + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/notifications?status-types=unread", user2.Name, repo1.GroupID, repo1.Name)). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiNL) @@ -73,7 +73,7 @@ func TestAPINotification(t *testing.T) { assert.EqualValues(t, 4, apiNL[0].ID) // -- GET /repos/{owner}/{repo}/notifications -- multiple status-types - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&status-types=pinned", user2.Name, repo1.Name)). + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/notifications?status-types=unread&status-types=pinned", user2.Name, repo1.GroupID, repo1.Name)). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiNL) @@ -130,7 +130,7 @@ func TestAPINotification(t *testing.T) { assert.Len(t, apiNL, 2) lastReadAt := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 <- only Notification 4 is in this filter ... - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s", user2.Name, repo1.Name, lastReadAt)). + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/notifications?last_read_at=%s", user2.Name, repo1.GroupID, repo1.Name, lastReadAt)). AddTokenAuth(token) MakeRequest(t, req, http.StatusResetContent) diff --git a/tests/integration/api_packages_cargo_test.go b/tests/integration/api_packages_cargo_test.go index 8b5caa7ea7b2c..8014ba0a7c080 100644 --- a/tests/integration/api_packages_cargo_test.go +++ b/tests/integration/api_packages_cargo_test.go @@ -72,7 +72,7 @@ func testPackageCargo(t *testing.T, _ *neturl.URL) { err := cargo_service.InitializeIndexRepository(db.DefaultContext, user, user) assert.NoError(t, err) - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, user.Name, cargo_service.IndexRepositoryName) + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, user.Name, cargo_service.IndexRepositoryName, 0) assert.NotNil(t, repo) assert.NoError(t, err) diff --git a/tests/integration/api_pull_commits_test.go b/tests/integration/api_pull_commits_test.go index f43ad7d3be74f..e5b27b39b4715 100644 --- a/tests/integration/api_pull_commits_test.go +++ b/tests/integration/api_pull_commits_test.go @@ -24,7 +24,7 @@ func TestAPIPullCommits(t *testing.T) { assert.NoError(t, pr.LoadIssue(db.DefaultContext)) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pr.HeadRepoID}) - req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pr.Index) + req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%d/%s/pulls/%d/commits", repo.OwnerName, repo.GroupID, repo.Name, pr.Index) resp := MakeRequest(t, req, http.StatusOK) var commits []*api.Commit diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go index 1fc65ddea89dc..537f2d3a9fab8 100644 --- a/tests/integration/api_pull_review_test.go +++ b/tests/integration/api_pull_review_test.go @@ -34,7 +34,7 @@ func TestAPIPullReview(t *testing.T) { // test ListPullReviews session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index). + req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -59,14 +59,14 @@ func TestAPIPullReview(t *testing.T) { assert.True(t, reviews[5].Official) // test GetPullReview - req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[3].ID). + req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%d/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index, reviews[3].ID). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) var review api.PullReview DecodeJSON(t, resp, &review) assert.Equal(t, *reviews[3], review) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[5].ID). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index, reviews[5].ID). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &review) @@ -74,7 +74,7 @@ func TestAPIPullReview(t *testing.T) { // test GetPullReviewComments comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 7}) - req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d/comments", repo.OwnerName, repo.Name, pullIssue.Index, 10). + req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%d/%s/pulls/%d/reviews/%d/comments", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index, 10). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) var reviewComments []*api.PullReviewComment @@ -87,7 +87,7 @@ func TestAPIPullReview(t *testing.T) { assert.Equal(t, comment.HTMLURL(db.DefaultContext), reviewComments[0].HTMLURL) // test CreatePullReview - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Body: "body1", // Event: "" # will result in PENDING Comments: []api.CreatePullReviewComment{ @@ -116,7 +116,7 @@ func TestAPIPullReview(t *testing.T) { assert.Equal(t, 3, review.CodeCommentsCount) // test SubmitPullReview - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.SubmitPullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index, review.ID), &api.SubmitPullReviewOptions{ Event: "APPROVED", Body: "just two nits", }).AddTokenAuth(token) @@ -127,7 +127,7 @@ func TestAPIPullReview(t *testing.T) { assert.Equal(t, 3, review.CodeCommentsCount) // test dismiss review - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.DismissPullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews/%d/dismissals", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index, review.ID), &api.DismissPullReviewOptions{ Message: "test", }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -136,7 +136,7 @@ func TestAPIPullReview(t *testing.T) { assert.True(t, review.Dismissed) // test dismiss review - req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/undismissals", repo.OwnerName, repo.Name, pullIssue.Index, review.ID)). + req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews/%d/undismissals", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index, review.ID)). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &review) @@ -144,7 +144,7 @@ func TestAPIPullReview(t *testing.T) { assert.False(t, review.Dismissed) // test DeletePullReview - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Body: "just a comment", Event: "COMMENT", }).AddTokenAuth(token) @@ -152,12 +152,12 @@ func TestAPIPullReview(t *testing.T) { DecodeJSON(t, resp, &review) assert.EqualValues(t, "COMMENT", review.State) assert.Equal(t, 0, review.CodeCommentsCount) - req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID). + req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%d/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index, review.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // test CreatePullReview Comment without body but with comments - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ // Body: "", Event: "COMMENT", Comments: []api.CreatePullReviewComment{ @@ -185,7 +185,7 @@ func TestAPIPullReview(t *testing.T) { // test CreatePullReview Comment with body but without comments commentBody := "This is a body of the comment." - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Body: commentBody, Event: "COMMENT", Comments: []api.CreatePullReviewComment{}, @@ -199,7 +199,7 @@ func TestAPIPullReview(t *testing.T) { assert.False(t, commentReview.Dismissed) // test CreatePullReview Comment without body and no comments - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Body: "", Event: "COMMENT", Comments: []api.CreatePullReviewComment{}, @@ -215,7 +215,7 @@ func TestAPIPullReview(t *testing.T) { assert.NoError(t, pullIssue12.LoadAttributes(db.DefaultContext)) repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID}) - req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews", repo3.OwnerName, repo3.Name, pullIssue12.Index). + req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo3.OwnerName, repo3.GroupID, repo3.Name, pullIssue12.Index). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &reviews) @@ -243,19 +243,19 @@ func TestAPIPullReviewRequest(t *testing.T) { // Test add Review Request session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com", "user8"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) // poster of pr can't be reviewer - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user1"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusUnprocessableEntity) // test user not exist - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"testOther"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) @@ -264,18 +264,18 @@ func TestAPIPullReviewRequest(t *testing.T) { session2 := loginUser(t, "user4") token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user4"}, }).AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) // doer is not admin - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, }).AddTokenAuth(token2) MakeRequest(t, req, http.StatusUnprocessableEntity) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) @@ -286,12 +286,12 @@ func TestAPIPullReviewRequest(t *testing.T) { pull21Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue21.RepoID}) // repo60 user38Session := loginUser(t, "user38") user38Token := getTokenForLoggedInUser(t, user38Session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.GroupID, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com"}, }).AddTokenAuth(user38Token) MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.GroupID, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com"}, }).AddTokenAuth(user38Token) MakeRequest(t, req, http.StatusNoContent) @@ -299,12 +299,12 @@ func TestAPIPullReviewRequest(t *testing.T) { // the poster of the PR can add/remove a review request user39Session := loginUser(t, "user39") user39Token := getTokenForLoggedInUser(t, user39Session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.GroupID, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, }).AddTokenAuth(user39Token) MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.GroupID, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, }).AddTokenAuth(user39Token) MakeRequest(t, req, http.StatusNoContent) @@ -313,12 +313,12 @@ func TestAPIPullReviewRequest(t *testing.T) { pullIssue22 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 22}) assert.NoError(t, pullIssue22.LoadAttributes(db.DefaultContext)) pull22Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue22.RepoID}) // repo61 - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.GroupID, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user38"}, }).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.GroupID, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ Reviewers: []string{"user38"}, }).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit MakeRequest(t, req, http.StatusNoContent) @@ -329,35 +329,35 @@ func TestAPIPullReviewRequest(t *testing.T) { repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID}) // Test add Team Review Request - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.GroupID, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ TeamReviewers: []string{"team1", "owners"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) // Test add Team Review Request to not allowned - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.GroupID, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ TeamReviewers: []string{"test_team"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusUnprocessableEntity) // Test add Team Review Request to not exist - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.GroupID, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ TeamReviewers: []string{"not_exist_team"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) // Test Remove team Review Request - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.GroupID, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ TeamReviewers: []string{"team1"}, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) // empty request test - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.GroupID, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.GroupID, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) } @@ -377,7 +377,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { token8 := getTokenForLoggedInUser(t, session8, auth_model.AccessTokenScopeWriteRepository) // user2 request user8 - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{user8.LoginName}, }).AddTokenAuth(token2) MakeRequest(t, req, http.StatusCreated) @@ -387,7 +387,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { pullIssue.ID, user8.ID, 0, 1, 1, false) // user2 request user8 again, it is expected to be ignored - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{user8.LoginName}, }).AddTokenAuth(token2) MakeRequest(t, req, http.StatusCreated) @@ -397,7 +397,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { pullIssue.ID, user8.ID, 0, 1, 1, false) // user8 reviews it as accept - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Event: "APPROVED", Body: "lgtm", }).AddTokenAuth(token8) @@ -413,7 +413,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { assert.NoError(t, err) // user2 request user8 again - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ Reviewers: []string{user8.LoginName}, }).AddTokenAuth(token2) MakeRequest(t, req, http.StatusCreated) @@ -433,7 +433,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { pullIssue.ID, user8.ID, 1, 0, 1, false) // add a new valid approval - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Event: "APPROVED", Body: "lgtm", }).AddTokenAuth(token8) @@ -444,7 +444,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { pullIssue.ID, user8.ID, 1, 0, 2, true) // now add a change request witch should dismiss the approval - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/reviews", repo.OwnerName, repo.GroupID, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Event: "REQUEST_CHANGES", Body: "please change XYZ", }).AddTokenAuth(token8) diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index f2df6021e1c93..ce9489bc35ff6 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -41,7 +41,7 @@ func TestAPIViewPulls(t *testing.T) { ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls?state=all", owner.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/pulls?state=all", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(ctx.Token) resp := ctx.Session.MakeRequest(t, req, http.StatusOK) @@ -150,7 +150,7 @@ func TestAPIViewPullsByBaseHead(t *testing.T) { ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/pulls/master/branch2", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(ctx.Token) resp := ctx.Session.MakeRequest(t, req, http.StatusOK) @@ -159,7 +159,7 @@ func TestAPIViewPullsByBaseHead(t *testing.T) { assert.EqualValues(t, 3, pull.Index) assert.EqualValues(t, 2, pull.ID) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/pulls/master/branch-not-exist", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(ctx.Token) ctx.Session.MakeRequest(t, req, http.StatusNotFound) } @@ -180,7 +180,7 @@ func TestAPIMergePullWIP(t *testing.T) { session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner.Name, repo.Name, pr.Index), &forms.MergePullRequestForm{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d/merge", owner.Name, repo.GroupID, repo.Name, pr.Index), &forms.MergePullRequestForm{ MergeMessageField: pr.Issue.Title, Do: string(repo_model.MergeStyleMerge), }).AddTokenAuth(token) @@ -199,7 +199,7 @@ func TestAPICreatePullSuccess(t *testing.T) { session := loginUser(t, owner11.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), &api.CreatePullRequestOption{ Head: owner11.Name + ":master", Base: "master", Title: "create a failure pr", @@ -224,7 +224,7 @@ func TestAPICreatePullBasePermission(t *testing.T) { Base: "master", Title: "create a failure pr", } - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), &opts).AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) // add user4 to be a collaborator to base repo @@ -232,7 +232,7 @@ func TestAPICreatePullBasePermission(t *testing.T) { t.Run("AddUser4AsCollaborator", doAPIAddCollaborator(ctx, user4.Name, perm.AccessModeRead)) // create again - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), &opts).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) } @@ -252,18 +252,18 @@ func TestAPICreatePullHeadPermission(t *testing.T) { Base: "master", Title: "create a failure pr", } - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), &opts).AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) // add user4 to be a collaborator to head repo with read permission ctx := NewAPITestContext(t, repo11.OwnerName, repo11.Name, auth_model.AccessTokenScopeWriteRepository) t.Run("AddUser4AsCollaboratorWithRead", doAPIAddCollaborator(ctx, user4.Name, perm.AccessModeRead)) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), &opts).AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) // add user4 to be a collaborator to head repo with write permission t.Run("AddUser4AsCollaboratorWithWrite", doAPIAddCollaborator(ctx, user4.Name, perm.AccessModeWrite)) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), &opts).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) } @@ -275,7 +275,7 @@ func TestAPICreatePullSameRepoSuccess(t *testing.T) { session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner.Name, repo.Name), &api.CreatePullRequestOption{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner.Name, repo.GroupID, repo.Name), &api.CreatePullRequestOption{ Head: owner.Name + ":pr-to-update", Base: "master", Title: "successfully create a PR between branches of the same repository", @@ -306,7 +306,7 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) { Labels: []int64{5}, } - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), opts). + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), opts). AddTokenAuth(token) res := MakeRequest(t, req, http.StatusCreated) @@ -339,7 +339,7 @@ func TestAPICreatePullWithFieldsFailure(t *testing.T) { Base: "master", } - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), opts). + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), opts). AddTokenAuth(token) MakeRequest(t, req, http.StatusUnprocessableEntity) opts.Title = "is required" @@ -365,7 +365,7 @@ func TestAPIEditPull(t *testing.T) { session := loginUser(t, owner10.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) title := "create a success pr" - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls", owner10.Name, repo10.GroupID, repo10.Name), &api.CreatePullRequestOption{ Head: "develop", Base: "master", Title: title, @@ -377,7 +377,7 @@ func TestAPIEditPull(t *testing.T) { newTitle := "edit a this pr" newBody := "edited body" - req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, apiPull.Index), &api.EditPullRequestOption{ + req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d", owner10.Name, repo10.GroupID, repo10.Name, apiPull.Index), &api.EditPullRequestOption{ Base: "feature/1", Title: newTitle, Body: &newBody, @@ -392,7 +392,7 @@ func TestAPIEditPull(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: pull.Issue.ID, OldTitle: title, NewTitle: newTitle}) unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: pull.Issue.ID, ContentText: newBody, IsFirstCreated: false}) - req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{ + req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%d/%s/pulls/%d", owner10.Name, repo10.GroupID, repo10.Name, pull.Index), &api.EditPullRequestOption{ Base: "not-exist", }).AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) @@ -424,11 +424,11 @@ func TestAPICommitPullRequest(t *testing.T) { ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3" - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/commits/%s/pull", owner.Name, repo.GroupID, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token) ctx.Session.MakeRequest(t, req, http.StatusOK) invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/commits/%s/pull", owner.Name, repo.GroupID, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) ctx.Session.MakeRequest(t, req, http.StatusNotFound) } diff --git a/tests/integration/api_releases_attachment_test.go b/tests/integration/api_releases_attachment_test.go index 5df3042437e94..991ad29837866 100644 --- a/tests/integration/api_releases_attachment_test.go +++ b/tests/integration/api_releases_attachment_test.go @@ -31,7 +31,7 @@ func TestAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) filename := "file.bad" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", repoOwner.Name, repo.Name, release.ID, attachment.ID) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/%d/assets/%d", repoOwner.Name, repo.GroupID, repo.Name, release.ID, attachment.ID) req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ "name": filename, }).AddTokenAuth(token) diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index a3dbc0363b237..04aeea10cf9ec 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -32,7 +32,7 @@ func TestAPIListReleases(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeReadRepository) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name)) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases", user2.Name, repo.GroupID, repo.Name)) resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) var apiReleases []*api.Release DecodeJSON(t, resp, &apiReleases) @@ -78,7 +78,7 @@ func TestAPIListReleases(t *testing.T) { } func createNewReleaseUsingAPI(t *testing.T, token string, owner *user_model.User, repo *repo_model.Repository, name, target, title, desc string) *api.Release { - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases", owner.Name, repo.GroupID, repo.Name) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ TagName: name, Title: title, @@ -122,7 +122,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { newRelease := createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", target, "v0.0.1", "test") - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d", owner.Name, repo.Name, newRelease.ID) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/%d", owner.Name, repo.GroupID, repo.Name, newRelease.ID) req := NewRequest(t, "GET", urlStr). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -169,7 +169,7 @@ func TestAPICreateProtectedTagRelease(t *testing.T) { commit, err := gitRepo.GetBranchCommit("master") assert.NoError(t, err) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/releases", repo.OwnerName, repo.Name), &api.CreateReleaseOption{ + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases", repo.OwnerName, repo.GroupID, repo.Name), &api.CreateReleaseOption{ TagName: "v0.0.1", Title: "v0.0.1", IsDraft: false, @@ -216,7 +216,7 @@ func TestAPICreateReleaseGivenInvalidTarget(t *testing.T) { session := loginUser(t, owner.LowerName) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases", owner.Name, repo.GroupID, repo.Name) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ TagName: "i-point-to-an-invalid-target", Title: "Invalid Target", @@ -232,7 +232,7 @@ func TestAPIGetLatestRelease(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/latest", owner.Name, repo.Name)) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/latest", owner.Name, repo.GroupID, repo.Name)) resp := MakeRequest(t, req, http.StatusOK) var release *api.Release @@ -249,7 +249,7 @@ func TestAPIGetReleaseByTag(t *testing.T) { tag := "v1.1" - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, tag)) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/tags/%s", owner.Name, repo.GroupID, repo.Name, tag)) resp := MakeRequest(t, req, http.StatusOK) var release *api.Release @@ -259,7 +259,7 @@ func TestAPIGetReleaseByTag(t *testing.T) { nonexistingtag := "nonexistingtag" - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, nonexistingtag)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/tags/%s", owner.Name, repo.GroupID, repo.Name, nonexistingtag)) resp = MakeRequest(t, req, http.StatusNotFound) var err *api.APIError @@ -278,17 +278,17 @@ func TestAPIDeleteReleaseByTagName(t *testing.T) { createNewReleaseUsingAPI(t, token, owner, repo, "release-tag", "", "Release Tag", "test") // delete release - req := NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/releases/tags/release-tag", owner.Name, repo.Name). + req := NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%d/%s/releases/tags/release-tag", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) _ = MakeRequest(t, req, http.StatusNoContent) // make sure release is deleted - req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/releases/tags/release-tag", owner.Name, repo.Name). + req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%d/%s/releases/tags/release-tag", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) _ = MakeRequest(t, req, http.StatusNotFound) // delete release tag too - req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/tags/release-tag", owner.Name, repo.Name). + req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%d/%s/tags/release-tag", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) _ = MakeRequest(t, req, http.StatusNoContent) } @@ -306,7 +306,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { filename := "image.png" buff := generateImg() - assetURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner.Name, repo.Name, r.ID) + assetURL := fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/%d/assets", owner.Name, repo.GroupID, repo.Name, r.ID) t.Run("multipart/form-data", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go index 97c2c0d54b5a6..cb6382687a59e 100644 --- a/tests/integration/api_repo_archive_test.go +++ b/tests/integration/api_repo_archive_test.go @@ -30,13 +30,13 @@ func TestAPIDownloadArchive(t *testing.T) { session := loginUser(t, user2.LowerName) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.zip", user2.Name, repo.Name)) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/archive/master.zip", user2.Name, repo.GroupID, repo.Name)) resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err := io.ReadAll(resp.Body) assert.NoError(t, err) assert.Len(t, bs, 320) - link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.tar.gz", user2.Name, repo.Name)) + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/archive/master.tar.gz", user2.Name, repo.GroupID, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) @@ -52,13 +52,13 @@ func TestAPIDownloadArchive(t *testing.T) { // The locked URL should give the same bytes as the non-locked one assert.Equal(t, bs, bs2) - link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.bundle", user2.Name, repo.Name)) + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/archive/master.bundle", user2.Name, repo.GroupID, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) assert.Len(t, bs, 382) - link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name)) + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/archive/master", user2.Name, repo.GroupID, repo.Name)) MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest) t.Run("GitHubStyle", testAPIDownloadArchiveGitHubStyle) @@ -73,13 +73,13 @@ func testAPIDownloadArchiveGitHubStyle(t *testing.T) { session := loginUser(t, user2.LowerName) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/zipball/master", user2.Name, repo.Name)) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/zipball/master", user2.Name, repo.GroupID, repo.Name)) resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err := io.ReadAll(resp.Body) assert.NoError(t, err) assert.Len(t, bs, 320) - link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/tarball/master", user2.Name, repo.Name)) + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/tarball/master", user2.Name, repo.GroupID, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) @@ -95,7 +95,7 @@ func testAPIDownloadArchiveGitHubStyle(t *testing.T) { // The locked URL should give the same bytes as the non-locked one assert.Equal(t, bs, bs2) - link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/bundle/master", user2.Name, repo.Name)) + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%d/%s/bundle/master", user2.Name, repo.GroupID, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) diff --git a/tests/integration/api_repo_avatar_test.go b/tests/integration/api_repo_avatar_test.go index 6677885f7eb20..1a17d63dab6cf 100644 --- a/tests/integration/api_repo_avatar_test.go +++ b/tests/integration/api_repo_avatar_test.go @@ -38,7 +38,7 @@ func TestAPIUpdateRepoAvatar(t *testing.T) { Image: base64.StdEncoding.EncodeToString(avatar), } - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/avatar", repo.OwnerName, repo.GroupID, repo.Name), &opts). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) @@ -47,7 +47,7 @@ func TestAPIUpdateRepoAvatar(t *testing.T) { Image: "Invalid", } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/avatar", repo.OwnerName, repo.GroupID, repo.Name), &opts). AddTokenAuth(token) MakeRequest(t, req, http.StatusBadRequest) @@ -62,7 +62,7 @@ func TestAPIUpdateRepoAvatar(t *testing.T) { Image: base64.StdEncoding.EncodeToString(text), } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/avatar", repo.OwnerName, repo.GroupID, repo.Name), &opts). AddTokenAuth(token) MakeRequest(t, req, http.StatusInternalServerError) } @@ -74,7 +74,7 @@ func TestAPIDeleteRepoAvatar(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository) - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name)). + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/avatar", repo.OwnerName, repo.GroupID, repo.Name)). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) } diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go index 11e2924e8423e..67b9502523548 100644 --- a/tests/integration/api_repo_collaborator_test.go +++ b/tests/integration/api_repo_collaborator_test.go @@ -32,7 +32,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, repo2Owner.Name). AddTokenAuth(testCtx.Token) resp := MakeRequest(t, req, http.StatusOK) @@ -45,7 +45,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { t.Run("CollaboratorWithReadAccess", func(t *testing.T) { t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, user4.Name). AddTokenAuth(testCtx.Token) resp := MakeRequest(t, req, http.StatusOK) @@ -58,7 +58,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, user4.Name). AddTokenAuth(testCtx.Token) resp := MakeRequest(t, req, http.StatusOK) @@ -71,7 +71,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, user4.Name). AddTokenAuth(testCtx.Token) resp := MakeRequest(t, req, http.StatusOK) @@ -82,7 +82,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { }) t.Run("CollaboratorNotFound", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user"). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, "non-existent-user"). AddTokenAuth(testCtx.Token) MakeRequest(t, req, http.StatusNotFound) }) @@ -99,7 +99,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { _session := loginUser(t, user5.Name) _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, user5.Name). AddTokenAuth(_testCtx.Token) resp := _session.MakeRequest(t, req, http.StatusOK) @@ -112,7 +112,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { session := loginUser(t, user5.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, user5.Name).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) repoCollPerm := api.RepoCollaboratorPermission{} @@ -128,7 +128,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { _session := loginUser(t, user5.Name) _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, user5.Name). AddTokenAuth(_testCtx.Token) resp := _session.MakeRequest(t, req, http.StatusOK) @@ -145,7 +145,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { _session := loginUser(t, user10.Name) _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/collaborators/%s/permission", repo2Owner.Name, repo2.GroupID, repo2.Name, user11.Name). AddTokenAuth(_testCtx.Token) resp := _session.MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index af3bc546803a1..6d676ff761146 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -180,7 +180,7 @@ func TestAPICreateFile(t *testing.T) { createFileOptions.BranchName = branch fileID++ treePath := fmt.Sprintf("new/file%d.txt", fileID) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &createFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) @@ -215,7 +215,7 @@ func TestAPICreateFile(t *testing.T) { createFileOptions.NewBranchName = "new_branch" fileID++ treePath := fmt.Sprintf("new/file%d.txt", fileID) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &createFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) var fileResponse api.FileResponse @@ -233,7 +233,7 @@ func TestAPICreateFile(t *testing.T) { createFileOptions.Message = "" fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &createFileOptions). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &fileResponse) @@ -243,7 +243,7 @@ func TestAPICreateFile(t *testing.T) { // Test trying to create a file that already exists, should fail createFileOptions = getCreateFileOptions() treePath = "README.md" - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &createFileOptions). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusUnprocessableEntity) expectedAPIError := context.APIError{ @@ -258,7 +258,7 @@ func TestAPICreateFile(t *testing.T) { createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &createFileOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) @@ -266,14 +266,14 @@ func TestAPICreateFile(t *testing.T) { createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &createFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &createFileOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusCreated) @@ -281,7 +281,7 @@ func TestAPICreateFile(t *testing.T) { createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", org3.Name, repo3.GroupID, repo3.Name, treePath), &createFileOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusCreated) @@ -289,14 +289,14 @@ func TestAPICreateFile(t *testing.T) { createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", org3.Name, repo3.GroupID, repo3.Name, treePath), &createFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using repo "user2/repo1" where user4 is a NOT collaborator createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &createFileOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusForbidden) diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index 9dd47f93e6167..e05e16a32f1be 100644 --- a/tests/integration/api_repo_file_delete_test.go +++ b/tests/integration/api_repo_file_delete_test.go @@ -66,7 +66,7 @@ func TestAPIDeleteFile(t *testing.T) { createFile(user2, repo1, treePath) deleteFileOptions := getDeleteFileOptions() deleteFileOptions.BranchName = branch - req := NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). + req := NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &deleteFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusOK) var fileResponse api.FileResponse @@ -82,7 +82,7 @@ func TestAPIDeleteFile(t *testing.T) { deleteFileOptions := getDeleteFileOptions() deleteFileOptions.BranchName = repo1.DefaultBranch deleteFileOptions.NewBranchName = "new_branch" - req := NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). + req := NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &deleteFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusOK) var fileResponse api.FileResponse @@ -97,7 +97,7 @@ func TestAPIDeleteFile(t *testing.T) { createFile(user2, repo1, treePath) deleteFileOptions = getDeleteFileOptions() deleteFileOptions.Message = "" - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &deleteFileOptions). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) @@ -110,7 +110,7 @@ func TestAPIDeleteFile(t *testing.T) { createFile(user2, repo1, treePath) deleteFileOptions = getDeleteFileOptions() deleteFileOptions.SHA = "badsha" - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &deleteFileOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusUnprocessableEntity) @@ -119,7 +119,7 @@ func TestAPIDeleteFile(t *testing.T) { treePath = fmt.Sprintf("delete/file%d.txt", fileID) createFile(user2, repo16, treePath) deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions). + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &deleteFileOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) @@ -128,7 +128,7 @@ func TestAPIDeleteFile(t *testing.T) { treePath = fmt.Sprintf("delete/file%d.txt", fileID) createFile(user2, repo16, treePath) deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions) + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &deleteFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns @@ -136,7 +136,7 @@ func TestAPIDeleteFile(t *testing.T) { treePath = fmt.Sprintf("delete/file%d.txt", fileID) createFile(user2, repo16, treePath) deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions). + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &deleteFileOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) @@ -145,7 +145,7 @@ func TestAPIDeleteFile(t *testing.T) { treePath = fmt.Sprintf("delete/file%d.txt", fileID) createFile(org3, repo3, treePath) deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &deleteFileOptions). + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", org3.Name, repo3.GroupID, repo3.Name, treePath), &deleteFileOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) @@ -154,7 +154,7 @@ func TestAPIDeleteFile(t *testing.T) { treePath = fmt.Sprintf("delete/file%d.txt", fileID) createFile(org3, repo3, treePath) deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &deleteFileOptions) + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", org3.Name, repo3.GroupID, repo3.Name, treePath), &deleteFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using repo "user2/repo1" where user4 is a NOT collaborator @@ -162,7 +162,7 @@ func TestAPIDeleteFile(t *testing.T) { treePath = fmt.Sprintf("delete/file%d.txt", fileID) createFile(user2, repo1, treePath) deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &deleteFileOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusForbidden) }) diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index 9a56711da6a1d..e5bf859c78adb 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -136,7 +136,7 @@ func TestAPIUpdateFile(t *testing.T) { createFile(user2, repo1, treePath) updateFileOptions := getUpdateFileOptions() updateFileOptions.BranchName = branch - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &updateFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusOK) gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) @@ -167,7 +167,7 @@ func TestAPIUpdateFile(t *testing.T) { fileID++ treePath := fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo1, treePath) - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &updateFileOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusOK) var fileResponse api.FileResponse @@ -188,7 +188,7 @@ func TestAPIUpdateFile(t *testing.T) { createFile(user2, repo1, treePath) updateFileOptions.FromPath = treePath treePath = "rename/" + treePath - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &updateFileOptions). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) @@ -206,7 +206,7 @@ func TestAPIUpdateFile(t *testing.T) { fileID++ treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo1, treePath) - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &updateFileOptions). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) @@ -220,7 +220,7 @@ func TestAPIUpdateFile(t *testing.T) { updateFileOptions = getUpdateFileOptions() correctSHA := updateFileOptions.SHA updateFileOptions.SHA = "badsha" - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &updateFileOptions). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusUnprocessableEntity) expectedAPIError := context.APIError{ @@ -236,7 +236,7 @@ func TestAPIUpdateFile(t *testing.T) { treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo16, treePath) updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &updateFileOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) @@ -245,7 +245,7 @@ func TestAPIUpdateFile(t *testing.T) { treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo16, treePath) updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &updateFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns @@ -253,7 +253,7 @@ func TestAPIUpdateFile(t *testing.T) { treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo16, treePath) updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath), &updateFileOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) @@ -262,7 +262,7 @@ func TestAPIUpdateFile(t *testing.T) { treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(org3, repo3, treePath) updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions). + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", org3.Name, repo3.GroupID, repo3.Name, treePath), &updateFileOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) @@ -271,7 +271,7 @@ func TestAPIUpdateFile(t *testing.T) { treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(org3, repo3, treePath) updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", org3.Name, repo3.GroupID, repo3.Name, treePath), &updateFileOptions) MakeRequest(t, req, http.StatusNotFound) // Test using repo "user2/repo1" where user4 is a NOT collaborator @@ -279,7 +279,7 @@ func TestAPIUpdateFile(t *testing.T) { treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo1, treePath) updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath), &updateFileOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusForbidden) }) diff --git a/tests/integration/api_repo_files_change_test.go b/tests/integration/api_repo_files_change_test.go index 999bcdc680fbc..6ebf39ddce1bf 100644 --- a/tests/integration/api_repo_files_change_test.go +++ b/tests/integration/api_repo_files_change_test.go @@ -90,7 +90,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", user2.Name, repo1.GroupID, repo1.Name), &changeFilesOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) @@ -142,7 +142,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[2].Path = deleteTreePath createFile(user2, repo1, updateTreePath) createFile(user2, repo1, deleteTreePath) - url := fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) + url := fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", user2.Name, repo1.GroupID, repo1.Name) req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) @@ -235,7 +235,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", user2.Name, repo16.GroupID, repo16.Name), &changeFilesOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) @@ -250,7 +250,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", user2.Name, repo16.GroupID, repo16.Name), &changeFilesOptions) MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns @@ -264,7 +264,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", user2.Name, repo16.GroupID, repo16.Name), &changeFilesOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusCreated) @@ -279,7 +279,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", org3.Name, repo3.GroupID, repo3.Name), &changeFilesOptions). AddTokenAuth(token2) MakeRequest(t, req, http.StatusCreated) @@ -294,7 +294,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", org3.Name, repo3.GroupID, repo3.Name), &changeFilesOptions) MakeRequest(t, req, http.StatusNotFound) // Test using repo "user2/repo1" where user4 is a NOT collaborator @@ -308,7 +308,7 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/contents", user2.Name, repo1.GroupID, repo1.Name), &changeFilesOptions). AddTokenAuth(token4) MakeRequest(t, req, http.StatusForbidden) }) diff --git a/tests/integration/api_repo_files_get_test.go b/tests/integration/api_repo_files_get_test.go index a4ded7da3f80c..54b346437b424 100644 --- a/tests/integration/api_repo_files_get_test.go +++ b/tests/integration/api_repo_files_get_test.go @@ -96,13 +96,13 @@ func TestAPIGetRequestedFiles(t *testing.T) { t.Run("PermissionCheck", func(t *testing.T) { filesOptions := &api.GetFilesOptions{Files: []string{"README.md"}} // Test accessing private ref with user token that does not have access - should fail - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token4) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/file-contents", user2.Name, repo16.GroupID, repo16.Name), &filesOptions).AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) // Test access private ref of owner of token - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token2) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/file-contents", user2.Name, repo16.GroupID, repo16.Name), &filesOptions).AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) // Test access of org org3 private repo file by owner user2 - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", org3.Name, repo3.Name), &filesOptions).AddTokenAuth(token2) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/file-contents", org3.Name, repo3.GroupID, repo3.Name), &filesOptions).AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) }) diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go index 563d6fcc10dca..50c88fee6a5cd 100644 --- a/tests/integration/api_repo_get_contents_list_test.go +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -94,7 +94,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // ref is default ref ref := repo1.DefaultBranch refType := "branch" - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents?ref=%s", user2.Name, repo1.GroupID, repo1.Name, ref) resp := MakeRequest(t, req, http.StatusOK) var contentsListResponse []*api.ContentsResponse DecodeJSON(t, resp, &contentsListResponse) @@ -106,7 +106,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // No ref refType = "branch" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo1.Name) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/", user2.Name, repo1.GroupID, repo1.Name) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) @@ -117,7 +117,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // ref is the branch we created above in setup ref = newBranch refType = "branch" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents?ref=%s", user2.Name, repo1.GroupID, repo1.Name, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) @@ -131,7 +131,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // ref is the new tag we created above in setup ref = newTag refType = "tag" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/?ref=%s", user2.Name, repo1.GroupID, repo1.Name, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) @@ -145,7 +145,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // ref is a commit ref = commitID refType = "commit" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/?ref=%s", user2.Name, repo1.GroupID, repo1.Name, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) @@ -154,21 +154,21 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // Test file contents a file with a bad ref ref = "badref" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/?ref=%s", user2.Name, repo1.GroupID, repo1.Name, ref) MakeRequest(t, req, http.StatusNotFound) // Test accessing private ref with user token that does not have access - should fail - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/", user2.Name, repo16.GroupID, repo16.Name). AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) // Test access private ref of owner of token - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/", user2.Name, repo16.GroupID, repo16.Name). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) // Test access of org org3 private repo file by owner user2 - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", org3.Name, repo3.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/", org3.Name, repo3.GroupID, repo3.Name). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) } diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index 33df74f6eeb74..b3f54dece0035 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -98,14 +98,14 @@ func testAPIGetContents(t *testing.T, u *url.URL) { /*** END SETUP ***/ // not found - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/no-such/file.md", user2.Name, repo1.Name) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/no-such/file.md", user2.Name, repo1.GroupID, repo1.Name) resp := MakeRequest(t, req, http.StatusNotFound) assert.Contains(t, resp.Body.String(), "object does not exist [id: , rel_path: no-such]") // ref is default ref ref := repo1.DefaultBranch refType := "branch" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s?ref=%s", user2.Name, repo1.GroupID, repo1.Name, treePath, ref) resp = MakeRequest(t, req, http.StatusOK) var contentsResponse api.ContentsResponse DecodeJSON(t, resp, &contentsResponse) @@ -115,7 +115,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // No ref refType = "branch" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo1.GroupID, repo1.Name, treePath) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) expectedContentsResponse = getExpectedContentsResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) @@ -124,7 +124,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // ref is the branch we created above in setup ref = newBranch refType = "branch" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s?ref=%s", user2.Name, repo1.GroupID, repo1.Name, treePath, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) branchCommit, _ := gitRepo.GetBranchCommit(ref) @@ -135,7 +135,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // ref is the new tag we created above in setup ref = newTag refType = "tag" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s?ref=%s", user2.Name, repo1.GroupID, repo1.Name, treePath, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) tagCommit, _ := gitRepo.GetTagCommit(ref) @@ -146,7 +146,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // ref is a commit ref = commitID refType = "commit" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s?ref=%s", user2.Name, repo1.GroupID, repo1.Name, treePath, ref) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &contentsResponse) expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, commitID) @@ -154,21 +154,21 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // Test file contents a file with a bad ref ref = "badref" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s?ref=%s", user2.Name, repo1.GroupID, repo1.Name, treePath, ref) MakeRequest(t, req, http.StatusNotFound) // Test accessing private ref with user token that does not have access - should fail - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s", user2.Name, repo16.GroupID, repo16.Name, treePath). AddTokenAuth(token4) MakeRequest(t, req, http.StatusNotFound) // Test access private ref of owner of token - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md", user2.Name, repo16.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/readme.md", user2.Name, repo16.GroupID, repo16.Name). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) // Test access of org org3 private repo file by owner user2 - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/contents/%s", org3.Name, repo3.GroupID, repo3.Name, treePath). AddTokenAuth(token2) MakeRequest(t, req, http.StatusOK) } diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index d4274bdb4042c..3cc16ae662ec1 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -35,7 +35,7 @@ func TestAPIReposGitBlobs(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test a public repo that anyone can GET the blob of - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo1.Name, repo1ReadmeSHA) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/%s", user2.Name, repo1.GroupID, repo1.Name, repo1ReadmeSHA) resp := MakeRequest(t, req, http.StatusOK) var gitBlobResponse api.GitBlobResponse DecodeJSON(t, resp, &gitBlobResponse) @@ -44,30 +44,30 @@ func TestAPIReposGitBlobs(t *testing.T) { assert.Equal(t, expectedContent, *gitBlobResponse.Content) // Tests a private repo with no token so will fail - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo16.Name, repo16ReadmeSHA) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/%s", user2.Name, repo16.GroupID, repo16.Name, repo16ReadmeSHA) MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo16.Name, repo16ReadmeSHA). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/%s", user2.Name, repo16.GroupID, repo16.Name, repo16ReadmeSHA). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) // Test using bad sha - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo1.Name, badSHA) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/%s", user2.Name, repo1.GroupID, repo1.Name, badSHA) MakeRequest(t, req, http.StatusBadRequest) // Test using org repo "org3/repo3" where user2 is a collaborator - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", org3.Name, repo3.Name, repo3ReadmeSHA). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/%s", org3.Name, repo3.GroupID, repo3.Name, repo3ReadmeSHA). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) // Test using org repo "org3/repo3" where user2 is a collaborator - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", org3.Name, repo3.Name, repo3ReadmeSHA). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/%s", org3.Name, repo3.GroupID, repo3.Name, repo3ReadmeSHA). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) // Test using org repo "org3/repo3" with no user token - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", org3.Name, repo3ReadmeSHA, repo3.Name) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/%s", org3.Name, repo3ReadmeSHA, repo3.GroupID, repo3.Name) MakeRequest(t, req, http.StatusNotFound) // Login as User4. @@ -75,6 +75,6 @@ func TestAPIReposGitBlobs(t *testing.T) { token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/blobs/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.GroupID, repo3.Name, token4) MakeRequest(t, req, http.StatusNotFound) } diff --git a/tests/integration/api_repo_git_hook_test.go b/tests/integration/api_repo_git_hook_test.go index c28c4336e2d78..00e8ef93e4282 100644 --- a/tests/integration/api_repo_git_hook_test.go +++ b/tests/integration/api_repo_git_hook_test.go @@ -37,7 +37,7 @@ echo "TestGitHookScript" // user1 is an admin user session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/hooks/git", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var apiGitHooks []*api.GitHook @@ -63,7 +63,7 @@ echo "TestGitHookScript" // user1 is an admin user session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/hooks/git", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var apiGitHooks []*api.GitHook @@ -83,7 +83,7 @@ echo "TestGitHookScript" session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/hooks/git", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) }) @@ -97,7 +97,7 @@ echo "TestGitHookScript" // user1 is an admin user session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/hooks/git/pre-receive", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var apiGitHook *api.GitHook @@ -113,7 +113,7 @@ echo "TestGitHookScript" session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/hooks/git/pre-receive", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) }) @@ -139,7 +139,7 @@ echo "TestGitHookScript" assert.True(t, apiGitHook.IsActive) assert.Equal(t, testHookContent, apiGitHook.Content) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/hooks/git/pre-receive", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) var apiGitHook2 *api.GitHook @@ -156,7 +156,7 @@ echo "TestGitHookScript" session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/hooks/git/pre-receive", owner.Name, repo.GroupID, repo.Name) req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ Content: testHookContent, }).AddTokenAuth(token) @@ -173,11 +173,11 @@ echo "TestGitHookScript" session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%d/%s/hooks/git/pre-receive", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/hooks/git/pre-receive", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var apiGitHook2 *api.GitHook @@ -194,7 +194,7 @@ echo "TestGitHookScript" session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%d/%s/hooks/git/pre-receive", owner.Name, repo.GroupID, repo.Name). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) }) diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index 5a6633758939d..7760feb27d896 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -46,7 +46,7 @@ func TestAPIGitTags(t *testing.T) { aTag, _ := gitRepo.GetTag(aTagName) // SHOULD work for annotated tags - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/tags/%s", user.Name, repo.GroupID, repo.Name, aTag.ID.String()). AddTokenAuth(token) res := MakeRequest(t, req, http.StatusOK) @@ -62,7 +62,7 @@ func TestAPIGitTags(t *testing.T) { assert.Equal(t, util.URLJoin(repo.APIURL(), "git/tags", aTag.ID.String()), tag.URL) // Should NOT work for lightweight tags - badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, commit.ID.String()). + badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/tags/%s", user.Name, repo.GroupID, repo.Name, commit.ID.String()). AddTokenAuth(token) MakeRequest(t, badReq, http.StatusBadRequest) } @@ -75,14 +75,14 @@ func TestAPIDeleteTagByName(t *testing.T) { session := loginUser(t, owner.LowerName) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequest(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/delete-tag", owner.Name, repo.Name)). + req := NewRequest(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/tags/delete-tag", owner.Name, repo.GroupID, repo.Name)). AddTokenAuth(token) _ = MakeRequest(t, req, http.StatusNoContent) // Make sure that actual releases can't be deleted outright createNewReleaseUsingAPI(t, token, owner, repo, "release-tag", "", "Release Tag", "test") - req = NewRequest(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag", owner.Name, repo.Name)). + req = NewRequest(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%d/%s/tags/release-tag", owner.Name, repo.GroupID, repo.Name)). AddTokenAuth(token) _ = MakeRequest(t, req, http.StatusConflict) } diff --git a/tests/integration/api_repo_git_trees_test.go b/tests/integration/api_repo_git_trees_test.go index ea7630f414567..5ce9e10d10912 100644 --- a/tests/integration/api_repo_git_trees_test.go +++ b/tests/integration/api_repo_git_trees_test.go @@ -57,26 +57,26 @@ func TestAPIReposGitTrees(t *testing.T) { "master", // Branch repo1TreeSHA, // Tag } { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", user2.Name, repo16.Name, ref) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/trees/%s", user2.Name, repo16.GroupID, repo16.Name, ref) MakeRequest(t, req, http.StatusNotFound) } // Test using access token for a private repo that the user of the token owns - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", user2.Name, repo16.Name, repo16TreeSHA). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/trees/%s", user2.Name, repo16.GroupID, repo16.Name, repo16TreeSHA). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) // Test using bad sha - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", user2.Name, repo1.Name, badSHA) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/trees/%s", user2.Name, repo1.GroupID, repo1.Name, badSHA) MakeRequest(t, req, http.StatusBadRequest) // Test using org repo "org3/repo3" where user2 is a collaborator - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", org3.Name, repo3.Name, repo3TreeSHA). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/trees/%s", org3.Name, repo3.GroupID, repo3.Name, repo3TreeSHA). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) // Test using org repo "org3/repo3" with no user token - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", org3.Name, repo3TreeSHA, repo3.Name) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/trees/%s", org3.Name, repo3TreeSHA, repo3.GroupID, repo3.Name) MakeRequest(t, req, http.StatusNotFound) // Login as User4. @@ -84,6 +84,6 @@ func TestAPIReposGitTrees(t *testing.T) { token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/git/trees/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.GroupID, repo3.Name, token4) MakeRequest(t, req, http.StatusNotFound) } diff --git a/tests/integration/api_repo_hook_test.go b/tests/integration/api_repo_hook_test.go index f27fcc00d6b3f..ab52b77d9222e 100644 --- a/tests/integration/api_repo_hook_test.go +++ b/tests/integration/api_repo_hook_test.go @@ -27,7 +27,7 @@ func TestAPICreateHook(t *testing.T) { // user1 is an admin user session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/%s", owner.Name, repo.Name, "hooks"), api.CreateHookOption{ + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/%s", owner.Name, repo.GroupID, repo.Name, "hooks"), api.CreateHookOption{ Type: "gitea", Config: api.CreateHookOptionConfig{ "content_type": "json", diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go index ec6a3a3b5726d..e1fe4aa911c9c 100644 --- a/tests/integration/api_repo_lfs_test.go +++ b/tests/integration/api_repo_lfs_test.go @@ -64,7 +64,7 @@ func createLFSTestRepository(t *testing.T, name string) *repo_model.Repository { ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepo", doAPICreateRepository(ctx, false)) - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs-"+name+"-repo") + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs-"+name+"-repo", 0) assert.NoError(t, err) return repo diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index 3932a8ba2b083..ec9fb93017229 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -56,7 +56,7 @@ func TestAPIRepoTags(t *testing.T) { } // get created tag - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags/%s", user.Name, repoName, newTag.Name). + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/tags/%s", user.Name, repoName, newTag.GroupID, newTag.Name). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) var tag *api.Tag @@ -64,7 +64,7 @@ func TestAPIRepoTags(t *testing.T) { assert.Equal(t, newTag, tag) // delete tag - delReq := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/tags/%s", user.Name, repoName, newTag.Name). + delReq := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%d/%s/tags/%s", user.Name, repoName, newTag.GroupID, newTag.Name). AddTokenAuth(token) MakeRequest(t, delReq, http.StatusNoContent) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index a2c3a467c60d9..e34a4002355a3 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -94,66 +94,66 @@ func TestAPISearchRepo(t *testing.T) { }{ { name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ - nil: {count: 36}, - user: {count: 36}, - user2: {count: 36}, - }, + nil: {count: 36}, + user: {count: 36}, + user2: {count: 36}, + }, }, { name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ - nil: {count: 10}, - user: {count: 10}, - user2: {count: 10}, - }, + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, }, { name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{ - nil: {count: 10}, - user: {count: 10}, - user2: {count: 10}, - }, + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, }, { name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{ - nil: {count: 7, repoName: "big_test_"}, - user: {count: 7, repoName: "big_test_"}, - user2: {count: 7, repoName: "big_test_"}, - }, + nil: {count: 7, repoName: "big_test_"}, + user: {count: 7, repoName: "big_test_"}, + user2: {count: 7, repoName: "big_test_"}, + }, }, { name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{ - user2: {count: 2, repoName: "big_test_"}, - }, + user2: {count: 2, repoName: "big_test_"}, + }, }, { name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{ - nil: {count: 5}, - user: {count: 9, includesPrivate: true}, - user2: {count: 6, includesPrivate: true}, - }, + nil: {count: 5}, + user: {count: 9, includesPrivate: true}, + user2: {count: 6, includesPrivate: true}, + }, }, { name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 2, includesPrivate: true}, - user2: {count: 2, includesPrivate: true}, - user4: {count: 1}, - }, + nil: {count: 1}, + user: {count: 2, includesPrivate: true}, + user2: {count: 2, includesPrivate: true}, + user4: {count: 1}, + }, }, { name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", org3.ID), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 4, includesPrivate: true}, - user2: {count: 3, includesPrivate: true}, - org3: {count: 4, includesPrivate: true}, - }, + nil: {count: 1}, + user: {count: 4, includesPrivate: true}, + user2: {count: 3, includesPrivate: true}, + org3: {count: 4, includesPrivate: true}, + }, }, { name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{ - nil: {count: 1, repoOwnerID: orgUser.ID}, - user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true}, - user2: {count: 1, repoOwnerID: orgUser.ID}, - }, + nil: {count: 1, repoOwnerID: orgUser.ID}, + user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true}, + user2: {count: 1, repoOwnerID: orgUser.ID}, + }, }, {name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{ nil: {count: 3}, @@ -577,7 +577,7 @@ func TestAPIRepoTransfer(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) session = loginUser(t, user.Name) token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer", repo.OwnerName, repo.Name), &api.TransferRepoOption{ + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/transfer", repo.OwnerName, repo.GroupID, repo.Name), &api.TransferRepoOption{ NewOwner: testCase.newOwner, TeamIDs: testCase.teams, }).AddTokenAuth(token) @@ -608,7 +608,7 @@ func transfer(t *testing.T) *repo_model.Repository { DecodeJSON(t, resp, apiRepo) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer", repo.OwnerName, repo.Name), &api.TransferRepoOption{ + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/transfer", repo.OwnerName, repo.GroupID, repo.Name), &api.TransferRepoOption{ NewOwner: "user4", }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) @@ -624,7 +624,7 @@ func TestAPIAcceptTransfer(t *testing.T) { // try to accept with not authorized user session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)). + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/transfer/reject", repo.OwnerName, repo.GroupID, repo.Name)). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -637,7 +637,7 @@ func TestAPIAcceptTransfer(t *testing.T) { session = loginUser(t, "user4") token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept", repo.OwnerName, repo.Name)). + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/transfer/accept", repo.OwnerName, repo.GroupID, repo.Name)). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusAccepted) apiRepo := new(api.Repository) @@ -653,7 +653,7 @@ func TestAPIRejectTransfer(t *testing.T) { // try to reject with not authorized user session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)). + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/transfer/reject", repo.OwnerName, repo.GroupID, repo.Name)). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -666,7 +666,7 @@ func TestAPIRejectTransfer(t *testing.T) { session = loginUser(t, "user4") token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)). + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/transfer/reject", repo.OwnerName, repo.GroupID, repo.Name)). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) apiRepo := new(api.Repository) @@ -685,7 +685,7 @@ func TestAPIGenerateRepo(t *testing.T) { // user repo := new(api.Repository) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/generate", templateRepo.OwnerName, templateRepo.GroupID, templateRepo.Name), &api.GenerateRepoOption{ Owner: user.Name, Name: "new-repo", Description: "test generate repo", @@ -698,7 +698,7 @@ func TestAPIGenerateRepo(t *testing.T) { assert.Equal(t, "new-repo", repo.Name) // org - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/generate", templateRepo.OwnerName, templateRepo.GroupID, templateRepo.Name), &api.GenerateRepoOption{ Owner: "org3", Name: "new-repo", Description: "test generate repo", @@ -718,7 +718,7 @@ func TestAPIRepoGetReviewers(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers", user.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/reviewers", user.Name, repo.GroupID, repo.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var reviewers []*api.User @@ -735,7 +735,7 @@ func TestAPIRepoGetAssignees(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name). + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%d/%s/assignees", user.Name, repo.GroupID, repo.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var assignees []*api.User diff --git a/tests/integration/api_repo_topic_test.go b/tests/integration/api_repo_topic_test.go index 82d0c54ca8ee8..a9760b1701ed8 100644 --- a/tests/integration/api_repo_topic_test.go +++ b/tests/integration/api_repo_topic_test.go @@ -83,7 +83,7 @@ func TestAPIRepoTopic(t *testing.T) { token2 := getUserToken(t, user2.Name, auth_model.AccessTokenScopeWriteRepository) // Test read topics using login - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name)). + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/topics", user2.Name, repo2.GroupID, repo2.Name)). AddTokenAuth(token2) res := MakeRequest(t, req, http.StatusOK) var topics *api.TopicName @@ -91,21 +91,21 @@ func TestAPIRepoTopic(t *testing.T) { assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) // Test delete a topic - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%d/%s/topics/%s", user2.Name, repo2.GroupID, repo2.Name, "Topicname1"). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) // Test add an existing topic - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Golang"). + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%d/%s/topics/%s", user2.Name, repo2.GroupID, repo2.Name, "Golang"). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) // Test add a topic - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "topicName3"). + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%d/%s/topics/%s", user2.Name, repo2.GroupID, repo2.Name, "topicName3"). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) - url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) + url := fmt.Sprintf("/api/v1/repos/%s/%d/%s/topics", user2.Name, repo2.GroupID, repo2.Name) // Test read topics using token req = NewRequest(t, "GET", url). @@ -158,12 +158,12 @@ func TestAPIRepoTopic(t *testing.T) { MakeRequest(t, req, http.StatusUnprocessableEntity) // Test add a topic when there is already maximum - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "t26"). + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%d/%s/topics/%s", user2.Name, repo2.GroupID, repo2.Name, "t26"). AddTokenAuth(token2) MakeRequest(t, req, http.StatusUnprocessableEntity) // Test delete a topic that repo doesn't have - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%d/%s/topics/%s", user2.Name, repo2.GroupID, repo2.Name, "Topicname1"). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNotFound) @@ -171,14 +171,14 @@ func TestAPIRepoTopic(t *testing.T) { token4 := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteRepository) // Test read topics with write access - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", org3.Name, repo3.Name)). + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/topics", org3.Name, repo3.GroupID, repo3.Name)). AddTokenAuth(token4) res = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, res, &topics) assert.Empty(t, topics.TopicNames) // Test add a topic to repo with write access (requires repo admin access) - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", org3.Name, repo3.Name, "topicName"). + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%d/%s/topics/%s", org3.Name, repo3.GroupID, repo3.Name, "topicName"). AddTokenAuth(token4) MakeRequest(t, req, http.StatusForbidden) } diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index 8c45d8881cf5d..0d434d8361786 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -36,8 +36,8 @@ func TestEditor(t *testing.T) { t.Run("DiffPreview", testEditorDiffPreview) t.Run("CreateFile", testEditorCreateFile) t.Run("EditFile", func(t *testing.T) { - testEditFile(t, sessionUser2, "user2", "repo1", "master", "README.md", "Hello, World (direct)\n") - testEditFileToNewBranch(t, sessionUser2, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (commit-to-new-branch)\n") + testEditFile(t, sessionUser2, 0, "user2", "repo1", "master", "README.md", "Hello, World (direct)\n") + testEditFileToNewBranch(t, sessionUser2, 0, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (commit-to-new-branch)\n") }) t.Run("PatchFile", testEditorPatchFile) t.Run("DeleteFile", func(t *testing.T) { @@ -56,7 +56,7 @@ func TestEditor(t *testing.T) { func testEditorCreateFile(t *testing.T) { session := loginUser(t, "user2") - testCreateFile(t, session, "user2", "repo1", "master", "", "test.txt", "Content") + testCreateFile(t, session, 0, "user2", "repo1", "master", "", "test.txt", "Content") testEditorActionPostRequestError(t, session, "/user2/repo1/_new/master/", map[string]string{ "tree_path": "test.txt", "commit_choice": "direct", @@ -69,12 +69,12 @@ func testEditorCreateFile(t *testing.T) { }, `Branch "master" already exists in this repository.`) } -func testCreateFile(t *testing.T, session *TestSession, user, repo, baseBranchName, newBranchName, filePath, content string) { +func testCreateFile(t *testing.T, session *TestSession, groupID int64, user, repo, baseBranchName, newBranchName, filePath, content string) { commitChoice := "direct" if newBranchName != "" && newBranchName != baseBranchName { commitChoice = "commit-to-new-branch" } - testEditorActionEdit(t, session, user, repo, "_new", baseBranchName, "", map[string]string{ + testEditorActionEdit(t, session, groupID, user, repo, "_new", baseBranchName, "", map[string]string{ "tree_path": filePath, "content": content, "commit_choice": commitChoice, @@ -119,10 +119,14 @@ func testEditorActionPostRequestError(t *testing.T, session *TestSession, reques assert.Equal(t, errorMessage, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) } -func testEditorActionEdit(t *testing.T, session *TestSession, user, repo, editorAction, branch, filePath string, params map[string]string) *httptest.ResponseRecorder { +func testEditorActionEdit(t *testing.T, session *TestSession, groupID int64, user, repo, editorAction, branch, filePath string, params map[string]string) *httptest.ResponseRecorder { params["tree_path"] = util.IfZero(params["tree_path"], filePath) newBranchName := util.Iif(params["commit_choice"] == "direct", branch, params["new_branch_name"]) - resp := testEditorActionPostRequest(t, session, fmt.Sprintf("/%s/%s/%s/%s/%s", user, repo, editorAction, branch, filePath), params) + var groupSegment string + if groupID > 0 { + groupSegment = fmt.Sprintf("%d/", groupID) + } + resp := testEditorActionPostRequest(t, session, fmt.Sprintf("/%s/%s%s/%s/%s/%s", user, groupSegment, repo, editorAction, branch, filePath), params) assert.Equal(t, http.StatusOK, resp.Code) assert.NotEmpty(t, test.RedirectURL(resp)) req := NewRequest(t, "GET", path.Join(user, repo, "raw/branch", newBranchName, params["tree_path"])) @@ -131,15 +135,15 @@ func testEditorActionEdit(t *testing.T, session *TestSession, user, repo, editor return resp } -func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) { - testEditorActionEdit(t, session, user, repo, "_edit", branch, filePath, map[string]string{ +func testEditFile(t *testing.T, session *TestSession, groupID int64, user, repo, branch, filePath, newContent string) { + testEditorActionEdit(t, session, groupID, user, repo, "_edit", branch, filePath, map[string]string{ "content": newContent, "commit_choice": "direct", }) } -func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, branch, targetBranch, filePath, newContent string) { - testEditorActionEdit(t, session, user, repo, "_edit", branch, filePath, map[string]string{ +func testEditFileToNewBranch(t *testing.T, session *TestSession, groupID int64, user, repo, branch, targetBranch, filePath, newContent string) { + testEditorActionEdit(t, session, groupID, user, repo, "_edit", branch, filePath, map[string]string{ "content": newContent, "commit_choice": "commit-to-new-branch", "new_branch_name": targetBranch, diff --git a/tests/integration/eventsource_test.go b/tests/integration/eventsource_test.go index 2ef4218977509..d2ccf24c44f9f 100644 --- a/tests/integration/eventsource_test.go +++ b/tests/integration/eventsource_test.go @@ -73,7 +73,7 @@ func TestEventSourceManagerRun(t *testing.T) { assert.Len(t, apiNL, 2) lastReadAt := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 <- only Notification 4 is in this filter ... - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s", user2.Name, repo1.Name, lastReadAt)). + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%d/%s/notifications?last_read_at=%s", user2.Name, repo1.GroupID, repo1.Name, lastReadAt)). AddTokenAuth(token) session.MakeRequest(t, req, http.StatusResetContent) diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go index e72b7b4ff1d58..1b496430e2afa 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -676,7 +676,7 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { t.Run("SuccessfullyPushAndCreateTestRepository", doGitPushTestRepository(tmpDir, "origin", "master")) // Finally, fetch repo from database and ensure the correct repository has been created - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame) + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame, ctx.GroupID) assert.NoError(t, err) assert.False(t, repo.IsEmpty) assert.True(t, repo.IsPrivate) @@ -816,7 +816,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string pr1, pr2 *issues_model.PullRequest commit string ) - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame) + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame, ctx.GroupID) require.NoError(t, err) pullNum := unittest.GetCount(t, &issues_model.PullRequest{}) diff --git a/tests/integration/lfs_getobject_test.go b/tests/integration/lfs_getobject_test.go index a87f38be8ac9d..5e2dc0006bcd0 100644 --- a/tests/integration/lfs_getobject_test.go +++ b/tests/integration/lfs_getobject_test.go @@ -42,7 +42,7 @@ func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string } func storeAndGetLfsToken(t *testing.T, content *[]byte, extraHeader *http.Header, expectedStatus int, ts ...auth.AccessTokenScope) *httptest.ResponseRecorder { - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1") + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1", 0) assert.NoError(t, err) oid := storeObjectInRepo(t, repo.ID, content) defer git_model.RemoveLFSMetaObjectByOid(db.DefaultContext, repo.ID, oid) @@ -67,7 +67,7 @@ func storeAndGetLfsToken(t *testing.T, content *[]byte, extraHeader *http.Header } func storeAndGetLfs(t *testing.T, content *[]byte, extraHeader *http.Header, expectedStatus int) *httptest.ResponseRecorder { - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1") + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1", 0) assert.NoError(t, err) oid := storeObjectInRepo(t, repo.ID, content) defer git_model.RemoveLFSMetaObjectByOid(db.DefaultContext, repo.ID, oid) diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index c33b2eb04de37..32bcc3734ea1b 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -30,7 +30,7 @@ func TestMirrorPull(t *testing.T) { ctx := t.Context() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - repoPath := repo_model.RepoPath(user.Name, repo.Name) + repoPath := repo_model.RepoPath(user.Name, repo.Name, repo.GroupID) opts := migration.MigrateOptions{ RepoName: "test_mirror", diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index a2247801f76b0..43414cf976930 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -705,6 +705,10 @@ func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) { FullRepoName: "user2/commitsonpr", Private: false, }, + { + FullRepoName: "user2/test_commit_revert", + Private: true, + }, } assert.Equal(t, reposExpected, reposCaptured) diff --git a/tests/integration/privateactivity_test.go b/tests/integration/privateactivity_test.go index a1fbadec99ede..cf77ba4c8fe70 100644 --- a/tests/integration/privateactivity_test.go +++ b/tests/integration/privateactivity_test.go @@ -35,7 +35,7 @@ func testPrivateActivityDoSomethingForActionEntries(t *testing.T) { session := loginUser(t, privateActivityTestUser) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues?state=all", owner.Name, repoBefore.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%d/%s/issues?state=all", owner.Name, repoBefore.GroupID, repoBefore.Name) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{ Body: "test", Title: "test", diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 3afa5f10f18f6..0ca643a5ad1e1 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -293,7 +293,7 @@ func TestCantMergeUnrelated(t *testing.T) { OwnerID: user1.ID, Name: "repo1", }) - path := repo_model.RepoPath(user1.Name, repo1.Name) + path := repo_model.RepoPath(user1.Name, repo1.Name, repo1.GroupID) err := git.NewCommand("read-tree", "--empty").Run(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) @@ -403,7 +403,7 @@ func TestFastForwardOnlyMerge(t *testing.T) { BaseBranch: "master", }) - gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) + gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name, repo1.GroupID)) assert.NoError(t, err) err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "FAST-FORWARD-ONLY", false) @@ -445,7 +445,7 @@ func TestCantFastForwardOnlyMergeDiverging(t *testing.T) { BaseBranch: "master", }) - gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) + gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name, repo1.GroupID)) assert.NoError(t, err) err = pull_service.Merge(t.Context(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "DIVERGING", false) diff --git a/tests/integration/repo_merge_upstream_test.go b/tests/integration/repo_merge_upstream_test.go index d33d31c6465f9..8aad4f8140b2a 100644 --- a/tests/integration/repo_merge_upstream_test.go +++ b/tests/integration/repo_merge_upstream_test.go @@ -41,7 +41,7 @@ func TestRepoMergeUpstream(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // create a fork - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseUser.Name, baseRepo.Name), &api.CreateForkOption{ + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%d/%s/forks", baseUser.Name, baseRepo.GroupID, baseRepo.Name), &api.CreateForkOption{ Name: util.ToPointer("test-repo-fork"), }).AddTokenAuth(token) MakeRequest(t, req, http.StatusAccepted) diff --git a/tests/integration/repo_search_test.go b/tests/integration/repo_search_test.go index 36a2e81f3bdb7..02cc24dda300e 100644 --- a/tests/integration/repo_search_test.go +++ b/tests/integration/repo_search_test.go @@ -29,7 +29,7 @@ func resultFilenames(doc *HTMLDoc) []string { func TestSearchRepo(t *testing.T) { defer tests.PrepareTestEnv(t)() - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1") + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1", 0) assert.NoError(t, err) code_indexer.UpdateRepoIndexer(repo) @@ -39,7 +39,7 @@ func TestSearchRepo(t *testing.T) { setting.Indexer.IncludePatterns = setting.IndexerGlobFromString("**.txt") setting.Indexer.ExcludePatterns = setting.IndexerGlobFromString("**/y/**") - repo, err = repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "glob") + repo, err = repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "glob", 0) assert.NoError(t, err) code_indexer.UpdateRepoIndexer(repo) diff --git a/tests/integration/repo_tag_test.go b/tests/integration/repo_tag_test.go index 8ea75085598e4..2a3042d7a5168 100644 --- a/tests/integration/repo_tag_test.go +++ b/tests/integration/repo_tag_test.go @@ -148,7 +148,7 @@ func TestRepushTag(t *testing.T) { _, _, err = git.NewCommand("push", "origin", "--delete", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // query the release by API and it should be a draft - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/tags/%s", owner.Name, repo.GroupID, repo.Name, "v2.0")) resp := MakeRequest(t, req, http.StatusOK) var respRelease *api.Release DecodeJSON(t, resp, &respRelease) @@ -157,7 +157,7 @@ func TestRepushTag(t *testing.T) { _, _, err = git.NewCommand("push", "origin", "--tags", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // query the release by API and it should not be a draft - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%d/%s/releases/tags/%s", owner.Name, repo.GroupID, repo.Name, "v2.0")) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &respRelease) assert.False(t, respRelease.IsDraft) diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index adfe07519faed..408c926a5d9f4 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -534,7 +534,7 @@ func TestGenerateRepository(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, generatedRepo) - exist, err := util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name, generatedRepo.GroupID)) assert.NoError(t, err) assert.True(t, exist) @@ -545,7 +545,7 @@ func TestGenerateRepository(t *testing.T) { // a failed creating because some mock data // create the repository directory so that the creation will fail after database record created. - assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "generated-from-template-44"), os.ModePerm)) + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "generated-from-template-44", generatedRepo.GroupID), os.ModePerm)) generatedRepo2, err := repo_service.GenerateRepository(db.DefaultContext, user2, user2, repo44, repo_service.GenerateRepoOptions{ Name: "generated-from-template-44", @@ -557,7 +557,7 @@ func TestGenerateRepository(t *testing.T) { // assert the cleanup is successful unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) - exist, err = util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name, generatedRepo.GroupID)) assert.NoError(t, err) assert.False(t, exist) } diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index f1abac8cfa1fb..638b870f4cdd4 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -59,7 +59,7 @@ func TestNewWebHookLink(t *testing.T) { } } -func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string, branchFilter ...string) { +func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, groupID int64, userName, repoName, url, event string, branchFilter ...string) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) var branchFilterString string if len(branchFilter) > 0 { @@ -150,10 +150,10 @@ func Test_WebhookCreate(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "create") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "create") // 2. trigger the webhook - testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + testAPICreateBranch(t, session, 0, "user2", "repo1", "master", "master2", http.StatusCreated) // 3. validate the webhook is triggered assert.Len(t, payloads, 1) @@ -182,10 +182,10 @@ func Test_WebhookDelete(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "delete") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "delete") // 2. trigger the webhook - testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + testAPICreateBranch(t, session, 0, "user2", "repo1", "master", "master2", http.StatusCreated) testAPIDeleteBranch(t, "master2", http.StatusNoContent) // 3. validate the webhook is triggered @@ -215,7 +215,7 @@ func Test_WebhookFork(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user1") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "fork") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "fork") // 2. trigger the webhook testRepoFork(t, session, "user2", "repo1", "user1", "repo1-fork", "master") @@ -247,7 +247,7 @@ func Test_WebhookIssueComment(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "issue_comment") t.Run("create comment", func(t *testing.T) { // 2. trigger the webhook @@ -331,7 +331,7 @@ func Test_WebhookRelease(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "release") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "release") // 2. trigger the webhook createNewRelease(t, session, "/user2/repo1", "v0.0.99", "v0.0.99", false, false) @@ -364,10 +364,10 @@ func Test_WebhookPush(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "push") // 2. trigger the webhook - testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") + testCreateFile(t, session, 0, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") // 3. validate the webhook is triggered assert.Equal(t, "push", triggeredEvent) @@ -397,10 +397,10 @@ func Test_WebhookPushDevBranch(t *testing.T) { session := loginUser(t, "user2") // only for dev branch - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "develop") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "push", "develop") // 2. this should not trigger the webhook - testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") + testCreateFile(t, session, 0, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") assert.Empty(t, triggeredEvent) assert.Empty(t, payloads) @@ -413,7 +413,7 @@ func Test_WebhookPushDevBranch(t *testing.T) { assert.NoError(t, err) // 3. trigger the webhook - testCreateFile(t, session, "user2", "repo1", "develop", "", "test_webhook_push.md", "# a test file for webhook push") + testCreateFile(t, session, 0, "user2", "repo1", "develop", "", "test_webhook_push.md", "# a test file for webhook push") afterCommitID, err := gitRepo.GetBranchCommitID("develop") assert.NoError(t, err) @@ -453,7 +453,7 @@ func Test_WebhookPushToNewBranch(t *testing.T) { session := loginUser(t, "user2") // only for dev branch - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "new_branch") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "push", "new_branch") repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) gitRepo, err := gitrepo.OpenRepository(t.Context(), repo1) @@ -464,7 +464,7 @@ func Test_WebhookPushToNewBranch(t *testing.T) { assert.NoError(t, err) // 2. trigger the webhook - testCreateFile(t, session, "user2", "repo1", "master", "new_branch", "test_webhook_push.md", "# a new push from new branch") + testCreateFile(t, session, 0, "user2", "repo1", "master", "new_branch", "test_webhook_push.md", "# a new push from new branch") afterCommitID, err := gitRepo.GetBranchCommitID("new_branch") assert.NoError(t, err) @@ -504,7 +504,7 @@ func Test_WebhookIssue(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "issues") // 2. trigger the webhook testNewIssue(t, session, "user2", "repo1", "Title1", "Description1") @@ -538,7 +538,7 @@ func Test_WebhookIssueDelete(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "issues") issueURL := testNewIssue(t, session, "user2", "repo1", "Title1", "Description1") // 2. trigger the webhook @@ -575,7 +575,7 @@ func Test_WebhookIssueAssign(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_assign") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "pull_request_assign") // 2. trigger the webhook, issue 2 is a pull request testIssueAssign(t, session, repo1.Link(), 2, user2.ID) @@ -609,7 +609,7 @@ func Test_WebhookIssueMilestone(t *testing.T) { // create a new webhook with special webhook for repo1 session := loginUser(t, "user2") repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_milestone") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "issue_milestone") t.Run("assign a milestone", func(t *testing.T) { // trigger the webhook @@ -681,9 +681,9 @@ func Test_WebhookPullRequest(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "pull_request") - testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + testAPICreateBranch(t, session, 0, "user2", "repo1", "master", "master2", http.StatusCreated) // 2. trigger the webhook repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") @@ -717,9 +717,9 @@ func Test_WebhookPullRequestDelete(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "pull_request") - testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + testAPICreateBranch(t, session, 0, "user2", "repo1", "master", "master2", http.StatusCreated) repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) issueURL := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") @@ -756,10 +756,10 @@ func Test_WebhookPullRequestComment(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_comment") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "pull_request_comment") // 2. trigger the webhook - testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + testAPICreateBranch(t, session, 0, "user2", "repo1", "master", "master2", http.StatusCreated) repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) prID := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") @@ -794,7 +794,7 @@ func Test_WebhookWiki(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "wiki") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "wiki") // 2. trigger the webhook testAPICreateWikiPage(t, session, "user2", "repo1", "Test Wiki Page", http.StatusCreated) @@ -900,7 +900,7 @@ func Test_WebhookStatus(t *testing.T) { // 1. create a new webhook with special webhook for repo1 session := loginUser(t, "user2") - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "status") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "status") repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) @@ -949,7 +949,7 @@ func Test_WebhookStatus_NoWrongTrigger(t *testing.T) { testCreateWebhookForRepo(t, session, "gitea", "user2", "repo1", provider.URL(), "push_only") // 2. trigger the webhook with a push action - testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") + testCreateFile(t, session, 0, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") // 3. validate the webhook is triggered with right event assert.Equal(t, "push", trigger) @@ -978,7 +978,7 @@ func Test_WebhookWorkflowJob(t *testing.T) { session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "workflow_job") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", provider.URL(), "workflow_job") repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) @@ -1008,7 +1008,7 @@ jobs: - run: echo 'cmd 2' ` opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts) + createWorkflowFile(t, token, "user2", "repo1", wfTreePath, repo1.GroupID, opts) commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) assert.NoError(t, err) @@ -1147,7 +1147,7 @@ func testWebhookWorkflowRun(t *testing.T, webhookData *workflowRunWebhook) { session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", webhookData.URL, "workflow_run") repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) @@ -1171,7 +1171,7 @@ jobs: steps: - run: echo 'test the webhook' `) - createWorkflowFile(t, token, "user2", "repo1", ".gitea/workflows/dispatch.yml", opts) + createWorkflowFile(t, token, "user2", "repo1", ".gitea/workflows/dispatch.yml", repo1.GroupID, opts) // 2.2 trigger the webhooks @@ -1193,7 +1193,7 @@ jobs: - run: echo 'cmd 2' ` opts = getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts) + createWorkflowFile(t, token, "user2", "repo1", wfTreePath, repo1.GroupID, opts) commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) assert.NoError(t, err) @@ -1245,7 +1245,7 @@ func testWebhookWorkflowRunDepthLimit(t *testing.T, webhookData *workflowRunWebh session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run") + testAPICreateWebhookForRepo(t, session, 0, "user2", "repo1", webhookData.URL, "workflow_run") repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) @@ -1270,7 +1270,7 @@ jobs: - run: echo 'test the webhook' ` opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts) + createWorkflowFile(t, token, "user2", "repo1", wfTreePath, repo1.GroupID, opts) commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) assert.NoError(t, err)