1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682 |
- # -*- coding: utf-8 -*-
- """
- (c) 2014-2018 - Copyright Red Hat Inc
- Authors:
- Pierre-Yves Chibon <pingou@pingoured.fr>
- Farhaan Bukhsh <farhaan.bukhsh@gmail.com>
- """
- # pylint: disable=too-many-lines
- # pylint: disable=too-many-branches
- # pylint: disable=too-many-locals
- # pylint: disable=too-many-statements
- # pylint: disable=bare-except
- # pylint: disable=broad-except
- from __future__ import unicode_literals, absolute_import
- import datetime
- import json
- import logging
- import os
- import re
- from math import ceil
- import flask
- import pygit2
- import kitchen.text.converters as ktc
- import six
- import werkzeug.utils
- from six import BytesIO
- from PIL import Image
- from sqlalchemy.exc import SQLAlchemyError
- from binaryornot.helpers import is_binary_string
- import pagure.exceptions
- import pagure.lib.git
- import pagure.lib.mimetype
- import pagure.lib.plugins
- import pagure.lib.query
- import pagure.lib.tasks
- import pagure.forms
- import pagure.ui.plugins
- from pagure.config import config as pagure_config
- from pagure.flask_app import _get_user
- from pagure.lib import encoding_utils
- from pagure.ui import UI_NS
- from pagure.utils import (
- __get_file_in_tree,
- login_required,
- is_true,
- stream_template,
- )
- from pagure.decorators import (
- is_repo_admin,
- is_admin_sess_timedout,
- has_issue_tracker,
- has_issue_or_pr_enabled,
- has_pr_enabled,
- )
- _log = logging.getLogger(__name__)
- def get_preferred_readme(tree):
- """ Establish some order about which README gets displayed
- if there are several in the repository. If none of the listed
- README files is availabe, display either the next file that
- starts with 'README' or nothing at all.
- """
- order = ["README.md", "README.rst", "README", "README.txt"]
- readmes = [x for x in tree if x.name.startswith("README")]
- if len(readmes) > 1:
- for i in order:
- for j in readmes:
- if i == j.name:
- return j
- elif len(readmes) == 1:
- return readmes[0]
- return None
- @UI_NS.route("/<repo>.git")
- @UI_NS.route("/<namespace>/<repo>.git")
- @UI_NS.route("/fork/<username>/<repo>.git")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>.git")
- def view_repo_git(repo, username=None, namespace=None):
- """ Redirect to the project index page when user wants to view
- the git repo of the project
- """
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_repo",
- repo=repo,
- username=username,
- namespace=namespace,
- )
- )
- @UI_NS.route("/<repo>/")
- @UI_NS.route("/<repo>")
- @UI_NS.route("/<namespace>/<repo>/")
- @UI_NS.route("/<namespace>/<repo>")
- @UI_NS.route("/fork/<username>/<repo>/")
- @UI_NS.route("/fork/<username>/<repo>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>")
- def view_repo(repo, username=None, namespace=None):
- """ Front page of a specific repo.
- """
- repo_db = flask.g.repo
- repo_obj = flask.g.repo_obj
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- head = repo_obj.head.shorthand
- else:
- head = None
- cnt = 0
- last_commits = []
- tree = []
- if not repo_obj.is_empty:
- try:
- for commit in repo_obj.walk(
- repo_obj.head.target, pygit2.GIT_SORT_NONE
- ):
- last_commits.append(commit)
- cnt += 1
- if cnt == 3:
- break
- tree = sorted(last_commits[0].tree, key=lambda x: x.filemode)
- except pygit2.GitError:
- pass
- readme = None
- safe = False
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- branchname = repo_obj.head.shorthand
- else:
- branchname = None
- project = pagure.lib.query.get_authorized_project(
- flask.g.session, repo, user=username, namespace=namespace
- )
- watch_users = set()
- watch_users.add(project.user.username)
- for access_type in project.access_users.keys():
- for user in project.access_users[access_type]:
- watch_users.add(user.username)
- for watcher in project.watchers:
- if watcher.watch_issues or watcher.watch_commits:
- watch_users.add(watcher.user.username)
- readmefile = get_preferred_readme(tree)
- if readmefile:
- name, ext = os.path.splitext(readmefile.name)
- content = __get_file_in_tree(
- repo_obj, last_commits[0].tree, [readmefile.name]
- ).data
- readme, safe = pagure.doc_utils.convert_readme(
- content,
- ext,
- view_file_url=flask.url_for(
- "ui_ns.view_raw_file",
- username=username,
- repo=repo_db.name,
- identifier=branchname,
- filename="",
- ),
- )
- return flask.render_template(
- "repo_info.html",
- select="overview",
- repo=repo_db,
- username=username,
- head=head,
- readme=readme,
- safe=safe,
- origin="view_repo",
- branchname=branchname,
- last_commits=last_commits,
- tree=tree,
- num_watchers=len(watch_users),
- )
- """
- @UI_NS.route('/<repo>/branch/<path:branchname>')
- @UI_NS.route('/<namespace>/<repo>/branch/<path:branchname>')
- @UI_NS.route('/fork/<username>/<repo>/branch/<path:branchname>')
- @UI_NS.route('/fork/<username>/<namespace>/<repo>/branch/<path:branchname>')
- def view_repo_branch(repo, branchname, username=None, namespace=None):
- ''' Returns the list of branches in the repo. '''
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- if branchname not in repo_obj.listall_branches():
- flask.abort(404, description='Branch not found')
- branch = repo_obj.lookup_branch(branchname)
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- head = repo_obj.head.shorthand
- else:
- head = None
- cnt = 0
- last_commits = []
- for commit in repo_obj.walk(branch.peel().hex, pygit2.GIT_SORT_NONE):
- last_commits.append(commit)
- cnt += 1
- if cnt == 3:
- break
- diff_commits = []
- if repo.is_fork and repo.parent:
- parentname = repo.parent.repopath('main')
- else:
- parentname = repo.repopath('main')
- orig_repo = pygit2.Repository(parentname)
- tree = None
- safe = False
- readme = None
- if not repo_obj.is_empty and not orig_repo.is_empty:
- if not orig_repo.head_is_unborn:
- compare_branch = orig_repo.lookup_branch(orig_repo.head.shorthand)
- else:
- compare_branch = None
- commit_list = []
- if compare_branch:
- commit_list = [
- commit.oid.hex
- for commit in orig_repo.walk(
- compare_branch.peel().hex,
- pygit2.GIT_SORT_NONE)
- ]
- repo_commit = repo_obj[branch.peel().hex]
- for commit in repo_obj.walk(
- repo_commit.oid.hex, pygit2.GIT_SORT_NONE):
- if commit.oid.hex in commit_list:
- break
- diff_commits.append(commit.oid.hex)
- tree = sorted(last_commits[0].tree, key=lambda x: x.filemode)
- for i in tree:
- name, ext = os.path.splitext(i.name)
- if name == 'README':
- content = __get_file_in_tree(
- repo_obj, last_commits[0].tree, [i.name]).data
- readme, safe = pagure.doc_utils.convert_readme(
- content, ext,
- view_file_url=flask.url_for(
- 'ui_ns.view_raw_file', username=username,
- namespace=repo.namespace,
- repo=repo.name, identifier=branchname, filename=''))
- return flask.render_template(
- 'repo_info.html',
- select='overview',
- repo=repo,
- head=head,
- username=username,
- branchname=branchname,
- origin='view_repo_branch',
- last_commits=last_commits,
- tree=tree,
- safe=safe,
- readme=readme,
- diff_commits=diff_commits,
- )
- """
- @UI_NS.route("/<repo>/commits/")
- @UI_NS.route("/<repo>/commits")
- @UI_NS.route("/<repo>/commits/<path:branchname>")
- @UI_NS.route("/<namespace>/<repo>/commits/")
- @UI_NS.route("/<namespace>/<repo>/commits")
- @UI_NS.route("/<namespace>/<repo>/commits/<path:branchname>")
- @UI_NS.route("/fork/<username>/<repo>/commits/")
- @UI_NS.route("/fork/<username>/<repo>/commits")
- @UI_NS.route("/fork/<username>/<repo>/commits/<path:branchname>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/commits/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/commits")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/commits/<path:branchname>")
- def view_commits(repo, branchname=None, username=None, namespace=None):
- """ Displays the commits of the specified repo.
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- commit = None
- branch = None
- if branchname and branchname in repo_obj.listall_branches():
- branch = repo_obj.lookup_branch(branchname)
- commit = branch.peel(pygit2.Commit)
- elif branchname:
- try:
- commit = repo_obj.get(branchname)
- except (ValueError, TypeError):
- pass
- if "refs/tags/%s" % branchname in list(repo_obj.references):
- ref = repo_obj.lookup_reference("refs/tags/%s" % branchname)
- commit = ref.peel(pygit2.Commit)
- # If we're arriving here from the release page, we may have a Tag
- # where we expected a commit, in this case, get the actual commit
- if isinstance(commit, pygit2.Tag):
- commit = commit.peel(pygit2.Commit)
- branchname = commit.oid.hex
- elif isinstance(commit, pygit2.Blob):
- try:
- commit = commit.peel(pygit2.Commit)
- branchname = commit.oid.hex
- except Exception:
- flask.abort(
- 404, description="Invalid branch/identifier provided"
- )
- elif not repo_obj.is_empty and not repo_obj.head_is_unborn:
- branch = repo_obj.lookup_branch(repo_obj.head.shorthand)
- commit = branch.peel(pygit2.Commit)
- branchname = branch.branch_name
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- head = repo_obj.head.shorthand
- else:
- head = None
- try:
- page = int(flask.request.args.get("page", 1))
- except (ValueError, TypeError):
- page = 1
- author = flask.request.args.get("author", None)
- author_obj = None
- if author:
- try:
- author_obj = pagure.lib.query.get_user(flask.g.session, author)
- except pagure.exceptions.PagureException:
- pass
- if not author_obj:
- flask.flash("No user found for the author: %s" % author, "error")
- limit = pagure_config["ITEM_PER_PAGE"]
- start = limit * (page - 1)
- end = limit * page
- n_commits = 0
- last_commits = []
- if commit:
- for commit in repo_obj.walk(commit.hex, pygit2.GIT_SORT_NONE):
- # Filters the commits for a user
- if author_obj:
- tmp = False
- for email in author_obj.emails:
- if email.email == commit.author.email:
- tmp = True
- break
- if not tmp:
- continue
- if n_commits >= start and n_commits <= end:
- last_commits.append(commit)
- n_commits += 1
- total_page = int(ceil(n_commits / float(limit)) if n_commits > 0 else 1)
- diff_commits = []
- diff_commits_full = []
- if repo.is_fork and repo.parent:
- parentname = repo.parent.repopath("main")
- else:
- parentname = repo.repopath("main")
- orig_repo = pygit2.Repository(parentname)
- if (
- not repo_obj.is_empty
- and not orig_repo.is_empty
- and len(repo_obj.listall_branches()) > 1
- ):
- if not orig_repo.head_is_unborn:
- compare_branch = orig_repo.lookup_branch(orig_repo.head.shorthand)
- else:
- compare_branch = None
- if compare_branch and branch:
- (
- diff,
- diff_commits_full,
- orig_commit,
- ) = pagure.lib.git.get_diff_info(
- repo_obj,
- orig_repo,
- branch.branch_name,
- compare_branch.branch_name,
- )
- for commit in diff_commits_full:
- diff_commits.append(commit.oid.hex)
- return flask.render_template(
- "commits.html",
- select="commits",
- origin="view_commits",
- repo=repo,
- username=username,
- head=head,
- branchname=branchname,
- last_commits=last_commits,
- diff_commits=diff_commits,
- diff_commits_full=diff_commits_full,
- number_of_commits=n_commits,
- page=page,
- total_page=total_page,
- flag_statuses_labels=json.dumps(pagure_config["FLAG_STATUSES_LABELS"]),
- )
- @UI_NS.route("/<repo>/c/<commit1>..<commit2>/")
- @UI_NS.route("/<repo>/c/<commit1>..<commit2>")
- @UI_NS.route("/<namespace>/<repo>/c/<commit1>..<commit2>/")
- @UI_NS.route("/<namespace>/<repo>/c/<commit1>..<commit2>")
- @UI_NS.route("/fork/<username>/<repo>/c/<commit1>..<commit2>/")
- @UI_NS.route("/fork/<username>/<repo>/c/<commit1>..<commit2>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commit1>..<commit2>/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commit1>..<commit2>")
- def compare_commits(repo, commit1, commit2, username=None, namespace=None):
- """ Compares two commits for specified repo
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- head = repo_obj.head.shorthand
- else:
- head = None
- # Check commit1 and commit2 existence
- commit1_obj = repo_obj.get(commit1)
- commit2_obj = repo_obj.get(commit2)
- if commit1_obj is None:
- flask.abort(404, description="First commit does not exist")
- if commit2_obj is None:
- flask.abort(404, description="Last commit does not exist")
- # Get commits diff data
- diff = repo_obj.diff(commit1, commit2)
- # Get commits list
- diff_commits = []
- order = pygit2.GIT_SORT_NONE
- first_commit = commit1
- last_commit = commit2
- commits = [
- commit.oid.hex[: len(first_commit)]
- for commit in repo_obj.walk(last_commit, pygit2.GIT_SORT_NONE)
- ]
- if first_commit not in commits:
- first_commit = commit2
- last_commit = commit1
- for commit in repo_obj.walk(last_commit, order):
- diff_commits.append(commit)
- if commit.oid.hex == first_commit or commit.oid.hex.startswith(
- first_commit
- ):
- break
- if first_commit == commit2:
- diff_commits.reverse()
- return flask.render_template(
- "repo_comparecommits.html",
- select="commits",
- origin="compare_commits",
- repo=repo,
- username=username,
- head=head,
- commit1=commit1,
- commit2=commit2,
- diff=diff,
- diff_commits=diff_commits,
- )
- @UI_NS.route("/<repo>/blob/<path:identifier>/f/<path:filename>")
- @UI_NS.route("/<namespace>/<repo>/blob/<path:identifier>/f/<path:filename>")
- @UI_NS.route(
- "/fork/<username>/<repo>/blob/<path:identifier>/f/<path:filename>"
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/blob/<path:identifier>/f/"
- "<path:filename>"
- )
- def view_file(repo, identifier, filename, username=None, namespace=None):
- """ Displays the content of a file or a tree for the specified repo.
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- if repo_obj.is_empty:
- flask.abort(404, description="Empty repo cannot have a file")
- if identifier in repo_obj.listall_branches():
- branchname = identifier
- branch = repo_obj.lookup_branch(identifier)
- commit = branch.peel(pygit2.Commit)
- else:
- try:
- commit = repo_obj.get(identifier)
- branchname = identifier
- except ValueError:
- if "master" not in repo_obj.listall_branches():
- flask.abort(404, description="Branch not found")
- # If it's not a commit id then it's part of the filename
- commit = repo_obj[repo_obj.head.target]
- branchname = "master"
- if isinstance(commit, pygit2.Tag):
- commit = commit.peel(pygit2.Commit)
- tree = None
- if isinstance(commit, pygit2.Tree):
- tree = commit
- elif isinstance(commit, pygit2.Commit):
- tree = commit.tree
- if tree and commit and not isinstance(commit, pygit2.Blob):
- content = __get_file_in_tree(
- repo_obj, tree, filename.split("/"), bail_on_tree=True
- )
- if not content:
- flask.abort(404, description="File not found")
- content = repo_obj[content.oid]
- else:
- content = commit
- if not content:
- flask.abort(404, description="File not found")
- readme = None
- safe = False
- readme_ext = None
- headers = {}
- huge = False
- isbinary = False
- if "data" in dir(content):
- isbinary = is_binary_string(content.data)
- if isinstance(content, pygit2.Blob):
- rawtext = is_true(flask.request.args.get("text"))
- ext = filename[filename.rfind(".") :]
- if ext in (
- ".gif",
- ".png",
- ".bmp",
- ".tif",
- ".tiff",
- ".jpg",
- ".jpeg",
- ".ppm",
- ".pnm",
- ".pbm",
- ".pgm",
- ".webp",
- ".ico",
- ):
- try:
- Image.open(BytesIO(content.data))
- output_type = "image"
- except IOError as err:
- _log.debug("Failed to load image %s, error: %s", filename, err)
- output_type = "binary"
- elif ext in (".rst", ".mk", ".md", ".markdown") and not rawtext:
- content, safe = pagure.doc_utils.convert_readme(content.data, ext)
- output_type = "markup"
- elif "data" in dir(content) and not isbinary:
- file_content = None
- try:
- file_content = encoding_utils.decode(
- ktc.to_bytes(content.data)
- )
- except pagure.exceptions.PagureException:
- # We cannot decode the file, so let's pretend it's a binary
- # file and let the user download it instead of displaying
- # it.
- output_type = "binary"
- if file_content is not None:
- output_type = "file"
- content = encoding_utils.decode(content.data)
- else:
- output_type = "binary"
- elif not isbinary:
- output_type = "file"
- huge = True
- safe = False
- content = content.data.decode("utf-8")
- else:
- output_type = "binary"
- elif isinstance(content, pygit2.Commit):
- flask.abort(404, description="File not found")
- else:
- content = sorted(content, key=lambda x: x.filemode)
- for i in content:
- name, ext = os.path.splitext(i.name)
- if not isinstance(name, six.text_type):
- name = name.decode("utf-8")
- if name == "README":
- readme_file = __get_file_in_tree(
- repo_obj, content, [i.name]
- ).data
- readme, safe = pagure.doc_utils.convert_readme(
- readme_file, ext
- )
- readme_ext = ext
- output_type = "tree"
- if output_type == "binary":
- headers[str("Content-Disposition")] = "attachment"
- return flask.Response(
- flask.stream_with_context(
- stream_template(
- flask.current_app,
- "file.html",
- select="tree",
- repo=repo,
- origin="view_file",
- username=username,
- branchname=branchname,
- filename=filename,
- content=content,
- output_type=output_type,
- readme=readme,
- readme_ext=readme_ext,
- safe=safe,
- huge=huge,
- )
- ),
- 200,
- headers,
- )
- @UI_NS.route("/<repo>/raw/<path:identifier>")
- @UI_NS.route("/<namespace>/<repo>/raw/<path:identifier>")
- @UI_NS.route("/<repo>/raw/<path:identifier>/f/<path:filename>")
- @UI_NS.route("/<namespace>/<repo>/raw/<path:identifier>/f/<path:filename>")
- @UI_NS.route("/fork/<username>/<repo>/raw/<path:identifier>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/raw/<path:identifier>")
- @UI_NS.route("/fork/<username>/<repo>/raw/<path:identifier>/f/<path:filename>")
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/raw/<path:identifier>/f/"
- "<path:filename>"
- )
- def view_raw_file(
- repo, identifier, filename=None, username=None, namespace=None
- ):
- """ Displays the raw content of a file of a commit for the specified repo.
- """
- repo_obj = flask.g.repo_obj
- if repo_obj.is_empty:
- flask.abort(404, description="Empty repo cannot have a file")
- if identifier in repo_obj.listall_branches():
- branch = repo_obj.lookup_branch(identifier)
- commit = branch.peel(pygit2.Commit)
- else:
- try:
- commit = repo_obj.get(identifier)
- except ValueError:
- if "master" not in repo_obj.listall_branches():
- flask.abort(404, description="Branch not found")
- # If it's not a commit id then it's part of the filename
- commit = repo_obj[repo_obj.head.target]
- if not commit:
- flask.abort(404, description="Commit %s not found" % (identifier))
- if isinstance(commit, pygit2.Tag):
- commit = commit.peel(pygit2.Commit)
- if filename:
- if isinstance(commit, pygit2.Blob):
- content = commit
- else:
- content = __get_file_in_tree(
- repo_obj, commit.tree, filename.split("/"), bail_on_tree=True
- )
- if not content or isinstance(content, pygit2.Tree):
- flask.abort(404, description="File not found")
- data = repo_obj[content.oid].data
- else:
- if commit.parents:
- # We need to take this not so nice road to ensure that the
- # identifier retrieved from the URL is actually valid
- try:
- parent = repo_obj.revparse_single("%s^" % identifier)
- diff = repo_obj.diff(parent, commit)
- except (KeyError, ValueError):
- flask.abort(404, description="Identifier not found")
- else:
- # First commit in the repo
- diff = commit.tree.diff_to_tree(swap=True)
- data = diff.patch
- if not data:
- flask.abort(404, description="No content found")
- return (data, 200, pagure.lib.mimetype.get_type_headers(filename, data))
- @UI_NS.route("/<repo>/blame/<path:filename>")
- @UI_NS.route("/<namespace>/<repo>/blame/<path:filename>")
- @UI_NS.route("/fork/<username>/<repo>/blame/<path:filename>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/blame/<path:filename>")
- def view_blame_file(repo, filename, username=None, namespace=None):
- """ Displays the blame of a file or a tree for the specified repo.
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- branchname = flask.request.args.get("identifier")
- if repo_obj.is_empty:
- flask.abort(404, description="Empty repo cannot have a file")
- if branchname is None:
- if repo_obj.head_is_unborn:
- flask.abort(
- 404, description="Identifier is mandatory on unborn HEAD repos"
- )
- branchname = repo_obj.head.shorthand
- commit = repo_obj[repo_obj.head.target]
- else:
- if branchname in repo_obj.listall_branches():
- branch = repo_obj.lookup_branch(branchname)
- commit = branch.peel(pygit2.Commit)
- elif branchname in pagure.lib.git.get_git_tags(repo):
- branch = repo_obj.lookup_reference(
- "refs/tags/{}".format(branchname)
- )
- commit = branch.peel(pygit2.Commit)
- else:
- try:
- commit = repo_obj[branchname]
- except ValueError:
- flask.abort(
- 404, description="Cannot find specified identifier"
- )
- try:
- if isinstance(commit, pygit2.Tag):
- commit = commit.peel(pygit2.Commit)
- elif isinstance(commit, pygit2.Blob):
- commit = commit.peel(pygit2.Commit)
- except Exception:
- flask.abort(404, description="Invalid identified provided")
- content = __get_file_in_tree(
- repo_obj, commit.tree, filename.split("/"), bail_on_tree=True
- )
- if not content:
- flask.abort(404, description="File not found")
- if not isinstance(content, pygit2.Blob):
- flask.abort(404, description="File not found")
- if is_binary_string(content.data):
- flask.abort(400, description="Binary files cannot be blamed")
- try:
- content = encoding_utils.decode(content.data)
- except pagure.exceptions.PagureException:
- # We cannot decode the file, so bail but warn the admins
- _log.exception("File could not be decoded")
- flask.abort(500, description="File could not be decoded")
- blame = repo_obj.blame(filename, newest_commit=commit.oid.hex)
- return flask.render_template(
- "blame.html",
- select="tree",
- repo=repo,
- origin="view_file",
- username=username,
- filename=filename,
- branchname=branchname,
- content=content,
- output_type="blame",
- blame=blame,
- )
- @UI_NS.route("/<repo>/history/<path:filename>")
- @UI_NS.route("/<namespace>/<repo>/history/<path:filename>")
- @UI_NS.route("/fork/<username>/<repo>/history/<path:filename>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/history/<path:filename>")
- def view_history_file(repo, filename, username=None, namespace=None):
- """ Displays the history of a file or a tree for the specified repo.
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- branchname = flask.request.args.get("identifier")
- if repo_obj.is_empty:
- flask.abort(404, description="Empty repo cannot have a file")
- if not branchname:
- try:
- branch = repo_obj.lookup_branch(repo_obj.head.shorthand)
- branchname = branch.branch_name
- except pygit2.GitError:
- flask.abort(400, description="Invalid repository")
- try:
- log = pagure.lib.repo.PagureRepo.log(
- flask.g.reponame,
- log_options=["--pretty=oneline", "--abbrev-commit"],
- target=filename,
- fromref=branchname,
- )
- if log.strip():
- log = [line.split(" ", 1) for line in log.strip().split("\n")]
- else:
- log = []
- except Exception:
- log = []
- if not log:
- flask.abort(400, description="No history could be found for this file")
- return flask.render_template(
- "file_history.html",
- select="tree",
- repo=repo,
- origin="view_file",
- username=username,
- filename=filename,
- branchname=branchname,
- output_type="history",
- log=log,
- )
- @UI_NS.route("/<repo>/c/<commitid>/")
- @UI_NS.route("/<repo>/c/<commitid>")
- @UI_NS.route("/<namespace>/<repo>/c/<commitid>/")
- @UI_NS.route("/<namespace>/<repo>/c/<commitid>")
- @UI_NS.route("/fork/<username>/<repo>/c/<commitid>/")
- @UI_NS.route("/fork/<username>/<repo>/c/<commitid>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>")
- def view_commit(repo, commitid, username=None, namespace=None):
- """ Render a commit in a repo
- """
- repo = flask.g.repo
- if not repo:
- flask.abort(404, description="Project not found")
- repo_obj = flask.g.repo_obj
- branchname = flask.request.args.get("branch", None)
- splitview = flask.request.args.get("splitview", False)
- if "splitview" in flask.request.args:
- splitview = True
- else:
- splitview = False
- if branchname and branchname not in repo_obj.listall_branches():
- branchname = None
- try:
- commit = repo_obj.get(commitid)
- except ValueError:
- flask.abort(404, description="Commit not found")
- if commit is None:
- flask.abort(404, description="Commit not found")
- if isinstance(commit, pygit2.Blob):
- flask.abort(404, description="Commit not found")
- if isinstance(commit, pygit2.Tag):
- commit = commit.peel(pygit2.Commit)
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_commit",
- repo=repo.name,
- username=username,
- namespace=repo.namespace,
- commitid=commit.hex,
- )
- )
- if commit.parents:
- diff = repo_obj.diff(commit.parents[0], commit)
- else:
- # First commit in the repo
- diff = commit.tree.diff_to_tree(swap=True)
- if diff:
- diff.find_similar()
- return flask.render_template(
- "commit.html",
- select="commits",
- repo=repo,
- branchname=branchname,
- username=username,
- commitid=commitid,
- commit=commit,
- diff=diff,
- splitview=splitview,
- flags=pagure.lib.query.get_commit_flag(
- flask.g.session, repo, commitid
- ),
- )
- @UI_NS.route("/<repo>/c/<commitid>.patch")
- @UI_NS.route("/<namespace>/<repo>/c/<commitid>.patch")
- @UI_NS.route("/fork/<username>/<repo>/c/<commitid>.patch")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>.patch")
- def view_commit_patch(repo, commitid, username=None, namespace=None):
- """ Render a commit in a repo as patch
- """
- return view_commit_patch_or_diff(
- repo, commitid, username, namespace, diff=False
- )
- @UI_NS.route("/<repo>/c/<commitid>.diff")
- @UI_NS.route("/<namespace>/<repo>/c/<commitid>.diff")
- @UI_NS.route("/fork/<username>/<repo>/c/<commitid>.diff")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>.diff")
- def view_commit_diff(repo, commitid, username=None, namespace=None):
- """ Render a commit in a repo as diff
- """
- is_js = is_true(flask.request.args.get("js"))
- return view_commit_patch_or_diff(
- repo, commitid, username, namespace, diff=True, is_js=is_js
- )
- def view_commit_patch_or_diff(
- repo, commitid, username=None, namespace=None, diff=False, is_js=False
- ):
- """ Renders a commit either as a patch or as a diff. """
- repo_obj = flask.g.repo_obj
- if is_js:
- errorresponse = flask.jsonify(
- {"code": "ERROR", "message": "Commit not found"}
- )
- errorresponse.status_code = 404
- try:
- commit = repo_obj.get(commitid)
- except ValueError:
- if is_js:
- return errorresponse
- else:
- flask.abort(404, description="Commit not found")
- if commit is None:
- if is_js:
- return errorresponse
- else:
- flask.abort(404, description="Commit not found")
- if is_js:
- patches = pagure.lib.git.commit_to_patch(
- repo_obj, commit, diff_view=True, find_similar=True, separated=True
- )
- diffs = {}
- for idx, patch in enumerate(patches):
- diffs[idx + 1] = patch
- return flask.jsonify(diffs)
- else:
- patch = pagure.lib.git.commit_to_patch(
- repo_obj, commit, diff_view=diff
- )
- return flask.Response(patch, content_type="text/plain;charset=UTF-8")
- @UI_NS.route("/<repo>/tree/")
- @UI_NS.route("/<repo>/tree")
- @UI_NS.route("/<namespace>/<repo>/tree/")
- @UI_NS.route("/<namespace>/<repo>/tree")
- @UI_NS.route("/<repo>/tree/<path:identifier>")
- @UI_NS.route("/<namespace>/<repo>/tree/<path:identifier>")
- @UI_NS.route("/fork/<username>/<repo>/tree/")
- @UI_NS.route("/fork/<username>/<repo>/tree")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/tree/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/tree")
- @UI_NS.route("/fork/<username>/<repo>/tree/<path:identifier>")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/tree/<path:identifier>")
- def view_tree(repo, identifier=None, username=None, namespace=None):
- """ Render the tree of the repo
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- branchname = None
- content = None
- output_type = None
- commit = None
- readme = None
- safe = False
- readme_ext = None
- if not repo_obj.is_empty:
- if identifier in repo_obj.listall_branches():
- branchname = identifier
- branch = repo_obj.lookup_branch(identifier)
- commit = branch.peel(pygit2.Commit)
- else:
- try:
- commit = repo_obj.get(identifier)
- branchname = identifier
- except (ValueError, TypeError):
- # If it's not a commit id then it's part of the filename
- if not repo_obj.head_is_unborn:
- branchname = repo_obj.head.shorthand
- commit = repo_obj[repo_obj.head.target]
- if identifier:
- flask.flash(
- "'%s' not found in the git repository, going back "
- "to: %s" % (identifier, branchname),
- "error",
- )
- # If we're arriving here from the release page, we may have a Tag
- # where we expected a commit, in this case, get the actual commit
- if isinstance(commit, pygit2.Tag):
- commit = commit.peel(pygit2.Commit)
- branchname = commit.oid.hex
- if commit and not isinstance(commit, pygit2.Blob):
- content = sorted(commit.tree, key=lambda x: x.filemode)
- for i in commit.tree:
- name, ext = os.path.splitext(i.name)
- if name == "README":
- readme_file = __get_file_in_tree(
- repo_obj, commit.tree, [i.name]
- ).data
- readme, safe = pagure.doc_utils.convert_readme(
- readme_file, ext
- )
- readme_ext = ext
- output_type = "tree"
- return flask.render_template(
- "file.html",
- select="tree",
- origin="view_tree",
- repo=repo,
- username=username,
- branchname=branchname,
- filename="",
- content=content,
- output_type=output_type,
- readme=readme,
- readme_ext=readme_ext,
- safe=safe,
- )
- @UI_NS.route("/<repo>/releases/")
- @UI_NS.route("/<repo>/releases")
- @UI_NS.route("/<namespace>/<repo>/releases/")
- @UI_NS.route("/<namespace>/<repo>/releases")
- @UI_NS.route("/fork/<username>/<repo>/releases/")
- @UI_NS.route("/fork/<username>/<repo>/releases")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/releases/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/releases")
- def view_tags(repo, username=None, namespace=None):
- """ Presents all the tags of the project.
- """
- repo = flask.g.repo
- tags = pagure.lib.git.get_git_tags_objects(repo)
- upload_folder_path = pagure_config["UPLOAD_FOLDER_PATH"] or ""
- pagure_checksum = os.path.exists(
- os.path.join(upload_folder_path, repo.fullname, "CHECKSUMS")
- )
- return flask.render_template(
- "releases.html",
- select="tags",
- username=username,
- repo=repo,
- tags=tags,
- pagure_checksum=pagure_checksum,
- )
- @UI_NS.route("/<repo>/branches/")
- @UI_NS.route("/<repo>/branches")
- @UI_NS.route("/<namespace>/<repo>/branches/")
- @UI_NS.route("/<namespace>/<repo>/branches")
- @UI_NS.route("/fork/<username>/<repo>/branches/")
- @UI_NS.route("/fork/<username>/<repo>/branches")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/branches/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/branches")
- def view_branches(repo, username=None, namespace=None):
- """ Branches
- """
- repo_db = flask.g.repo
- repo_obj = flask.g.repo_obj
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- head = repo_obj.head.shorthand
- else:
- head = None
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- branchname = repo_obj.head.shorthand
- else:
- branchname = None
- return flask.render_template(
- "repo_branches.html",
- select="branches",
- repo=repo_db,
- username=username,
- head=head,
- origin="view_repo",
- branchname=branchname,
- )
- @UI_NS.route("/<repo>/forks/")
- @UI_NS.route("/<repo>/forks")
- @UI_NS.route("/<namespace>/<repo>/forks/")
- @UI_NS.route("/<namespace>/<repo>/forks")
- @UI_NS.route("/fork/<username>/<repo>/forks/")
- @UI_NS.route("/fork/<username>/<repo>/forks")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/forks/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/forks")
- def view_forks(repo, username=None, namespace=None):
- """ Forks
- """
- return flask.render_template(
- "repo_forks.html", select="forks", username=username, repo=flask.g.repo
- )
- @UI_NS.route("/<repo>/upload/", methods=("GET", "POST"))
- @UI_NS.route("/<repo>/upload", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/upload/", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/upload", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/upload/", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/upload", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/upload/", methods=("GET", "POST")
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/upload", methods=("GET", "POST")
- )
- @login_required
- @is_repo_admin
- def new_release(repo, username=None, namespace=None):
- """ Upload a new release.
- """
- if not pagure_config.get("UPLOAD_FOLDER_PATH") or not pagure_config.get(
- "UPLOAD_FOLDER_URL"
- ):
- flask.abort(404)
- repo = flask.g.repo
- form = pagure.forms.UploadFileForm()
- if form.validate_on_submit():
- filenames = []
- error = False
- for filestream in flask.request.files.getlist("filestream"):
- filename = werkzeug.utils.secure_filename(filestream.filename)
- filenames.append(filename)
- try:
- folder = os.path.join(
- pagure_config["UPLOAD_FOLDER_PATH"], repo.fullname
- )
- if not os.path.exists(folder):
- os.makedirs(folder)
- dest = os.path.join(folder, filename)
- if os.path.exists(dest):
- raise pagure.exceptions.PagureException(
- "This tarball has already been uploaded"
- )
- filestream.save(dest)
- flask.flash('File "%s" uploaded' % filename)
- except pagure.exceptions.PagureException as err:
- _log.debug(err)
- flask.flash(str(err), "error")
- error = True
- except Exception as err: # pragma: no cover
- _log.exception(err)
- flask.flash("Upload failed", "error")
- error = True
- if not error:
- task = pagure.lib.tasks.update_checksums_file.delay(
- folder=folder, filenames=filenames
- )
- _log.info(
- "Updating checksums for %s of project %s in task: %s"
- % (filenames, repo.fullname, task.id)
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_tags",
- repo=repo.name,
- username=username,
- namespace=repo.namespace,
- )
- )
- return flask.render_template(
- "new_release.html",
- select="tags",
- username=username,
- repo=repo,
- form=form,
- )
- @UI_NS.route("/<repo>/settings/", methods=("GET", "POST"))
- @UI_NS.route("/<repo>/settings", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/settings/", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/settings", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/settings/", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/settings", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/settings/", methods=("GET", "POST")
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/settings", methods=("GET", "POST")
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def view_settings(repo, username=None, namespace=None):
- """ Presents the settings of the project.
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- plugins = pagure.lib.plugins.get_plugin_names(
- pagure_config.get("DISABLED_PLUGINS")
- )
- tags = pagure.lib.query.get_tags_of_project(flask.g.session, repo)
- form = pagure.forms.ConfirmationForm()
- tag_form = pagure.forms.AddIssueTagForm()
- branches = repo_obj.listall_branches()
- branches_form = pagure.forms.DefaultBranchForm(branches=branches)
- priority_form = pagure.forms.DefaultPriorityForm(
- priorities=repo.priorities.values()
- )
- if form.validate_on_submit():
- settings = {}
- for key in flask.request.form:
- if key == "csrf_token":
- continue
- settings[key] = flask.request.form[key]
- try:
- message = pagure.lib.query.update_project_settings(
- flask.g.session,
- repo=repo,
- settings=settings,
- user=flask.g.fas_user.username,
- )
- flask.g.session.commit()
- flask.flash(message)
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_repo",
- username=username,
- repo=repo.name,
- namespace=repo.namespace,
- )
- )
- except pagure.exceptions.PagureException as msg:
- flask.g.session.rollback()
- _log.debug(msg)
- flask.flash(str(msg), "error")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- if not repo_obj.is_empty and not repo_obj.head_is_unborn:
- branchname = repo_obj.head.shorthand
- else:
- branchname = None
- if flask.request.method == "GET" and branchname:
- branches_form.branches.data = branchname
- priority_form.priority.data = repo.default_priority
- return flask.render_template(
- "settings.html",
- select="settings",
- username=username,
- repo=repo,
- access_users=repo.access_users,
- access_groups=repo.access_groups,
- form=form,
- tag_form=tag_form,
- branches_form=branches_form,
- priority_form=priority_form,
- tags=tags,
- plugins=plugins,
- branchname=branchname,
- pagure_admin=pagure.utils.is_admin(),
- )
- @UI_NS.route("/<repo>/settings/test_hook", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/settings/test_hook", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<repo>/settings/test_hook", methods=("GET", "POST")
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/settings/test_hook",
- methods=("GET", "POST"),
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def test_web_hook(repo, username=None, namespace=None):
- """ Endpoint that can be called to send a test message to the web-hook
- service allowing to test the web-hooks set.
- """
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- pagure.lib.notify.log(
- project=repo,
- topic="Test.notification",
- msg={"content": "Test message"},
- webhook=True,
- )
- flask.flash("Notification triggered")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=repo.namespace,
- )
- + "#projectoptions-tab"
- )
- @UI_NS.route("/<repo>/update", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/update", methods=["POST"])
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/update", methods=["POST"])
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def update_project(repo, username=None, namespace=None):
- """ Update the description of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.ProjectFormSimplified()
- if form.validate_on_submit():
- try:
- repo.description = form.description.data
- repo.avatar_email = form.avatar_email.data.strip()
- repo.url = form.url.data.strip()
- if repo.private:
- repo.private = form.private.data
- pagure.lib.query.update_tags(
- flask.g.session,
- repo,
- tags=[t.strip() for t in form.tags.data.split(",")],
- username=flask.g.fas_user.username,
- )
- flask.g.session.add(repo)
- flask.g.session.commit()
- flask.flash("Project updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- else:
- for field in form.errors:
- flask.flash(
- 'Field "%s" errored with errors: %s'
- % (field, ", ".join(form.errors[field])),
- "error",
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=repo.namespace,
- )
- + "#projectdetails-tab"
- )
- @UI_NS.route("/<repo>/update/priorities", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update/priorities", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/update/priorities", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/update/priorities", methods=["POST"]
- )
- @login_required
- @has_issue_tracker
- @is_admin_sess_timedout
- @is_repo_admin
- def update_priorities(repo, username=None, namespace=None):
- """ Update the priorities of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- error = False
- if form.validate_on_submit():
- weights = [
- w.strip()
- for w in flask.request.form.getlist("priority_weigth")
- if w.strip()
- ]
- try:
- weights = [int(w) for w in weights]
- except (ValueError, TypeError):
- flask.flash("Priorities weights must be numbers", "error")
- error = True
- titles = [
- p.strip()
- for p in flask.request.form.getlist("priority_title")
- if p.strip()
- ]
- if len(weights) != len(titles):
- flask.flash(
- "Priorities weights and titles are not of the same length",
- "error",
- )
- error = True
- for weight in weights:
- if weights.count(weight) != 1:
- flask.flash(
- "Priority weight %s is present %s times"
- % (weight, weights.count(weight)),
- "error",
- )
- error = True
- break
- for title in titles:
- if titles.count(title) != 1:
- flask.flash(
- "Priority %s is present %s times"
- % (title, titles.count(title)),
- "error",
- )
- error = True
- break
- if not error:
- priorities = {}
- if weights:
- for cnt in range(len(weights)):
- priorities[weights[cnt]] = titles[cnt]
- priorities[""] = ""
- try:
- repo.priorities = priorities
- if repo.default_priority not in priorities.values():
- flask.flash(
- "Default priority reset as it is no longer one of "
- "set priorities."
- )
- repo.default_priority = None
- flask.g.session.add(repo)
- flask.g.session.commit()
- flask.flash("Priorities updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=repo.namespace,
- )
- + "#priorities-tab"
- )
- @UI_NS.route("/<repo>/update/default_priority", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update/default_priority", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<repo>/update/default_priority", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/update/default_priority",
- methods=["POST"],
- )
- @login_required
- @has_issue_tracker
- @is_admin_sess_timedout
- @is_repo_admin
- def default_priority(repo, username=None, namespace=None):
- """ Update the default priority of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.DefaultPriorityForm(
- priorities=repo.priorities.values()
- )
- if form.validate_on_submit():
- priority = form.priority.data or None
- if priority in repo.priorities.values() or priority is None:
- repo.default_priority = priority
- try:
- flask.g.session.add(repo)
- flask.g.session.commit()
- if priority:
- flask.flash("Default priority set to %s" % priority)
- else:
- flask.flash("Default priority reset")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=repo.namespace,
- )
- + "#priorities-tab"
- )
- @UI_NS.route("/<repo>/update/milestones", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update/milestones", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/update/milestones", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/update/milestones", methods=["POST"]
- )
- @login_required
- @has_issue_tracker
- @is_admin_sess_timedout
- @is_repo_admin
- def update_milestones(repo, username=None, namespace=None):
- """ Update the milestones of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- error = False
- if form.validate_on_submit():
- redirect = flask.request.args.get("from")
- milestones = flask.request.form.getlist("milestones")
- miles = {}
- keys = []
- for idx in milestones:
- milestone = flask.request.form.get(
- "milestone_%s_name" % (idx), None
- )
- date = flask.request.form.get("milestone_%s_date" % (idx), None)
- active = (
- True
- if flask.request.form.get("milestone_%s_active" % (idx))
- else False
- )
- if milestone and milestone.strip():
- milestone = milestone.strip()
- if milestone in miles:
- flask.flash(
- "Milestone %s is present multiple times" % milestone,
- "error",
- )
- error = True
- break
- miles[milestone] = {
- "date": date.strip() if date else None,
- "active": active,
- }
- keys.append(milestone)
- if not error:
- try:
- repo.milestones = miles
- repo.milestones_keys = keys
- flask.g.session.add(repo)
- flask.g.session.commit()
- flask.flash("Milestones updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- if redirect == "issues":
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_issues",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- + "#roadmap-tab"
- )
- @UI_NS.route("/<repo>/default/branch/", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/default/branch/", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/default/branch/", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/default/branch/", methods=["POST"]
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def change_ref_head(repo, username=None, namespace=None):
- """ Change HEAD reference
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- branches = repo_obj.listall_branches()
- form = pagure.forms.DefaultBranchForm(branches=branches)
- if form.validate_on_submit():
- branchname = form.branches.data
- try:
- pagure.lib.git.git_set_ref_head(project=repo, branch=branchname)
- flask.flash("Default branch updated to %s" % branchname)
- except Exception as err: # pragma: no cover
- _log.exception(err)
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- + "#defaultbranch-tab"
- )
- @UI_NS.route("/<repo>/delete", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/delete", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/delete", methods=["POST"])
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/delete", methods=["POST"])
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def delete_repo(repo, username=None, namespace=None):
- """ Delete the present project.
- """
- repo = flask.g.repo
- del_project = pagure_config.get("ENABLE_DEL_PROJECTS", True)
- del_fork = pagure_config.get("ENABLE_DEL_FORKS", del_project)
- if (not repo.is_fork and not del_project) or (
- repo.is_fork and not del_fork
- ):
- flask.abort(404)
- if repo.read_only:
- flask.flash(
- "The ACLs of this project are being refreshed in the backend "
- "this prevents the project from being deleted. Please wait "
- "for this task to finish before trying again. Thanks!"
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#deleteproject-tab"
- )
- task = pagure.lib.tasks.delete_project.delay(
- namespace=repo.namespace,
- name=repo.name,
- user=repo.user.user if repo.is_fork else None,
- action_user=flask.g.fas_user.username,
- )
- return pagure.utils.wait_for_task(task)
- @UI_NS.route("/<repo>/hook_token", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/hook_token", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/hook_token", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/hook_token", methods=["POST"]
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def new_repo_hook_token(repo, username=None, namespace=None):
- """ Re-generate a hook token for the present project.
- """
- if not pagure_config.get("WEBHOOK", False):
- flask.abort(404)
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- if not form.validate_on_submit():
- flask.abort(400, description="Invalid request")
- try:
- repo.hook_token = pagure.lib.login.id_generator(40)
- flask.g.session.commit()
- flask.flash("New hook token generated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("Could not generate a new token for this project", "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#privatehookkey-tab"
- )
- @UI_NS.route("/<repo>/dropdeploykey/<int:keyid>", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/dropdeploykey/<int:keyid>", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<repo>/dropdeploykey/<int:keyid>", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/dropdeploykey/<int:keyid>",
- methods=["POST"],
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def remove_deploykey(repo, keyid, username=None, namespace=None):
- """ Remove the specified deploy key from the project.
- """
- if not pagure_config.get("DEPLOY_KEY", True):
- flask.abort(
- 404, description="This pagure instance disabled deploy keys"
- )
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- found = False
- sshkey = None
- for key in repo.deploykeys:
- if key.id == keyid:
- sshkey = key.public_ssh_key
- flask.g.session.delete(key)
- found = True
- break
- if not found:
- flask.flash("Deploy key does not exist in project.", "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=repo.namespace,
- )
- + "#deploykeys-tab"
- )
- try:
- flask.g.session.commit()
- pagure.lib.query.create_deploykeys_ssh_keys_on_disk(
- repo, pagure_config.get("GITOLITE_KEYDIR", None)
- )
- pagure.lib.tasks.gitolite_post_compile_only.delay()
- if (
- pagure_config.get("GIT_AUTH_BACKEND")
- == "pagure_authorized_keys"
- ):
- _log.info("SSH FOLDER: %s", pagure_config.get("SSH_FOLDER"))
- pagure.lib.tasks.remove_key_from_authorized_keys.delay(
- ssh_folder=pagure_config.get("SSH_FOLDER"), sshkey=sshkey
- )
- flask.flash("Deploy key removed")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("Deploy key could not be removed", "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#deploykey-tab"
- )
- @UI_NS.route("/<repo>/dropuser/<int:userid>", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/dropuser/<int:userid>", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/dropuser/<int:userid>", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/dropuser/<int:userid>",
- methods=["POST"],
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def remove_user(repo, userid, username=None, namespace=None):
- """ Remove the specified user from the project.
- """
- if not pagure_config.get("ENABLE_USER_MNGT", True):
- flask.abort(
- 404,
- description="User management not allowed in the pagure instance",
- )
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- delete_themselves = False
- if form.validate_on_submit():
- try:
- user = pagure.lib.query.get_user_by_id(
- flask.g.session, int(userid)
- )
- delete_themselves = user.username == flask.g.fas_user.username
- msg = pagure.lib.query.remove_user_of_project(
- flask.g.session, user, repo, flask.g.fas_user.username
- )
- flask.flash(msg)
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("User could not be removed", "error")
- except pagure.exceptions.PagureException as err:
- flask.flash("%s" % err, "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=repo.namespace,
- )
- + "#usersgroups-tab"
- )
- endpoint = "ui_ns.view_settings"
- tab = "#usersgroups-tab"
- if delete_themselves:
- endpoint = "ui_ns.view_repo"
- tab = ""
- return flask.redirect(
- flask.url_for(
- endpoint, repo=repo.name, username=username, namespace=namespace
- )
- + tab
- )
- @UI_NS.route("/<repo>/adddeploykey/", methods=("GET", "POST"))
- @UI_NS.route("/<repo>/adddeploykey", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/adddeploykey/", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/adddeploykey", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/adddeploykey/", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/adddeploykey", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/adddeploykey/",
- methods=("GET", "POST"),
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/adddeploykey", methods=("GET", "POST")
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def add_deploykey(repo, username=None, namespace=None):
- """ Add the specified deploy key to the project.
- """
- if not pagure_config.get("DEPLOY_KEY", True):
- flask.abort(
- 404, description="This pagure instance disabled deploy keys"
- )
- repo = flask.g.repo
- form = pagure.forms.AddDeployKeyForm()
- if form.validate_on_submit():
- user = _get_user(username=flask.g.fas_user.username)
- try:
- msg = pagure.lib.query.add_sshkey_to_project_or_user(
- flask.g.session,
- ssh_key=form.ssh_key.data,
- creator=user,
- project=repo,
- pushaccess=form.pushaccess.data,
- )
- flask.g.session.commit()
- pagure.lib.query.create_deploykeys_ssh_keys_on_disk(
- repo, pagure_config.get("GITOLITE_KEYDIR", None)
- )
- pagure.lib.tasks.gitolite_post_compile_only.delay()
- if (
- pagure_config.get("GIT_AUTH_BACKEND")
- == "pagure_authorized_keys"
- ):
- _log.info("SSH FOLDER: %s", pagure_config.get("SSH_FOLDER"))
- pagure.lib.tasks.add_key_to_authorized_keys.delay(
- ssh_folder=pagure_config.get("SSH_FOLDER"),
- username=flask.g.fas_user.username,
- sshkey=form.ssh_key.data,
- )
- flask.flash(msg)
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#deploykey-tab"
- )
- except pagure.exceptions.PagureException as msg:
- flask.g.session.rollback()
- _log.debug(msg)
- flask.flash(str(msg), "error")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("Deploy key could not be added", "error")
- return flask.render_template(
- "add_deploykey.html", form=form, username=username, repo=repo
- )
- @UI_NS.route("/<repo>/adduser/", methods=("GET", "POST"))
- @UI_NS.route("/<repo>/adduser", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/adduser/", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/adduser", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/adduser/", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/adduser", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/adduser/", methods=("GET", "POST")
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/adduser", methods=("GET", "POST")
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def add_user(repo, username=None, namespace=None):
- """ Add the specified user to the project.
- """
- if not pagure_config.get("ENABLE_USER_MNGT", True):
- flask.abort(
- 404,
- description="User management is not allowed in this "
- "pagure instance",
- )
- repo = flask.g.repo
- user_to_update = flask.request.args.get("user", "").strip()
- user_to_update_obj = None
- user_access = None
- if user_to_update:
- user_to_update_obj = pagure.lib.query.search_user(
- flask.g.session, username=user_to_update
- )
- user_access = pagure.lib.query.get_obj_access(
- flask.g.session, repo, user_to_update_obj
- )
- # The requested user is not found
- if user_to_update_obj is None:
- user_to_update = None
- user_access = None
- form = pagure.forms.AddUserForm()
- if form.validate_on_submit():
- try:
- msg = pagure.lib.query.add_user_to_project(
- flask.g.session,
- repo,
- new_user=form.user.data,
- user=flask.g.fas_user.username,
- access=form.access.data,
- required_groups=pagure_config.get("REQUIRED_GROUPS"),
- )
- flask.g.session.commit()
- pagure.lib.git.generate_gitolite_acls(project=repo)
- flask.flash(msg)
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#usersgroups-tab"
- )
- except pagure.exceptions.PagureException as msg:
- flask.g.session.rollback()
- _log.debug(msg)
- flask.flash(str(msg), "error")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("User could not be added", "error")
- access_levels = pagure.lib.query.get_access_levels(flask.g.session)
- return flask.render_template(
- "add_user.html",
- form=form,
- username=username,
- repo=repo,
- access_levels=access_levels,
- user_to_update=user_to_update,
- user_access=user_access,
- )
- @UI_NS.route("/<repo>/dropgroup/<int:groupid>", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/dropgroup/<int:groupid>", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<repo>/dropgroup/<int:groupid>", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/dropgroup/<int:groupid>",
- methods=["POST"],
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def remove_group_project(repo, groupid, username=None, namespace=None):
- """ Remove the specified group from the project.
- """
- if not pagure_config.get("ENABLE_USER_MNGT", True):
- flask.abort(
- 404,
- description="User management is not allowed in this "
- "pagure instance",
- )
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- grpids = [grp.id for grp in repo.groups]
- if groupid not in grpids:
- flask.flash(
- "Group does not seem to be part of this project", "error"
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#usersgroups-tab"
- )
- for grp in repo.groups:
- if grp.id == groupid:
- repo.groups.remove(grp)
- break
- try:
- # Mark the project as read_only, celery will unmark it
- pagure.lib.query.update_read_only_mode(
- flask.g.session, repo, read_only=True
- )
- flask.g.session.commit()
- pagure.lib.git.generate_gitolite_acls(project=repo)
- flask.flash("Group removed")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("Group could not be removed", "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#usersgroups-tab"
- )
- @UI_NS.route("/<repo>/addgroup/", methods=("GET", "POST"))
- @UI_NS.route("/<repo>/addgroup", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/addgroup/", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/addgroup", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/addgroup/", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/addgroup", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/addgroup/", methods=("GET", "POST")
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/addgroup", methods=("GET", "POST")
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def add_group_project(repo, username=None, namespace=None):
- """ Add the specified group to the project.
- """
- if not pagure_config.get("ENABLE_USER_MNGT", True):
- flask.abort(
- 404,
- description="User management is not allowed in this "
- "pagure instance",
- )
- repo = flask.g.repo
- group_to_update = flask.request.args.get("group", "").strip()
- group_to_update_obj = None
- group_access = None
- if group_to_update:
- group_to_update_obj = pagure.lib.query.search_groups(
- flask.g.session, group_name=group_to_update
- )
- group_access = pagure.lib.query.get_obj_access(
- flask.g.session, repo, group_to_update_obj
- )
- # The requested group is not found
- if group_to_update_obj is None:
- group_to_update = None
- group_access = None
- form = pagure.forms.AddGroupForm()
- if form.validate_on_submit():
- try:
- msg = pagure.lib.query.add_group_to_project(
- flask.g.session,
- repo,
- new_group=form.group.data,
- user=flask.g.fas_user.username,
- access=form.access.data,
- create=pagure_config.get("ENABLE_GROUP_MNGT", False),
- is_admin=pagure.utils.is_admin(),
- )
- flask.g.session.commit()
- pagure.lib.git.generate_gitolite_acls(project=repo)
- flask.flash(msg)
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#usersgroups-tab"
- )
- except pagure.exceptions.PagureException as msg:
- flask.g.session.rollback()
- _log.debug(msg)
- flask.flash(str(msg), "error")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("Group could not be added", "error")
- access_levels = pagure.lib.query.get_access_levels(flask.g.session)
- return flask.render_template(
- "add_group_project.html",
- form=form,
- username=username,
- repo=repo,
- access_levels=access_levels,
- group_to_update=group_to_update,
- group_access=group_access,
- )
- @UI_NS.route("/<repo>/regenerate", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/regenerate", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/regenerate", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/regenerate", methods=["POST"]
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def regenerate_git(repo, username=None, namespace=None):
- """ Regenerate the specified git repo with the content in the project.
- """
- repo = flask.g.repo
- regenerate = flask.request.form.get("regenerate")
- if not regenerate or regenerate.lower() not in ["tickets", "requests"]:
- flask.abort(
- 400,
- description="You can only regenerate tickest or requests repos",
- )
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- if regenerate.lower() == "requests" and repo.settings.get(
- "pull_requests"
- ):
- # delete the requests repo and reinit
- # in case there are no requests
- if len(repo.requests) == 0:
- pagure.lib.git.reinit_git(
- project=repo, repofolder=pagure_config["REQUESTS_FOLDER"]
- )
- for request in repo.requests:
- pagure.lib.git.update_git(request, repo=repo)
- flask.flash("Requests git repo updating")
- elif (
- regenerate.lower() == "tickets"
- and repo.settings.get("issue_tracker")
- and pagure_config.get("ENABLE_TICKETS")
- ):
- # delete the ticket repo and reinit
- # in case there are no tickets
- if len(repo.issues) == 0:
- pagure.lib.git.reinit_git(
- project=repo, repofolder=pagure_config["TICKETS_FOLDER"]
- )
- for ticket in repo.issues:
- pagure.lib.git.update_git(ticket, repo=repo)
- flask.flash("Tickets git repo updating")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#regen-tab"
- )
- @UI_NS.route("/<repo>/token/new/", methods=("GET", "POST"))
- @UI_NS.route("/<repo>/token/new", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/token/new/", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/token/new", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/token/new/", methods=("GET", "POST"))
- @UI_NS.route("/fork/<username>/<repo>/token/new", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/token/new/", methods=("GET", "POST")
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/token/new", methods=("GET", "POST")
- )
- @login_required
- @is_admin_sess_timedout
- def add_token(repo, username=None, namespace=None):
- """ Add a token to a specified project.
- """
- repo = flask.g.repo
- if not flask.g.repo_committer:
- flask.abort(
- 403,
- description="You are not allowed to change the settings for "
- "this project",
- )
- acls = pagure.lib.query.get_acls(
- flask.g.session, restrict=pagure_config.get("USER_ACLS")
- )
- form = pagure.forms.NewTokenForm(acls=acls)
- if form.validate_on_submit():
- try:
- pagure.lib.query.add_token_to_user(
- flask.g.session,
- repo,
- description=form.description.data.strip() or None,
- acls=form.acls.data,
- username=flask.g.fas_user.username,
- expiration_date=form.expiration_date.data,
- )
- flask.g.session.commit()
- flask.flash("Token created")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#apikeys-tab"
- )
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("API token could not be added", "error")
- # When form is displayed after an empty submission, show an error.
- if form.errors.get("acls"):
- flask.flash("You must select at least one permission.", "error")
- return flask.render_template(
- "add_token.html",
- select="settings",
- form=form,
- acls=acls,
- username=username,
- repo=repo,
- )
- @UI_NS.route("/<repo>/token/renew/<token_id>", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/token/renew/<token_id>", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<repo>/token/renew/<token_id>", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/token/renew/<token_id>",
- methods=["POST"],
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def renew_api_token(repo, token_id, username=None, namespace=None):
- """ Renew a token to a specified project.
- """
- repo = flask.g.repo
- token = pagure.lib.query.get_api_token(flask.g.session, token_id)
- if (
- not token
- or token.project.fullname != repo.fullname
- or token.user.username != flask.g.fas_user.username
- ):
- flask.abort(404, description="Token not found")
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- acls = [acl.name for acl in token.acls]
- try:
- pagure.lib.query.add_token_to_user(
- flask.g.session,
- repo,
- description=token.description or None,
- acls=acls,
- username=flask.g.fas_user.username,
- expiration_date=datetime.date.today()
- + datetime.timedelta(days=(30 * 6)),
- )
- flask.g.session.commit()
- flask.flash("Token created")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#apikeys-tab"
- )
- except pagure.exceptions.PagureException as err:
- flask.flash(str(err), "error")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- flask.flash("API token could not be renewed", "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#apikeys-tab"
- )
- @UI_NS.route("/<repo>/token/revoke/<token_id>", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/token/revoke/<token_id>", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<repo>/token/revoke/<token_id>", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/token/revoke/<token_id>",
- methods=["POST"],
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def revoke_api_token(repo, token_id, username=None, namespace=None):
- """ Revokie a token to a specified project.
- """
- repo = flask.g.repo
- token = pagure.lib.query.get_api_token(flask.g.session, token_id)
- if (
- not token
- or token.project.fullname != repo.fullname
- or token.user.username != flask.g.fas_user.username
- ):
- flask.abort(404, description="Token not found")
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- try:
- if token.expiration >= datetime.datetime.utcnow():
- token.expiration = datetime.datetime.utcnow()
- flask.g.session.add(token)
- flask.g.session.commit()
- flask.flash("Token revoked")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.exception(err)
- message = flask.Markup(
- "Token could not be revoked,"
- ' please <a href="/about">contact an administrator</a>'
- )
- flask.flash(message, "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=namespace,
- )
- + "#apikeys-tab"
- )
- @UI_NS.route(
- "/<repo>/edit/<path:branchname>/f/<path:filename>", methods=("GET", "POST")
- )
- @UI_NS.route(
- "/<namespace>/<repo>/edit/<path:branchname>/f/<path:filename>",
- methods=("GET", "POST"),
- )
- @UI_NS.route(
- "/fork/<username>/<repo>/edit/<path:branchname>/f/<path:filename>",
- methods=("GET", "POST"),
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/edit/<path:branchname>/f/"
- "<path:filename>",
- methods=("GET", "POST"),
- )
- @login_required
- @is_repo_admin
- def edit_file(repo, branchname, filename, username=None, namespace=None):
- """ Edit a file online.
- """
- repo = flask.g.repo
- repo_obj = flask.g.repo_obj
- user = pagure.lib.query.search_user(
- flask.g.session, username=flask.g.fas_user.username
- )
- if repo_obj.is_empty:
- flask.abort(404, description="Empty repo cannot have a file")
- form = pagure.forms.EditFileForm(emails=user.emails)
- branch = None
- if branchname in repo_obj.listall_branches():
- branch = repo_obj.lookup_branch(branchname)
- commit = branch.peel(pygit2.Commit)
- else:
- flask.abort(400, description="Invalid branch specified")
- if form.validate_on_submit():
- try:
- task = pagure.lib.tasks.update_file_in_git.delay(
- repo.name,
- repo.namespace,
- repo.user.username if repo.is_fork else None,
- branch=branchname,
- branchto=form.branch.data,
- filename=filename,
- content=form.content.data,
- message="%s\n\n%s"
- % (
- form.commit_title.data.strip(),
- form.commit_message.data.strip(),
- ),
- username=user.username,
- email=form.email.data,
- )
- return pagure.utils.wait_for_task(task)
- except pagure.exceptions.PagureException as err: # pragma: no cover
- _log.exception(err)
- flask.flash("Commit could not be done", "error")
- data = form.content.data
- elif flask.request.method == "GET":
- form.email.data = user.default_email
- content = __get_file_in_tree(
- repo_obj, commit.tree, filename.split("/")
- )
- if not content or isinstance(content, pygit2.Tree):
- flask.abort(404, description="File not found")
- if is_binary_string(content.data):
- flask.abort(400, description="Cannot edit binary files")
- try:
- data = repo_obj[content.oid].data.decode("utf-8")
- except UnicodeDecodeError: # pragma: no cover
- # In theory we shouldn't reach here since we check if the file
- # is binary with `is_binary_string()` above
- flask.abort(400, description="Cannot edit binary files")
- else:
- data = form.content.data
- if not isinstance(data, six.text_type):
- data = data.decode("utf-8")
- return flask.render_template(
- "edit_file.html",
- select="tree",
- repo=repo,
- username=username,
- branchname=branchname,
- data=data,
- filename=filename,
- form=form,
- user=user,
- )
- @UI_NS.route("/<repo>/b/<path:branchname>/delete", methods=["POST"])
- @UI_NS.route(
- "/<namespace>/<repo>/b/<path:branchname>/delete", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<repo>/b/<path:branchname>/delete", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/b/<path:branchname>/delete",
- methods=["POST"],
- )
- @login_required
- def delete_branch(repo, branchname, username=None, namespace=None):
- """ Delete the branch of a project.
- """
- if not flask.g.repo.is_fork and not pagure_config.get(
- "ALLOW_DELETE_BRANCH", True
- ):
- flask.abort(
- 404,
- description="This pagure instance does not allow branch deletion",
- )
- repo_obj = flask.g.repo_obj
- if not flask.g.repo_committer:
- flask.abort(
- 403,
- description="You are not allowed to delete branch for "
- "this project",
- )
- if six.PY2:
- branchname = branchname.encode("utf-8")
- if branchname == "master":
- flask.abort(
- 403, description="You are not allowed to delete the master branch"
- )
- if branchname not in repo_obj.listall_branches():
- flask.abort(404, description="Branch not found")
- task = pagure.lib.tasks.delete_branch.delay(
- repo, namespace, username, branchname
- )
- return pagure.utils.wait_for_task(task)
- @UI_NS.route("/docs/<repo>/")
- @UI_NS.route("/docs/<repo>/<path:filename>")
- @UI_NS.route("/docs/<namespace>/<repo>/")
- @UI_NS.route("/docs/<namespace>/<repo>/<path:filename>")
- @UI_NS.route("/docs/fork/<username>/<repo>/")
- @UI_NS.route("/docs/fork/<username>/<namespace>/<repo>/<path:filename>")
- @UI_NS.route("/docs/fork/<username>/<repo>/")
- @UI_NS.route("/docs/fork/<username>/<namespace>/<repo>/<path:filename>")
- def view_docs(repo, username=None, filename=None, namespace=None):
- """ Display the documentation
- """
- repo = flask.g.repo
- if not pagure_config.get("DOC_APP_URL"):
- flask.abort(404, description="This pagure instance has no doc server")
- return flask.render_template(
- "docs.html",
- select="docs",
- repo=repo,
- username=username,
- filename=filename,
- endpoint="view_docs",
- )
- @UI_NS.route("/<repo>/activity/")
- @UI_NS.route("/<repo>/activity")
- @UI_NS.route("/<namespace>/<repo>/activity/")
- @UI_NS.route("/<namespace>/<repo>/activity")
- def view_project_activity(repo, namespace=None):
- """ Display the activity feed
- """
- if not pagure_config.get("DATAGREPPER_URL"):
- flask.abort(404)
- repo = flask.g.repo
- return flask.render_template("activity.html", repo=repo)
- @UI_NS.route("/<repo>/stargazers/")
- @UI_NS.route("/fork/<username>/<repo>/stargazers/")
- @UI_NS.route("/<namespace>/<repo>/stargazers/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/stargazers/")
- def view_stargazers(repo, username=None, namespace=None):
- """ View all the users who have starred the project """
- stargazers = flask.g.repo.stargazers
- users = [star.user for star in stargazers]
- return flask.render_template(
- "repo_stargazers.html",
- repo=flask.g.repo,
- username=username,
- namespace=namespace,
- users=users,
- )
- @UI_NS.route("/<repo>/star/<star>", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/star/<star>", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/star/<star>", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/star/<star>", methods=["POST"]
- )
- @login_required
- def star_project(repo, star, username=None, namespace=None):
- """ Star or Unstar a project
- :arg repo: string representing the project which has to be starred or
- unstarred.
- :arg star: either '0' or '1' for unstar and star respectively
- :arg username: string representing the user the fork of whose is being
- starred or unstarred.
- :arg namespace: namespace of the project if any
- """
- return_point = flask.url_for("ui_ns.index")
- if flask.request.referrer is not None and pagure.utils.is_safe_url(
- flask.request.referrer
- ):
- return_point = flask.request.referrer
- form = pagure.forms.ConfirmationForm()
- if not form.validate_on_submit():
- flask.abort(400)
- if star not in ["0", "1"]:
- flask.abort(400)
- try:
- msg = pagure.lib.query.update_star_project(
- flask.g.session,
- user=flask.g.fas_user.username,
- repo=flask.g.repo,
- star=star,
- )
- flask.g.session.commit()
- flask.flash(msg)
- except SQLAlchemyError:
- flask.flash("Could not star the project")
- return flask.redirect(return_point)
- @UI_NS.route("/<repo>/watch/settings/<watch>", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/watch/settings/<watch>", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<repo>/watch/settings/<watch>", methods=["POST"]
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/watch/settings/<watch>",
- methods=["POST"],
- )
- @login_required
- def watch_repo(repo, watch, username=None, namespace=None):
- """ Marked for watching or unwatching
- """
- return_point = flask.url_for("ui_ns.index")
- if pagure.utils.is_safe_url(flask.request.referrer):
- return_point = flask.request.referrer
- form = pagure.forms.ConfirmationForm()
- if not form.validate_on_submit():
- flask.abort(400)
- if "%s" % watch not in ["0", "1", "2", "3", "-1"]:
- flask.abort(400)
- try:
- msg = pagure.lib.query.update_watch_status(
- flask.g.session, flask.g.repo, flask.g.fas_user.username, watch
- )
- flask.g.session.commit()
- flask.flash(msg)
- except pagure.exceptions.PagureException as msg:
- _log.debug(msg)
- flask.flash(str(msg), "error")
- return flask.redirect(return_point)
- @UI_NS.route("/<repo>/update/public_notif", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/public_notif", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/public_notif", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/public_notif", methods=["POST"]
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def update_public_notifications(repo, username=None, namespace=None):
- """ Update the public notification settings of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.PublicNotificationForm()
- if form.validate_on_submit():
- issue_notifs = [
- w.strip() for w in form.issue_notifs.data.split(",") if w.strip()
- ]
- pr_notifs = [
- w.strip() for w in form.pr_notifs.data.split(",") if w.strip()
- ]
- try:
- notifs = repo.notifications
- notifs["issues"] = issue_notifs
- notifs["requests"] = pr_notifs
- repo.notifications = notifs
- flask.g.session.add(repo)
- flask.g.session.commit()
- flask.flash("Project updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- else:
- flask.flash(
- "Unable to adjust one or more of the email provided", "error"
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=repo.namespace,
- )
- + "#publicnotifications-tab"
- )
- @UI_NS.route("/<repo>/update/close_status", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update/close_status", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/update/close_status", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/update/close_status", methods=["POST"]
- )
- @login_required
- @has_issue_tracker
- @is_admin_sess_timedout
- @is_repo_admin
- def update_close_status(repo, username=None, namespace=None):
- """ Update the close_status of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- close_status = [
- w.strip()
- for w in flask.request.form.getlist("close_status")
- if w.strip()
- ]
- try:
- repo.close_status = close_status
- flask.g.session.add(repo)
- flask.g.session.commit()
- flask.flash("List of close status updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- + "#closestatus-tab"
- )
- @UI_NS.route("/<repo>/update/quick_replies", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update/quick_replies", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/update/quick_replies", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/update/quick_replies",
- methods=["POST"],
- )
- @login_required
- @has_issue_tracker
- @has_pr_enabled
- @is_admin_sess_timedout
- @is_repo_admin
- def update_quick_replies(repo, username=None, namespace=None):
- """ Update the quick_replies of a project.
- """
- repo = flask.g.repo
- if not repo.settings.get("pull_requests", True):
- flask.abort(
- 404, description="Pull requests are disabled for this project"
- )
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- quick_replies = [
- w.strip()
- for w in flask.request.form.getlist("quick_reply")
- if w.strip()
- ]
- try:
- repo.quick_replies = quick_replies
- flask.g.session.add(repo)
- flask.g.session.commit()
- flask.flash("List of quick replies updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- + "#quickreplies-tab"
- )
- @UI_NS.route("/<repo>/update/custom_keys", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update/custom_keys", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/update/custom_keys", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/update/custom_keys", methods=["POST"]
- )
- @login_required
- @has_issue_tracker
- @is_admin_sess_timedout
- @is_repo_admin
- def update_custom_keys(repo, username=None, namespace=None):
- """ Update the custom_keys of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- custom_keys = [
- w.strip()
- for w in flask.request.form.getlist("custom_keys")
- if w.strip()
- ]
- custom_keys_type = [
- w.strip()
- for w in flask.request.form.getlist("custom_keys_type")
- if w.strip()
- ]
- custom_keys_data = [
- w.strip() for w in flask.request.form.getlist("custom_keys_data")
- ]
- custom_keys_notify = []
- for idx in range(len(custom_keys)):
- custom_keys_notify.append(
- "%s"
- % flask.request.form.get("custom_keys_notify-%s" % (idx + 1))
- )
- try:
- msg = pagure.lib.query.set_custom_key_fields(
- flask.g.session,
- repo,
- custom_keys,
- custom_keys_type,
- custom_keys_data,
- custom_keys_notify,
- )
- flask.g.session.commit()
- flask.flash(msg)
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- + "#customfields-tab"
- )
- @UI_NS.route("/<repo>/delete/report", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/delete/report", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/delete/report", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/delete/report", methods=["POST"]
- )
- @login_required
- @has_issue_tracker
- @is_admin_sess_timedout
- @is_repo_admin
- def delete_report(repo, username=None, namespace=None):
- """ Delete a report from a project.
- """
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- report = flask.request.form.get("report")
- reports = repo.reports
- if report not in reports:
- flask.flash("Unknown report: %s" % report, "error")
- else:
- del reports[report]
- repo.reports = reports
- try:
- flask.g.session.add(repo)
- flask.g.session.commit()
- flask.flash("List of reports updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- + "#reports-tab"
- )
- @UI_NS.route("/<repo>/torepospanner", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/torepospanner", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/torepospanner", methods=["POST"])
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/torepospanner", methods=["POST"]
- )
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def move_to_repospanner(repo, username=None, namespace=None):
- """ Give a project to someone else.
- """
- repo = flask.g.repo
- if not pagure.utils.is_admin():
- flask.abort(
- 403,
- description="You are not allowed to transfer this project "
- "to repoSpanner",
- )
- if not pagure_config.get("REPOSPANNER_ADMIN_MIGRATION"):
- flask.abort(
- 403, description="It is not allowed to request migration of a repo"
- )
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- region = flask.request.form.get("region", "").strip()
- if not region:
- flask.abort(404, description="No target region specified")
- if region not in pagure_config.get("REPOSPANNER_REGIONS"):
- flask.abort(404, description="Invalid region specified")
- _log.info(
- "Repo %s requested to be migrated to repoSpanner region %s",
- repo.fullname,
- region,
- )
- task = pagure.lib.tasks.move_to_repospanner.delay(
- repo.name, namespace, username, region
- )
- return pagure.utils.wait_for_task(
- task,
- prev=flask.url_for(
- "ui_ns.view_repo",
- username=username,
- repo=repo.name,
- namespace=namespace,
- ),
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_repo",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- )
- @UI_NS.route("/<repo>/give", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/give", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/give", methods=["POST"])
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/give", methods=["POST"])
- @login_required
- @is_admin_sess_timedout
- @is_repo_admin
- def give_project(repo, username=None, namespace=None):
- """ Give a project to someone else.
- """
- if not pagure_config.get("ENABLE_GIVE_PROJECTS", True):
- flask.abort(404)
- repo = flask.g.repo
- if (
- flask.g.fas_user.username != repo.user.user
- and not pagure.utils.is_admin()
- ):
- flask.abort(
- 403, description="You are not allowed to give this project"
- )
- form = pagure.forms.ConfirmationForm()
- if form.validate_on_submit():
- new_username = flask.request.form.get("user", "").strip()
- if not new_username:
- flask.abort(404, description="No user specified")
- new_owner = pagure.lib.query.search_user(
- flask.g.session, username=new_username
- )
- if not new_owner:
- flask.abort(
- 404, description="No such user %s found" % new_username
- )
- failed = False
- try:
- old_main_admin = repo.user.user
- pagure.lib.query.set_project_owner(
- flask.g.session,
- repo,
- new_owner,
- required_groups=pagure_config.get("REQUIRED_GROUPS"),
- )
- flask.g.session.commit()
- except pagure.exceptions.PagureException as msg:
- failed = True
- flask.g.session.rollback()
- _log.debug(msg)
- flask.flash(str(msg), "error")
- except SQLAlchemyError: # pragma: no cover
- failed = True
- flask.g.session.rollback()
- flask.flash(
- "Due to a database error, this project could not be "
- "transferred.",
- "error",
- )
- if not failed:
- try:
- # If the person doing the action is the former main admin, keep
- # them as admins
- if flask.g.fas_user.username == old_main_admin:
- pagure.lib.query.add_user_to_project(
- flask.g.session,
- repo,
- new_user=flask.g.fas_user.username,
- user=flask.g.fas_user.username,
- )
- flask.g.session.commit()
- except pagure.exceptions.PagureException as msg:
- flask.g.session.rollback()
- _log.debug(msg)
- except SQLAlchemyError: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(
- "Due to a database error, this access could not be "
- "entirely set.",
- "error",
- )
- pagure.lib.git.generate_gitolite_acls(project=repo)
- flask.flash(
- "The project has been transferred to %s" % new_username
- )
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_repo",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- )
- @UI_NS.route("/<repo>/dowait/")
- @UI_NS.route("/<repo>/dowait")
- @UI_NS.route("/<namespace>/<repo>/dowait/")
- @UI_NS.route("/<namespace>/<repo>/dowait")
- @UI_NS.route("/fork/<username>/<repo>/dowait/")
- @UI_NS.route("/fork/<username>/<repo>/dowait")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/dowait/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/dowait")
- def project_dowait(repo, username=None, namespace=None):
- """ Schedules a task that just waits 10 seconds for testing locking.
- This is not available unless ALLOW_PROJECT_DOWAIT is set to True, which
- should only ever be done in test instances.
- """
- if not pagure_config.get("ALLOW_PROJECT_DOWAIT", False):
- flask.abort(401, description="No")
- task = pagure.lib.tasks.project_dowait.delay(
- name=repo, namespace=namespace, user=username
- )
- return pagure.utils.wait_for_task(task)
- @UI_NS.route("/<repo>/stats/")
- @UI_NS.route("/<repo>/stats")
- @UI_NS.route("/<namespace>/<repo>/stats/")
- @UI_NS.route("/<namespace>/<repo>/stats")
- @UI_NS.route("/fork/<username>/<repo>/stats/")
- @UI_NS.route("/fork/<username>/<repo>/stats")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/stats/")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/stats")
- def view_stats(repo, username=None, namespace=None):
- """ Displays some statistics about the specified repo.
- """
- return flask.render_template(
- "repo_stats.html", select="stats", username=username, repo=flask.g.repo
- )
- @UI_NS.route("/<repo>/update/tags", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/update/tags", methods=["POST"])
- @login_required
- @is_repo_admin
- @has_issue_or_pr_enabled
- def update_tags(repo, username=None, namespace=None):
- """ Update the tags of a project.
- """
- repo = flask.g.repo
- form = pagure.forms.ConfirmationForm()
- error = False
- if form.validate_on_submit():
- # Uniquify and order preserving
- seen = set()
- tags = [
- tag.strip()
- for tag in flask.request.form.getlist("tag")
- if tag.strip()
- and tag.strip() not in seen # noqa
- and not seen.add(tag.strip())
- ]
- tag_descriptions = [
- desc.strip()
- for desc in flask.request.form.getlist("tag_description")
- ]
- # Uniquify and order preserving
- colors = [
- col.strip()
- for col in flask.request.form.getlist("tag_color")
- if col.strip()
- ]
- pattern = re.compile(pagure.forms.TAGS_REGEX, re.IGNORECASE)
- for tag in tags:
- if not pattern.match(tag):
- flask.flash(
- "Tag: %s contains one or more invalid characters" % tag,
- "error",
- )
- error = True
- color_pattern = re.compile(r"^#\w{3,6}$")
- for color in colors:
- if not color_pattern.match(color):
- flask.flash(
- "Color: %s does not match the expected pattern" % color,
- "error",
- )
- error = True
- if not (len(tags) == len(colors) == len(tag_descriptions)):
- error = True
- # Store the lengths because we are going to use them a lot
- len_tags = len(tags)
- len_tag_descriptions = len(tag_descriptions)
- len_colors = len(colors)
- error_message = "Error: Incomplete request. "
- if len_colors > len_tags or len_tag_descriptions > len_tags:
- error_message += "One or more tag fields missing."
- elif len_colors < len_tags:
- error_message += "One or more tag color fields missing."
- elif len_tag_descriptions < len_tags:
- error_message += "One or more tag description fields missing."
- flask.flash(error_message, "error")
- if not error:
- known_tags = [tag.tag for tag in repo.tags_colored]
- for idx, tag in enumerate(tags):
- if tag in known_tags:
- flask.flash("Duplicated tag: %s" % tag, "error")
- break
- try:
- pagure.lib.query.new_tag(
- flask.g.session,
- tag,
- tag_descriptions[idx],
- colors[idx],
- repo.id,
- )
- flask.g.session.commit()
- flask.flash("Tags updated")
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(str(err), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- username=username,
- repo=repo.name,
- namespace=namespace,
- )
- + "#projecttags-tab"
- )
- @UI_NS.route("/<repo>/droptag/", methods=["POST"])
- @UI_NS.route("/<namespace>/<repo>/droptag/", methods=["POST"])
- @UI_NS.route("/fork/<username>/<repo>/droptag/", methods=["POST"])
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/droptag/", methods=["POST"])
- @login_required
- @is_repo_admin
- @has_issue_or_pr_enabled
- def remove_tag(repo, username=None, namespace=None):
- """ Remove the specified tag, associated with the issues, from the project.
- """
- repo = flask.g.repo
- form = pagure.forms.DeleteIssueTagForm()
- if form.validate_on_submit():
- tags = form.tag.data
- tags = [tag.strip() for tag in tags.split(",")]
- msgs = pagure.lib.query.remove_tags(
- flask.g.session, repo, tags, user=flask.g.fas_user.username
- )
- try:
- flask.g.session.commit()
- for msg in msgs:
- flask.flash(msg)
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.error(err)
- flask.flash("Could not remove tag: %s" % ",".join(tags), "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=repo.namespace,
- )
- + "#projecttags-tab"
- )
- @UI_NS.route("/<repo>/tag/<tag>/edit/", methods=("GET", "POST"))
- @UI_NS.route("/<repo>/tag/<tag>/edit", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/tag/<tag>/edit/", methods=("GET", "POST"))
- @UI_NS.route("/<namespace>/<repo>/tag/<tag>/edit", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<repo>/tag/<tag>/edit/", methods=("GET", "POST")
- )
- @UI_NS.route("/fork/<username>/<repo>/tag/<tag>/edit", methods=("GET", "POST"))
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/tag/<tag>/edit/",
- methods=("GET", "POST"),
- )
- @UI_NS.route(
- "/fork/<username>/<namespace>/<repo>/tag/<tag>/edit",
- methods=("GET", "POST"),
- )
- @login_required
- @is_repo_admin
- @has_issue_or_pr_enabled
- def edit_tag(repo, tag, username=None, namespace=None):
- """ Edit the specified tag associated with the issues of a project.
- """
- repo = flask.g.repo
- tags = pagure.lib.query.get_tags_of_project(flask.g.session, repo)
- if not tags:
- flask.abort(404, description="Project has no tags to edit")
- # Check the tag exists, and get its old/original color
- tagobj = pagure.lib.query.get_colored_tag(flask.g.session, tag, repo.id)
- if not tagobj:
- flask.abort(404, description="Tag %s not found in this project" % tag)
- form = pagure.forms.AddIssueTagForm()
- if form.validate_on_submit():
- new_tag = form.tag.data
- new_tag_description = form.tag_description.data
- new_tag_color = form.tag_color.data
- msgs = pagure.lib.query.edit_issue_tags(
- flask.g.session,
- repo,
- tagobj,
- new_tag,
- new_tag_description,
- new_tag_color,
- user=flask.g.fas_user.username,
- )
- try:
- flask.g.session.commit()
- for msg in msgs:
- flask.flash(msg)
- except SQLAlchemyError as err: # pragma: no cover
- flask.g.session.rollback()
- _log.error(err)
- flask.flash("Could not edit tag: %s" % tag, "error")
- return flask.redirect(
- flask.url_for(
- "ui_ns.view_settings",
- repo=repo.name,
- username=username,
- namespace=repo.namespace,
- )
- + "#projecttags-tab"
- )
- elif flask.request.method == "GET":
- tag_color = tagobj.tag_color
- if tag_color == "DeepSkyBlue":
- tag_color = "#00bfff"
- form.tag_color.data = tag_color
- form.tag_description.data = tagobj.tag_description
- form.tag.data = tag
- return flask.render_template(
- "edit_tag.html", username=username, repo=repo, form=form, tagname=tag
- )
- @UI_NS.route("/<repo>/archive/<ref>/<name>.tar")
- @UI_NS.route("/<namespace>/<repo>/archive/<ref>/<name>.tar")
- @UI_NS.route("/fork/<username>/<repo>/archive/<ref>/<name>.tar")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/archive/<ref>/<name>.tar")
- def get_project_archive_tar(repo, ref, name, namespace=None, username=None):
- """ Generate an archive or redirect the user to where it already exists
- """
- return generate_project_archive(
- repo,
- ref,
- name,
- extension="tar",
- namespace=namespace,
- username=username,
- )
- @UI_NS.route("/<repo>/archive/<ref>/<name>.tar.gz")
- @UI_NS.route("/<namespace>/<repo>/archive/<ref>/<name>.tar.gz")
- @UI_NS.route("/fork/<username>/<repo>/archive/<ref>/<name>.tar.gz")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/archive/<ref>/<name>.tar.gz")
- def get_project_archive_tar_gz(repo, ref, name, namespace=None, username=None):
- """ Generate an archive or redirect the user to where it already exists
- """
- return generate_project_archive(
- repo,
- ref,
- name,
- extension="tar.gz",
- namespace=namespace,
- username=username,
- )
- @UI_NS.route("/<repo>/archive/<ref>/<name>.zip")
- @UI_NS.route("/<namespace>/<repo>/archive/<ref>/<name>.zip")
- @UI_NS.route("/fork/<username>/<repo>/archive/<ref>/<name>.zip")
- @UI_NS.route("/fork/<username>/<namespace>/<repo>/archive/<ref>/<name>.zip")
- def get_project_archive_zip(repo, ref, name, namespace=None, username=None):
- """ Generate an archive or redirect the user to where it already exists
- """
- return generate_project_archive(
- repo,
- ref,
- name,
- extension="zip",
- namespace=namespace,
- username=username,
- )
- def generate_project_archive(
- repo, ref, name, extension, namespace=None, username=None
- ):
- """ Generate an archive or redirect the user to where it already
- exists.
- """
- archive_folder = pagure_config.get("ARCHIVE_FOLDER")
- if not archive_folder:
- _log.debug("No ARCHIVE_FOLDER specified in the configuration")
- flask.abort(
- 404,
- description="This pagure instance isn't configured to support "
- "this feature",
- )
- if not os.path.exists(archive_folder):
- _log.debug("No ARCHIVE_FOLDER could not be found on disk")
- flask.abort(
- 500,
- description="Incorrect configuration, please contact your admin",
- )
- extensions = ["tar.gz", "tar", "zip"]
- if extension not in extensions:
- _log.debug("%s no in %s", extension, extensions)
- flask.abort(400, description="Invalid archive format specified")
- name = werkzeug.utils.secure_filename(name)
- repo_obj = flask.g.repo_obj
- ref_string = "refs/tags/%s" % ref
- commit = None
- tag = None
- if ref_string in repo_obj.listall_references():
- reference = repo_obj.lookup_reference(ref_string)
- tag = repo_obj[reference.target]
- if isinstance(tag, pygit2.Tag):
- commit = tag.peel(pygit2.Commit)
- elif isinstance(tag, pygit2.Commit):
- commit = tag
- else:
- _log.debug("Found %s instead of a tag", tag)
- flask.abort(400, description="Invalid reference provided")
- else:
- try:
- commit = repo_obj.get(ref)
- except ValueError:
- flask.abort(404, description="Invalid commit provided")
- if not isinstance(commit, pygit2.Commit):
- flask.abort(400, description="Invalid reference specified")
- tag_path = ""
- tag_filename = None
- if tag:
- tag_filename = werkzeug.utils.secure_filename(ref)
- tag_path = os.path.join("tags", tag_filename)
- path = os.path.join(
- archive_folder,
- flask.g.repo.fullname,
- tag_path,
- commit.oid.hex,
- "%s.%s" % (name, extension),
- )
- headers = {
- str("Content-Disposition"): "attachment",
- str("Content-Type"): "application/x-gzip",
- }
- if os.path.exists(path):
- def _send_data():
- with open(path, "rb") as stream:
- yield stream.read()
- _log.info("Sending the existing archive")
- return flask.Response(
- flask.stream_with_context(_send_data()), headers=headers
- )
- _log.info("Re-generating the archive")
- task = pagure.lib.tasks.generate_archive.delay(
- repo,
- namespace=namespace,
- username=username,
- commit=commit.oid.hex,
- tag=tag_filename,
- name=name,
- archive_fmt=extension,
- )
- def _wait_for_task_and_send_data():
- while not task.ready():
- import time
- _log.info("waiting")
- time.sleep(0.5)
- with open(path, "rb") as stream:
- yield stream.read()
- _log.info("Sending the existing archive")
- return flask.Response(
- flask.stream_with_context(_wait_for_task_and_send_data()),
- headers=headers,
- )
- return pagure.utils.wait_for_task(task)
|